object.c 8.38 KB
Newer Older
1
/*
schu committed
2
 * Copyright (C) 2009-2012 the libgit2 contributors
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 */
#include <stdarg.h>

#include "git2/object.h"

#include "common.h"
#include "repository.h"

#include "commit.h"
#include "tree.h"
#include "blob.h"
#include "tag.h"

static const int OBJECT_BASE_SIZE = 4096;

static struct {
Vicent Marti committed
22 23
	const char	*str;	/* type name string */
	int			loose; /* valid loose object type flag */
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
	size_t		size;	/* size in bytes of the object structure */
} git_objects_table[] = {
	/* 0 = GIT_OBJ__EXT1 */
	{ "", 0, 0},

	/* 1 = GIT_OBJ_COMMIT */
	{ "commit", 1, sizeof(struct git_commit)},

	/* 2 = GIT_OBJ_TREE */
	{ "tree", 1, sizeof(struct git_tree) },

	/* 3 = GIT_OBJ_BLOB */
	{ "blob", 1, sizeof(struct git_blob) },

	/* 4 = GIT_OBJ_TAG */
	{ "tag", 1, sizeof(struct git_tag) },

	/* 5 = GIT_OBJ__EXT2 */
	{ "", 0, 0 },

	/* 6 = GIT_OBJ_OFS_DELTA */
	{ "OFS_DELTA", 0, 0 },

	/* 7 = GIT_OBJ_REF_DELTA */
	{ "REF_DELTA", 0, 0	}
};

51 52 53 54 55 56 57 58 59 60 61 62
static int create_object(git_object **object_out, git_otype type)
{
	git_object *object = NULL;

	assert(object_out);

	*object_out = NULL;

	switch (type) {
	case GIT_OBJ_COMMIT:
	case GIT_OBJ_TAG:
	case GIT_OBJ_BLOB:
Vicent Marti committed
63
	case GIT_OBJ_TREE:
64
		object = git__malloc(git_object__size(type));
65
		GITERR_CHECK_ALLOC(object);
66 67 68 69
		memset(object, 0x0, git_object__size(type));
		break;

	default:
70 71
		giterr_set(GITERR_INVALID, "The given type is invalid");
		return -1;
72 73
	}

Vicent Marti committed
74
	object->type = type;
75 76

	*object_out = object;
77
	return 0;
78 79
}

80 81 82 83 84 85 86 87 88 89
int git_object__from_odb_object(
	git_object **object_out,
	git_repository *repo,
	git_odb_object *odb_obj,
	git_otype type)
{
	int error;
	git_object *object = NULL;

	if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) {
90
		giterr_set(GITERR_INVALID, "The requested type does not match the type in the ODB");
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
		return GIT_ENOTFOUND;
	}

	type = odb_obj->raw.type;

	if ((error = create_object(&object, type)) < 0)
		return error;

	/* Initialize parent object */
	git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
	object->repo = repo;

	switch (type) {
	case GIT_OBJ_COMMIT:
		error = git_commit__parse((git_commit *)object, odb_obj);
		break;

	case GIT_OBJ_TREE:
		error = git_tree__parse((git_tree *)object, odb_obj);
		break;

	case GIT_OBJ_TAG:
		error = git_tag__parse((git_tag *)object, odb_obj);
		break;

	case GIT_OBJ_BLOB:
		error = git_blob__parse((git_blob *)object, odb_obj);
		break;

	default:
		break;
	}

	if (error < 0)
		git_object__free(object);
	else
		*object_out = git_cache_try_store(&repo->objects, object);

	return error;
}

132 133 134 135
int git_object_lookup_prefix(
	git_object **object_out,
	git_repository *repo,
	const git_oid *id,
136
	size_t len,
137
	git_otype type)
138 139
{
	git_object *object = NULL;
140
	git_odb *odb = NULL;
Vicent Marti committed
141
	git_odb_object *odb_obj;
142
	int error = 0;
143 144 145

	assert(repo && object_out && id);

146
	if (len < GIT_OID_MINPREFIXLEN)
147
		return GIT_EAMBIGUOUS;
Vicent Marti committed
148

149
	error = git_repository_odb__weakptr(&odb, repo);
150
	if (error < 0)
151 152
		return error;

Vicent Marti committed
153
	if (len > GIT_OID_HEXSZ)
154
		len = GIT_OID_HEXSZ;
155

Vicent Marti committed
156
	if (len == GIT_OID_HEXSZ) {
157 158 159 160 161
		/* We want to match the full id : we can first look up in the cache,
		 * since there is no need to check for non ambiguousity
		 */
		object = git_cache_get(&repo->objects, id);
		if (object != NULL) {
162
			if (type != GIT_OBJ_ANY && type != object->type) {
163
				git_object_free(object);
164
				giterr_set(GITERR_INVALID, "The requested type does not match the type in ODB");
165
				return GIT_ENOTFOUND;
166
			}
167 168

			*object_out = object;
169
			return 0;
170 171 172 173 174 175 176
		}

		/* Object was not found in the cache, let's explore the backends.
		 * We could just use git_odb_read_unique_short_oid,
		 * it is the same cost for packed and loose object backends,
		 * but it may be much more costly for sqlite and hiredis.
		 */
177
		error = git_odb_read(&odb_obj, odb, id);
178 179 180 181 182 183 184 185 186 187 188 189
	} else {
		git_oid short_oid;

		/* We copy the first len*4 bits from id and fill the remaining with 0s */
		memcpy(short_oid.id, id->id, (len + 1) / 2);
		if (len % 2)
			short_oid.id[len / 2] &= 0xF0;
		memset(short_oid.id + (len + 1) / 2, 0, (GIT_OID_HEXSZ - len) / 2);

		/* If len < GIT_OID_HEXSZ (a strict short oid was given), we have
		 * 2 options :
		 * - We always search in the cache first. If we find that short oid is
Vicent Marti committed
190 191 192 193
		 *	ambiguous, we can stop. But in all the other cases, we must then
		 *	explore all the backends (to find an object if there was match,
		 *	or to check that oid is not ambiguous if we have found 1 match in
		 *	the cache)
194 195 196
		 * - We never explore the cache, go right to exploring the backends
		 * We chose the latter : we explore directly the backends.
		 */
197
		error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len);
198 199
	}

