object.c 10 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 10 11 12 13 14 15 16 17 18
 */
#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;

19
typedef struct {
Vicent Marti committed
20
	const char	*str;	/* type name string */
21
	size_t		size;	/* size in bytes of the object structure */
22

23
	int  (*parse)(void *self, git_odb_object *obj);
24 25 26 27
	void (*free)(void *self);
} git_object_def;

static git_object_def git_objects_table[] = {
28
	/* 0 = GIT_OBJ__EXT1 */
29
	{ "", 0, NULL, NULL },
30 31

	/* 1 = GIT_OBJ_COMMIT */
32
	{ "commit", sizeof(git_commit), git_commit__parse, git_commit__free },
33 34

	/* 2 = GIT_OBJ_TREE */
35
	{ "tree", sizeof(git_tree), git_tree__parse, git_tree__free },
36 37

	/* 3 = GIT_OBJ_BLOB */
38
	{ "blob", sizeof(git_blob), git_blob__parse, git_blob__free },
39 40

	/* 4 = GIT_OBJ_TAG */
41
	{ "tag", sizeof(git_tag), git_tag__parse, git_tag__free },
42 43

	/* 5 = GIT_OBJ__EXT2 */
44
	{ "", 0, NULL, NULL },
45
	/* 6 = GIT_OBJ_OFS_DELTA */
46
	{ "OFS_DELTA", 0, NULL, NULL },
47
	/* 7 = GIT_OBJ_REF_DELTA */
48
	{ "REF_DELTA", 0, NULL, NULL },
49 50
};

51 52 53 54 55 56 57
int git_object__from_odb_object(
	git_object **object_out,
	git_repository *repo,
	git_odb_object *odb_obj,
	git_otype type)
{
	int error;
58 59
	size_t object_size;
	git_object_def *def;
60 61
	git_object *object = NULL;

62 63 64 65
	assert(object_out);
	*object_out = NULL;

	/* Validate type match */
Vicent Marti committed
66 67 68
	if (type != GIT_OBJ_ANY && type != odb_obj->cached.type) {
		giterr_set(GITERR_INVALID,
			"The requested type does not match the type in the ODB");
69 70 71
		return GIT_ENOTFOUND;
	}

72 73 74 75 76 77 78 79
	if ((object_size = git_object__size(odb_obj->cached.type)) == 0) {
		giterr_set(GITERR_INVALID, "The requested type is invalid");
		return GIT_ENOTFOUND;
	}

	/* Allocate and initialize base object */
	object = git__calloc(1, object_size);
	GITERR_CHECK_ALLOC(object);
80 81

	git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
Vicent Marti committed
82
	object->cached.type = odb_obj->cached.type;
83
	object->cached.size = odb_obj->cached.size;
84 85
	object->repo = repo;

86 87
	/* Parse raw object data */
	def = &git_objects_table[odb_obj->cached.type];
88
	assert(def->free && def->parse);
89

90
	if ((error = def->parse(object, odb_obj)) < 0)
91
		def->free(object);
92 93
	else
		*object_out = git_cache_store_parsed(&repo->objects, object);
94

95
	return error;
96 97
}

98 99 100 101 102 103 104 105 106 107 108
void git_object__free(void *obj)
{
	git_otype type = ((git_object *)obj)->cached.type;

	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);
}

109 110 111 112
int git_object_lookup_prefix(
	git_object **object_out,
	git_repository *repo,
	const git_oid *id,
113
	size_t len,
114
	git_otype type)
115 116
{
	git_object *object = NULL;
117
	git_odb *odb = NULL;
118
	git_odb_object *odb_obj = NULL;
119
	int error = 0;
120 121 122

	assert(repo && object_out && id);

123 124
	if (len < GIT_OID_MINPREFIXLEN) {
		giterr_set(GITERR_OBJECT, "Ambiguous lookup - OID prefix is too short");
125
		return GIT_EAMBIGUOUS;
126
	}
Vicent Marti committed
127

128
	error = git_repository_odb__weakptr(&odb, repo);
129
	if (error < 0)
130 131
		return error;

Vicent Marti committed
132
	if (len > GIT_OID_HEXSZ)
133
		len = GIT_OID_HEXSZ;
134

Vicent Marti committed
135
	if (len == GIT_OID_HEXSZ) {
136 137
		git_cached_obj *cached = NULL;

138 139 140
		/* 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
		 */
141 142 143 144 145
		cached = git_cache_get_any(&repo->objects, id);
		if (cached != NULL) {
			if (cached->flags == GIT_CACHE_STORE_PARSED) {
				object = (git_object *)cached;

Vicent Marti committed
146
				if (type != GIT_OBJ_ANY && type != object->cached.type) {
147 148 149 150 151 152 153 154 155 156 157 158
					git_object_free(object);
					giterr_set(GITERR_INVALID,
						"The requested type does not match the type in ODB");
					return GIT_ENOTFOUND;
				}

				*object_out = object;
				return 0;
			} else if (cached->flags == GIT_CACHE_STORE_RAW) {
				odb_obj = (git_odb_object *)cached;
			} else {
				assert(!"Wrong caching type in the global object cache");
159
			}
160 161 162 163 164 165 166
		} 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);
167 168 169 170 171 172 173 174 175 176 177 178 179
		}
	} 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
