refdb.c 8.97 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8
#include "refdb.h"
9

10 11 12
#include "git2/object.h"
#include "git2/refs.h"
#include "git2/refdb.h"
13 14
#include "git2/sys/refdb_backend.h"

15 16
#include "hash.h"
#include "refs.h"
17
#include "reflog.h"
18
#include "posix.h"
19

20 21 22
#define DEFAULT_NESTING_LEVEL	5
#define MAX_NESTING_LEVEL		10

23 24 25 26
int git_refdb_new(git_refdb **out, git_repository *repo)
{
	git_refdb *db;

Edward Thomson committed
27 28
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(repo);
29 30

	db = git__calloc(1, sizeof(*db));
31
	GIT_ERROR_CHECK_ALLOC(db);
32 33 34 35 36 37 38 39 40 41 42 43 44

	db->repo = repo;

	*out = db;
	GIT_REFCOUNT_INC(db);
	return 0;
}

int git_refdb_open(git_refdb **out, git_repository *repo)
{
	git_refdb *db;
	git_refdb_backend *dir;

Edward Thomson committed
45 46
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(repo);
47 48 49 50 51 52 53

	*out = NULL;

	if (git_refdb_new(&db, repo) < 0)
		return -1;

	/* Add the default (filesystem) backend */
54
	if (git_refdb_backend_fs(&dir, repo) < 0) {
55 56 57 58 59 60 61 62 63 64 65
		git_refdb_free(db);
		return -1;
	}

	db->repo = repo;
	db->backend = dir;

	*out = db;
	return 0;
}

66
static void refdb_free_backend(git_refdb *db)
67
{
68 69
	if (db->backend)
		db->backend->free(db->backend);
70
}
71

72 73
int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
{
74 75
	GIT_ERROR_CHECK_VERSION(backend, GIT_REFDB_BACKEND_VERSION, "git_refdb_backend");

76 77 78 79 80 81 82 83 84 85
	if (!backend->exists || !backend->lookup || !backend->iterator ||
	    !backend->write || !backend->rename || !backend->del ||
	    !backend->has_log || !backend->ensure_log || !backend->free ||
	    !backend->reflog_read || !backend->reflog_write ||
	    !backend->reflog_rename || !backend->reflog_delete ||
	    (backend->lock && !backend->unlock)) {
		git_error_set(GIT_ERROR_REFERENCE, "incomplete refdb backend implementation");
		return GIT_EINVALID;
	}

86
	refdb_free_backend(db);
87 88 89 90 91 92 93
	db->backend = backend;

	return 0;
}

int git_refdb_compress(git_refdb *db)
{
Edward Thomson committed
94
	GIT_ASSERT_ARG(db);
95

96
	if (db->backend->compress)
97
		return db->backend->compress(db->backend);
98

99 100 101
	return 0;
}

Vicent Marti committed
102
void git_refdb__free(git_refdb *db)
103
{
104
	refdb_free_backend(db);
105
	git__memzero(db, sizeof(*db));
106 107 108
	git__free(db);
}

109 110 111 112 113
void git_refdb_free(git_refdb *db)
{
	if (db == NULL)
		return;

Vicent Marti committed
114
	GIT_REFCOUNT_DEC(db, git_refdb__free);
115 116
}

117 118
int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
{
Edward Thomson committed
119 120 121
	GIT_ASSERT_ARG(exists);
	GIT_ASSERT_ARG(refdb);
	GIT_ASSERT_ARG(refdb->backend);
122 123 124 125 126 127

	return refdb->backend->exists(exists, refdb->backend, ref_name);
}

int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
{
128 129 130
	git_reference *ref;
	int error;

Edward Thomson committed
131 132 133 134
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(db->backend);
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(ref_name);
135

136 137 138 139 140 141
	error = db->backend->lookup(&ref, db->backend, ref_name);
	if (error < 0)
		return error;

	GIT_REFCOUNT_INC(db);
	ref->db = db;
142

143 144
	*out = ref;
	return 0;
145 146
}

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
int git_refdb_resolve(
	git_reference **out,
	git_refdb *db,
	const char *ref_name,
	int max_nesting)
{
	git_reference *ref = NULL;
	int error = 0, nesting;

	*out = NULL;

	if (max_nesting > MAX_NESTING_LEVEL)
		max_nesting = MAX_NESTING_LEVEL;
	else if (max_nesting < 0)
		max_nesting = DEFAULT_NESTING_LEVEL;

	if ((error = git_refdb_lookup(&ref, db, ref_name)) < 0)
		goto out;

	for (nesting = 0; nesting < max_nesting; nesting++) {
		git_reference *resolved;

		if (ref->type == GIT_REFERENCE_DIRECT)
			break;
171 172 173 174 175 176 177 178

		if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0) {
			/* If we found a symbolic reference with a nonexistent target, return it. */
			if (error == GIT_ENOTFOUND) {
				error = 0;
				*out = ref;
				ref = NULL;
			}
179
			goto out;
180
		}
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

		git_reference_free(ref);
		ref = resolved;
	}

	if (ref->type != GIT_REFERENCE_DIRECT && max_nesting != 0) {
		git_error_set(GIT_ERROR_REFERENCE,
			"cannot resolve reference (>%u levels deep)", max_nesting);
		error = -1;
		goto out;
	}

	*out = ref;
	ref = NULL;
out:
	git_reference_free(ref);
	return error;
}

200
int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob)
201
{
202 203
	int error;

204
	if (!db->backend || !db->backend->iterator) {
205
		git_error_set(GIT_ERROR_REFERENCE, "this backend doesn't support iterators");
206 207 208
		return -1;
	}

209 210
	if ((error = db->backend->iterator(out, db->backend, glob)) < 0)
		return error;
211

212 213 214
	GIT_REFCOUNT_INC(db);
	(*out)->db = db;

215 216 217
	return 0;
}