200
	if (error < 0)
Russell Belfer committed
201
		return error;
202

203
	error = git_object__from_odb_object(object_out, repo, odb_obj, type);
204

205
	git_odb_object_free(odb_obj);
Vicent Marti committed
206

207
	return error;
208 209
}

210
int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {
Vicent Marti committed
211
	return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type);
212 213
}

Vicent Marti committed
214
void git_object__free(void *_obj)
215
{
Vicent Marti committed
216
	git_object *object = (git_object *)_obj;
217 218 219

	assert(object);

Vicent Marti committed
220
	switch (object->type) {
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
	case GIT_OBJ_COMMIT:
		git_commit__free((git_commit *)object);
		break;

	case GIT_OBJ_TREE:
		git_tree__free((git_tree *)object);
		break;

	case GIT_OBJ_TAG:
		git_tag__free((git_tag *)object);
		break;

	case GIT_OBJ_BLOB:
		git_blob__free((git_blob *)object);
		break;

	default:
238
		git__free(object);
239 240 241 242
		break;
	}
}

243
void git_object_free(git_object *object)
244 245 246 247
{
	if (object == NULL)
		return;

Vicent Marti committed
248
	git_cached_obj_decref((git_cached_obj *)object, git_object__free);
249 250
}

251
const git_oid *git_object_id(const git_object *obj)
252 253
{
	assert(obj);
Vicent Marti committed
254
	return &obj->cached.oid;
255 256
}

257
git_otype git_object_type(const git_object *obj)
258 259
{
	assert(obj);
Vicent Marti committed
260
	return obj->type;
261 262
}

263
git_repository *git_object_owner(const git_object *obj)
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
{
	assert(obj);
	return obj->repo;
}

const char *git_object_type2string(git_otype type)
{
	if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
		return "";

	return git_objects_table[type].str;
}

git_otype git_object_string2type(const char *str)
{
	size_t i;

	if (!str || !*str)
		return GIT_OBJ_BAD;

	for (i = 0; i < ARRAY_SIZE(git_objects_table); i++)
		if (!strcmp(str, git_objects_table[i].str))
			return (git_otype)i;

	return GIT_OBJ_BAD;
}

int git_object_typeisloose(git_otype type)
{
	if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
		return 0;

	return git_objects_table[type].loose;
}

size_t git_object__size(git_otype type)
{
	if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
		return 0;

	return git_objects_table[type].size;
}

307 308 309 310 311 312 313 314 315 316
static int dereference_object(git_object **dereferenced, git_object *obj)
{
	git_otype type = git_object_type(obj);

	switch (type) {
	case GIT_OBJ_COMMIT:
		return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj);

	case GIT_OBJ_TAG:
		return git_tag_target(dereferenced, (git_tag*)obj);
317 318

	case GIT_OBJ_BLOB:
319
		return GIT_ENOTFOUND;
320 321

	case GIT_OBJ_TREE:
322
		return GIT_EAMBIGUOUS;
323 324

	default:
325
		return GIT_EINVALIDSPEC;
326 327 328
	}
}

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
static int peel_error(int error, const git_oid *oid, git_otype type)
{
	const char *type_name;
	char hex_oid[GIT_OID_HEXSZ + 1];

	type_name = git_object_type2string(type);

	git_oid_fmt(hex_oid, oid);
	hex_oid[GIT_OID_HEXSZ] = '\0';

	giterr_set(GITERR_OBJECT, "The git_object of id '%s' can not be "
		"successfully peeled into a %s (git_otype=%i).", hex_oid, type_name, type);

	return error;
}

345
int git_object_peel(
346
	git_object **peeled,
Vicent Marti committed
347
	const git_object *object,
348
	git_otype target_type)
349 350
{
	git_object *source, *deref = NULL;
351 352 353 354 355 356 357 358
	int error;

	if (target_type != GIT_OBJ_TAG && 
		target_type != GIT_OBJ_COMMIT && 
		target_type != GIT_OBJ_TREE && 
		target_type != GIT_OBJ_BLOB && 
		target_type != GIT_OBJ_ANY)
			return GIT_EINVALIDSPEC;
359

360
	assert(object && peeled);
361 362

	if (git_object_type(object) == target_type)
Vicent Marti committed
363
		return git_object__dup(peeled, (git_object *)object);
364

Vicent Marti committed
365
	source = (git_object *)object;
366

367
	while (!(error = dereference_object(&deref, source))) {
368 369 370 371 372 373 374 375 376

		if (source != object)
			git_object_free(source);

		if (git_object_type(deref) == target_type) {
			*peeled = deref;
			return 0;
		}

377 378 379 380 381 382 383
		if (target_type == GIT_OBJ_ANY &&
			git_object_type(deref) != git_object_type(object))
		{
			*peeled = deref;
			return 0;
		}

384 385 386 387 388 389
		source = deref;
		deref = NULL;
	}

	if (source != object)
		git_object_free(source);
390

391
	git_object_free(deref);
392 393 394 395 396

	if (error)
		error = peel_error(error, git_object_id(object), target_type);

	return error;
397
}
398