object.c 13.2 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
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

#include "object.h"

10 11 12 13 14
#include "git2/object.h"

#include "repository.h"

#include "commit.h"
15
#include "hash.h"
16 17
#include "tree.h"
#include "blob.h"
18
#include "oid.h"
19 20
#include "tag.h"

21
bool git_object__strict_input_validation = true;
22

23
extern int git_odb_hash(git_oid *out, const void *data, size_t len, git_object_t type);
24
size_t git_object__size(git_object_t type);
25

26
typedef struct {
Vicent Marti committed
27
	const char	*str;	/* type name string */
28
	size_t		size;	/* size in bytes of the object structure */
29

30
	int  (*parse)(void *self, git_odb_object *obj);
31
	int  (*parse_raw)(void *self, const char *data, size_t size);
32 33 34 35
	void (*free)(void *self);
} git_object_def;

static git_object_def git_objects_table[] = {
36
	/* 0 = GIT_OBJECT__EXT1 */
37
	{ "", 0, NULL, NULL, NULL },
38

39
	/* 1 = GIT_OBJECT_COMMIT */
40
	{ "commit", sizeof(git_commit), git_commit__parse, git_commit__parse_raw, git_commit__free },
41

42
	/* 2 = GIT_OBJECT_TREE */
43
	{ "tree", sizeof(git_tree), git_tree__parse, git_tree__parse_raw, git_tree__free },
44

45
	/* 3 = GIT_OBJECT_BLOB */
46
	{ "blob", sizeof(git_blob), git_blob__parse, git_blob__parse_raw, git_blob__free },
47

48
	/* 4 = GIT_OBJECT_TAG */
49
	{ "tag", sizeof(git_tag), git_tag__parse, git_tag__parse_raw, git_tag__free },
50

51
	/* 5 = GIT_OBJECT__EXT2 */
52
	{ "", 0, NULL, NULL, NULL },
53
	/* 6 = GIT_OBJECT_OFS_DELTA */
54
	{ "OFS_DELTA", 0, NULL, NULL, NULL },
55
	/* 7 = GIT_OBJECT_REF_DELTA */
56
	{ "REF_DELTA", 0, NULL, NULL, NULL },
57 58
};

59 60 61 62
int git_object__from_raw(
	git_object **object_out,
	const char *data,
	size_t size,
63
	git_object_t type)
64 65 66 67 68 69
{
	git_object_def *def;
	git_object *object;
	size_t object_size;
	int error;

70
	GIT_ASSERT_ARG(object_out);
71 72 73
	*object_out = NULL;

	/* Validate type match */
74
	if (type != GIT_OBJECT_BLOB && type != GIT_OBJECT_TREE && type != GIT_OBJECT_COMMIT && type != GIT_OBJECT_TAG) {
75
		git_error_set(GIT_ERROR_INVALID, "the requested type is invalid");
76 77 78
		return GIT_ENOTFOUND;
	}

79
	if ((object_size = git_object__size(type)) == 0) {
80
		git_error_set(GIT_ERROR_INVALID, "the requested type is invalid");
81 82 83 84 85
		return GIT_ENOTFOUND;
	}

	/* Allocate and initialize base object */
	object = git__calloc(1, object_size);
86
	GIT_ERROR_CHECK_ALLOC(object);
87 88
	object->cached.flags = GIT_CACHE_STORE_PARSED;
	object->cached.type = type;
89 90
	if ((error = git_odb_hash(&object->cached.oid, data, size, type)) < 0)
		return error;
91 92 93

	/* Parse raw object data */
	def = &git_objects_table[type];
94
	GIT_ASSERT(def->free && def->parse_raw);
95

96
	if ((error = def->parse_raw(object, data, size)) < 0) {
97
		def->free(object);
98 99
		return error;
	}
100 101 102 103 104 105 106

	git_cached_obj_incref(object);
	*object_out = object;

	return 0;
}

107 108 109 110
int git_object__from_odb_object(
	git_object **object_out,
	git_repository *repo,
	git_odb_object *odb_obj,
111
	git_object_t type)