180 181 182 183
		 *	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)
184 185 186
		 * - We never explore the cache, go right to exploring the backends
		 * We chose the latter : we explore directly the backends.
		 */
187
		error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len);
188 189
	}

190
	if (error < 0)
Russell Belfer committed
191
		return error;
192

193
	error = git_object__from_odb_object(object_out, repo, odb_obj, type);
194

195
	git_odb_object_free(odb_obj);
Vicent Marti committed
196

197
	return error;
198 199
}

200
int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {
Vicent Marti committed
201
	return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type);
202 203
}

204
void git_object_free(git_object *object)
205 206 207 208
{
	if (object == NULL)
		return;

209
	git_cached_obj_decref(object);
210 211
}

212
const git_oid *git_object_id(const git_object *obj)
213 214
{
	assert(obj);
Vicent Marti committed
215
	return &obj->cached.oid;
216 217
}

218
git_otype git_object_type(const git_object *obj)
219 220
{
	assert(obj);
Vicent Marti committed
221
	return obj->cached.type;
222 223
}

224
git_repository *git_object_owner(const git_object *obj)
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
{
	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;

257
	return (git_objects_table[type].size > 0) ? 1 : 0;
258 259 260 261 262 263 264 265 266 267
}

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;
}

268 269 270 271 272 273 274 275 276 277
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);
278 279

	case GIT_OBJ_BLOB:
280
		return GIT_ENOTFOUND;
281 282

	case GIT_OBJ_TREE:
283
		return GIT_EAMBIGUOUS;
284 285

	default:
286
		return GIT_EINVALIDSPEC;
287 288 289
	}
}

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
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;
}

306
int git_object_peel(
307
	git_object **peeled,
Vicent Marti committed
308
	const git_object *object,
309
	git_otype target_type)
310 311
{
	git_object *source, *deref = NULL;
312 313
	int error;

314
	assert(object && peeled);
315 316

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

319 320 321 322 323
	assert(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);
324

Vicent Marti committed
325
	source = (git_object *)object;
326

327
	while (!(error = dereference_object(&deref, source))) {
328 329 330 331 332 333 334 335 336

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

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

337 338 339 340 341 342 343
		if (target_type == GIT_OBJ_ANY &&
			git_object_type(deref) != git_object_type(object))
		{
			*peeled = deref;
			return 0;
		}

344 345 346 347 348 349
		source = deref;
		deref = NULL;
	}

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

351
	git_object_free(deref);
352 353 354 355 356

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

	return error;
357
}
358

359 360 361 362 363 364
int git_object_dup(git_object **dest, git_object *source)
{
	git_cached_obj_incref(source);
	*dest = source;
	return 0;
}
Ben Straub committed
365 366 367 368 369 370 371 372 373 374 375 376 377

int git_object_lookup_bypath(
		git_object **out,
		const git_object *treeish,
		const char *path,
		git_otype type)
{
	int error = -1;
	git_tree *tree = NULL;
	git_tree_entry *entry = NULL;

	assert(out && treeish && path);

378
	if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE)) < 0 ||
379
		 (error = git_tree_entry_bypath(&entry, tree, path)) < 0)
Ben Straub committed
380 381 382 383
	{
		goto cleanup;
	}

384 385
	if (type != GIT_OBJ_ANY && git_tree_entry_type(entry) != type)
	{
Ben Straub committed
386 387 388 389
		giterr_set(GITERR_OBJECT,
				"object at path '%s' is not of the asked-for type %d",
				path, type);
		error = GIT_EINVALIDSPEC;
390
		goto cleanup;
Ben Straub committed
391 392
	}

393 394
	error = git_tree_entry_to_object(out, git_object_owner(treeish), entry);

Ben Straub committed
395 396 397 398 399
cleanup:
	git_tree_entry_free(entry);
	git_tree_free(tree);
	return error;
}
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442

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;

	assert(out && obj);

	git_buf_sanitize(out);
	repo = git_object_owner(obj);

	if ((error = git_repository__cvar(&len, repo, GIT_CVAR_ABBREV)) < 0)
		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;

		giterr_clear();
		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;
}