218
int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter)
219
{
220
	int error;
221

222 223
	if ((error = iter->next(out, iter)) < 0)
		return error;
224

225 226
	GIT_REFCOUNT_INC(iter->db);
	(*out)->db = iter->db;
227 228 229 230

	return 0;
}

231
int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter)
232
{
233
	return iter->next_name(out, iter);
234 235 236 237
}

void git_refdb_iterator_free(git_reference_iterator *iter)
{
Vicent Marti committed
238
	GIT_REFCOUNT_DEC(iter->db, git_refdb__free);
239
	iter->free(iter);
240 241
}

242
int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target)
243
{
Edward Thomson committed
244 245
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(db->backend);
Vicent Marti committed
246 247 248 249

	GIT_REFCOUNT_INC(db);
	ref->db = db;

250
	return db->backend->write(db->backend, ref, force, who, message, old_id, old_target);
Vicent Marti committed
251 252 253 254 255 256 257
}

int git_refdb_rename(
	git_reference **out,
	git_refdb *db,
	const char *old_name,
	const char *new_name,
258
	int force,
259
	const git_signature *who,
260
	const char *message)
Vicent Marti committed
261 262 263
{
	int error;

Edward Thomson committed
264 265 266
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(db->backend);

267
	error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message);
Vicent Marti committed
268 269 270 271 272 273 274 275 276
	if (error < 0)
		return error;

	if (out) {
		GIT_REFCOUNT_INC(db);
		(*out)->db = db;
	}

	return 0;
277 278
}

279
int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target)
280
{
Edward Thomson committed
281 282 283
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(db->backend);

284
	return db->backend->del(db->backend, ref_name, old_id, old_target);
285
}
286 287 288 289 290

int git_refdb_reflog_read(git_reflog **out, git_refdb *db,  const char *name)
{
	int error;

Edward Thomson committed
291 292
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(db->backend);
293 294 295 296 297 298 299 300 301

	if ((error = db->backend->reflog_read(out, db->backend, name)) < 0)
		return error;

	GIT_REFCOUNT_INC(db);
	(*out)->db = db;

	return 0;
}
302

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref)
{
	int error, logall;

	error = git_repository__configmap_lookup(&logall, db->repo, GIT_CONFIGMAP_LOGALLREFUPDATES);
	if (error < 0)
		return error;

	/* Defaults to the opposite of the repo being bare */
	if (logall == GIT_LOGALLREFUPDATES_UNSET)
		logall = !git_repository_is_bare(db->repo);

	*out = 0;
	switch (logall) {
	case GIT_LOGALLREFUPDATES_FALSE:
		*out = 0;
		break;

	case GIT_LOGALLREFUPDATES_TRUE:
		/* Only write if it already has a log,
		 * or if it's under heads/, remotes/ or notes/
		 */
		*out = git_refdb_has_log(db, ref->name) ||
			!git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) ||
			!git__strcmp(ref->name, GIT_HEAD_FILE) ||
			!git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) ||
			!git__prefixcmp(ref->name, GIT_REFS_NOTES_DIR);
		break;

	case GIT_LOGALLREFUPDATES_ALWAYS:
		*out = 1;
		break;
	}

	return 0;
}

340 341
int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref)
{
342
	git_reference *head = NULL, *resolved = NULL;
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
	const char *name;
	int error;

	*out = 0;

	if (ref->type == GIT_REFERENCE_SYMBOLIC) {
		error = 0;
		goto out;
	}

	if ((error = git_refdb_lookup(&head, db, GIT_HEAD_FILE)) < 0)
		goto out;

	if (git_reference_type(head) == GIT_REFERENCE_DIRECT)
		goto out;

	/* Go down the symref chain until we find the branch */
360
	if ((error = git_refdb_resolve(&resolved, db, git_reference_symbolic_target(head), -1)) < 0) {
361 362 363 364
		if (error != GIT_ENOTFOUND)
			goto out;
		error = 0;
		name = git_reference_symbolic_target(head);
365 366
	} else if (git_reference_type(resolved) == GIT_REFERENCE_SYMBOLIC) {
		name = git_reference_symbolic_target(resolved);
367
	} else {
368
		name = git_reference_name(resolved);
369 370 371 372 373 374 375 376
	}

	if (strcmp(name, ref->name))
		goto out;

	*out = 1;

out:
377
	git_reference_free(resolved);
378 379 380 381
	git_reference_free(head);
	return error;
}

382 383
int git_refdb_has_log(git_refdb *db, const char *refname)
{
Edward Thomson committed
384 385
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(refname);
386 387 388 389

	return db->backend->has_log(db->backend, refname);
}

390 391
int git_refdb_ensure_log(git_refdb *db, const char *refname)
{
Edward Thomson committed
392 393
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(refname);
394 395 396

	return db->backend->ensure_log(db->backend, refname);
}
397

398
int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version)
399
{
400 401 402
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT);
	return 0;
403
}
404 405 406

int git_refdb_lock(void **payload, git_refdb *db, const char *refname)
{
Edward Thomson committed
407 408 409
	GIT_ASSERT_ARG(payload);
	GIT_ASSERT_ARG(db);
	GIT_ASSERT_ARG(refname);
410 411

	if (!db->backend->lock) {
412
		git_error_set(GIT_ERROR_REFERENCE, "backend does not support locking");
413 414 415 416 417 418 419 420
		return -1;
	}

	return db->backend->lock(payload, db->backend, refname);
}

int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message)
{
Edward Thomson committed
421
	GIT_ASSERT_ARG(db);
422 423 424

	return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message);
}