112 113
{
	int error;
114 115
	size_t object_size;
	git_object_def *def;
116 117
	git_object *object = NULL;

118
	GIT_ASSERT_ARG(object_out);
119 120 121
	*object_out = NULL;

	/* Validate type match */
122
	if (type != GIT_OBJECT_ANY && type != odb_obj->cached.type) {
123
		git_error_set(GIT_ERROR_INVALID,
124
			"the requested type does not match the type in the ODB");
125 126 127
		return GIT_ENOTFOUND;
	}

128
	if ((object_size = git_object__size(odb_obj->cached.type)) == 0) {
129
		git_error_set(GIT_ERROR_INVALID, "the requested type is invalid");
130 131 132 133 134
		return GIT_ENOTFOUND;
	}

	/* Allocate and initialize base object */
	object = git__calloc(1, object_size);
135
	GIT_ERROR_CHECK_ALLOC(object);
136 137

	git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
Vicent Marti committed
138
	object->cached.type = odb_obj->cached.type;
139
	object->cached.size = odb_obj->cached.size;
140 141
	object->repo = repo;

142 143
	/* Parse raw object data */
	def = &git_objects_table[odb_obj->cached.type];
144
	GIT_ASSERT(def->free && def->parse);
145

146
	if ((error = def->parse(object, odb_obj)) < 0)
147
		def->free(object);
148 149
	else
		*object_out = git_cache_store_parsed(&repo->objects, object);
150

151
	return error;
152 153
}

154 155
void git_object__free(void *obj)
{
156
	git_object_t type = ((git_object *)obj)->cached.type;
157 158 159 160 161 162 163 164

	if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) ||
		!git_objects_table[type].free)
		git__free(obj);
	else
		git_objects_table[type].free(obj);
}

165 166 167 168
int git_object_lookup_prefix(
	git_object **object_out,
	git_repository *repo,
	const git_oid *id,
169
	size_t len,
170
	git_object_t type)
171 172
{
	git_object *object = NULL;
173
	git_odb *odb = NULL;
174
	git_odb_object *odb_obj = NULL;
175
	int error = 0;
176

177 178 179
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(object_out);
	GIT_ASSERT_ARG(id);
180

181
	if (len < GIT_OID_MINPREFIXLEN) {
182
		git_error_set(GIT_ERROR_OBJECT, "ambiguous lookup - OID prefix is too short");
183
		return GIT_EAMBIGUOUS;
184
	}
Vicent Marti committed
185

186
	error = git_repository_odb__weakptr(&odb, repo);
187
	if (error < 0)
188 189
		return error;

190 191
	if (len > GIT_OID_HEXSZ)
		len = GIT_OID_HEXSZ;
192

193
	if (len == GIT_OID_HEXSZ) {
194 195
		git_cached_obj *cached = NULL;

196 197 198
		/* 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
		 */
199 200 201 202 203
		cached = git_cache_get_any(&repo->objects, id);
		if (cached != NULL) {
			if (cached->flags == GIT_CACHE_STORE_PARSED) {
				object = (git_object *)cached;

204
				if (type != GIT_OBJECT_ANY && type != object->cached.type) {
205
					git_object_free(object);
206
					git_error_set(GIT_ERROR_INVALID,
207
						"the requested type does not match the type in the ODB");
208 209 210 211 212 213 214 215
					return GIT_ENOTFOUND;
				}

				*object_out = object;
				return 0;
			} else if (cached->flags == GIT_CACHE_STORE_RAW) {
				odb_obj = (git_odb_object *)cached;
			} else {
216
				GIT_ASSERT(!"Wrong caching type in the global object cache");
217
			}
218 219 220 221 222 223 224
		} else {
			/* 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.
			 */
			error = git_odb_read(&odb_obj, odb, id);
225 226
		}
	} else {
227
		git_oid short_oid = {{ 0 }};
228

229
		git_oid__cpy_prefix(&short_oid, id, len);
230

231
		/* If len < GIT_OID_HEXSZ (a strict short oid was given), we have
232 233
		 * 2 options :
		 * - We always search in the cache first. If we find that short oid is
Vicent Marti committed
234 235 236 237
		 *	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)
238 239 240
		 * - We never explore the cache, go right to exploring the backends
		 * We chose the latter : we explore directly the backends.
		 */
241
		error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len);
242 243
	}

244
	if (error < 0)
Russell Belfer committed
245
		return error;
246

247
	error = git_object__from_odb_object(object_out, repo, odb_obj, type);
248

249
	git_odb_object_free(odb_obj);
Vicent Marti committed
250

251
	return error;
252 253
}

254
int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_object_t type) {
Vicent Marti committed
255
	return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type);
256 257
}

258
void git_object_free(git_object *object)
259 260 261 262
{
	if (object == NULL)
		return;

263
	git_cached_obj_decref(object);
264 265
}

266
const git_oid *git_object_id(const git_object *obj)
267
{
268
	GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL);
Vicent Marti committed
269
	return &obj->cached.oid;
270 271
}

272
git_object_t git_object_type(const git_object *obj)
273
{
274
	GIT_ASSERT_ARG_WITH_RETVAL(obj, GIT_OBJECT_INVALID);
Vicent Marti committed
275
	return obj->cached.type;
276 277
}

278
git_repository *git_object_owner(const git_object *obj)
279
{
280
	GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL);
281 282 283
	return obj->repo;
}

284
const char *git_object_type2string(git_object_t type)
285 286 287 288 289 290 291
{
	if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
		return "";

	return git_objects_table[type].str;
}

292
git_object_t git_object_string2type(const char *str)
293
{
294
	if (!str)
295
		return GIT_OBJECT_INVALID;
296 297 298 299

	return git_object_stringn2type(str, strlen(str));
}

300
git_object_t git_object_stringn2type(const char *str, size_t len)
301
{
302 303
	size_t i;

304
	if (!str || !len || !*str)
305
		return GIT_OBJECT_INVALID;
306 307

	for (i = 0; i < ARRAY_SIZE(git_objects_table); i++)
308 309
		if (*git_objects_table[i].str &&
			!git__prefixncmp(str, len, git_objects_table[i].str))
310
			return (git_object_t)i;
311

312
	return GIT_OBJECT_INVALID;
313 314
}

315
int git_object_typeisloose(git_object_t type)
316 317 318 319
{
	if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
		return 0;

320
	return (git_objects_table[type].size > 0) ? 1 : 0;
321 322
}

323
size_t git_object__size(git_object_t type)
324 325 326 327 328 329 330
{
	if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
		return 0;

	return git_objects_table[type].size;
}

331 332
static int dereference_object(git_object **dereferenced, git_object *obj)
{
333
	git_object_t type = git_object_type(obj);
334 335

	switch (type) {
336
	case GIT_OBJECT_COMMIT:
337 338
		return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj);

339
	case GIT_OBJECT_TAG:
340
		return git_tag_target(dereferenced, (git_tag*)obj);
341

342 343
	case GIT_OBJECT_BLOB:
	case GIT_OBJECT_TREE:
344
		return GIT_EPEEL;
345 346

	default:
347
		return GIT_EINVALIDSPEC;
348 349 350
	}
}

351
static int peel_error(int error, const git_oid *oid, git_object_t type)
352 353 354 355 356 357 358 359 360
{
	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';

361
	git_error_set(GIT_ERROR_OBJECT, "the git_object of id '%s' can not be "
362
		"successfully peeled into a %s (git_object_t=%i).", hex_oid, type_name, type);
363 364 365 366

	return error;
}

367
static int check_type_combination(git_object_t type, git_object_t target)
368 369 370 371 372
{
	if (type == target)
		return 0;

	switch (type) {
373 374
	case GIT_OBJECT_BLOB:
	case GIT_OBJECT_TREE:
375 376 377
		/* a blob or tree can never be peeled to anything but themselves */
		return GIT_EINVALIDSPEC;
		break;
378
	case GIT_OBJECT_COMMIT:
379
		/* a commit can only be peeled to a tree */
380
		if (target != GIT_OBJECT_TREE && target != GIT_OBJECT_ANY)
381 382
			return GIT_EINVALIDSPEC;
		break;
383
	case GIT_OBJECT_TAG:
384 385 386 387 388 389 390 391 392
		/* a tag may point to anything, so we let anything through */
		break;
	default:
		return GIT_EINVALIDSPEC;
	}

	return 0;
}

393
int git_object_peel(
394
	git_object **peeled,
Vicent Marti committed
395
	const git_object *object,
396
	git_object_t target_type)
397 398
{
	git_object *source, *deref = NULL;
399 400
	int error;

401 402
	GIT_ASSERT_ARG(object);
	GIT_ASSERT_ARG(peeled);
403

404
	GIT_ASSERT_ARG(target_type == GIT_OBJECT_TAG ||
405 406 407 408
		target_type == GIT_OBJECT_COMMIT ||
		target_type == GIT_OBJECT_TREE ||
		target_type == GIT_OBJECT_BLOB ||
		target_type == GIT_OBJECT_ANY);
409

410 411 412 413 414 415
	if ((error = check_type_combination(git_object_type(object), target_type)) < 0)
		return peel_error(error, git_object_id(object), target_type);

	if (git_object_type(object) == target_type)
		return git_object_dup(peeled, (git_object *)object);

Vicent Marti committed
416
	source = (git_object *)object;
417

418
	while (!(error = dereference_object(&deref, source))) {
419 420 421 422 423 424 425 426 427

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

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

428
		if (target_type == GIT_OBJECT_ANY &&
429 430 431 432 433 434
			git_object_type(deref) != git_object_type(object))
		{
			*peeled = deref;
			return 0;
		}

435 436 437 438 439 440
		source = deref;
		deref = NULL;
	}

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

442
	git_object_free(deref);
443 444 445 446 447

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

	return error;
448
}
449

450 451 452 453 454 455
int git_object_dup(git_object **dest, git_object *source)
{
	git_cached_obj_incref(source);
	*dest = source;
	return 0;
}
Ben Straub committed
456 457 458 459 460

int git_object_lookup_bypath(
		git_object **out,
		const git_object *treeish,
		const char *path,
461
		git_object_t type)
Ben Straub committed
462 463 464 465 466
{
	int error = -1;
	git_tree *tree = NULL;
	git_tree_entry *entry = NULL;

467 468 469
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(treeish);
	GIT_ASSERT_ARG(path);
Ben Straub committed
470

471
	if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJECT_TREE)) < 0 ||
472
		 (error = git_tree_entry_bypath(&entry, tree, path)) < 0)
Ben Straub committed
473 474 475 476
	{
		goto cleanup;
	}

477
	if (type != GIT_OBJECT_ANY && git_tree_entry_type(entry) != type)
478
	{
479
		git_error_set(GIT_ERROR_OBJECT,
Ben Straub committed
480 481 482
				"object at path '%s' is not of the asked-for type %d",
				path, type);
		error = GIT_EINVALIDSPEC;
483
		goto cleanup;
Ben Straub committed
484 485
	}

486 487
	error = git_tree_entry_to_object(out, git_object_owner(treeish), entry);

Ben Straub committed
488 489 490 491 492
cleanup:
	git_tree_entry_free(entry);
	git_tree_free(tree);
	return error;
}
493 494 495 496 497 498 499 500

int git_object_short_id(git_buf *out, const git_object *obj)
{
	git_repository *repo;
	int len = GIT_ABBREV_DEFAULT, error;
	git_oid id = {{0}};
	git_odb *odb;

501 502
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(obj);
503

504 505 506
	if ((error = git_buf_sanitize(out)) < 0)
		return error;

507 508
	repo = git_object_owner(obj);

509
	if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0)
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
		return error;

	if ((error = git_repository_odb(&odb, repo)) < 0)
		return error;

	while (len < GIT_OID_HEXSZ) {
		/* set up short oid */
		memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2);
		if (len & 1)
			id.id[len / 2] &= 0xf0;

		error = git_odb_exists_prefix(NULL, odb, &id, len);
		if (error != GIT_EAMBIGUOUS)
			break;

525
		git_error_clear();
526 527 528 529 530 531 532 533 534 535 536 537 538
		len++;
	}

	if (!error && !(error = git_buf_grow(out, len + 1))) {
		git_oid_tostr(out->ptr, len + 1, &id);
		out->size = len;
	}

	git_odb_free(odb);

	return error;
}

539
bool git_object__is_valid(
540
	git_repository *repo, const git_oid *id, git_object_t expected_type)
541 542
{
	git_odb *odb;
543
	git_object_t actual_type;
544 545 546 547 548 549 550 551 552 553
	size_t len;
	int error;

	if (!git_object__strict_input_validation)
		return true;

	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
		(error = git_odb_read_header(&len, &actual_type, odb, id)) < 0)
		return false;

554
	if (expected_type != GIT_OBJECT_ANY && expected_type != actual_type) {
555
		git_error_set(GIT_ERROR_INVALID,
556 557 558 559 560 561
			"the requested type does not match the type in the ODB");
		return false;
	}

	return true;
}