1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "refdb.h"
9 
10 #include "git2/object.h"
11 #include "git2/refs.h"
12 #include "git2/refdb.h"
13 #include "git2/sys/refdb_backend.h"
14 
15 #include "hash.h"
16 #include "refs.h"
17 #include "reflog.h"
18 #include "posix.h"
19 
20 #define DEFAULT_NESTING_LEVEL	5
21 #define MAX_NESTING_LEVEL		10
22 
git_refdb_new(git_refdb ** out,git_repository * repo)23 int git_refdb_new(git_refdb **out, git_repository *repo)
24 {
25 	git_refdb *db;
26 
27 	GIT_ASSERT_ARG(out);
28 	GIT_ASSERT_ARG(repo);
29 
30 	db = git__calloc(1, sizeof(*db));
31 	GIT_ERROR_CHECK_ALLOC(db);
32 
33 	db->repo = repo;
34 
35 	*out = db;
36 	GIT_REFCOUNT_INC(db);
37 	return 0;
38 }
39 
git_refdb_open(git_refdb ** out,git_repository * repo)40 int git_refdb_open(git_refdb **out, git_repository *repo)
41 {
42 	git_refdb *db;
43 	git_refdb_backend *dir;
44 
45 	GIT_ASSERT_ARG(out);
46 	GIT_ASSERT_ARG(repo);
47 
48 	*out = NULL;
49 
50 	if (git_refdb_new(&db, repo) < 0)
51 		return -1;
52 
53 	/* Add the default (filesystem) backend */
54 	if (git_refdb_backend_fs(&dir, repo) < 0) {
55 		git_refdb_free(db);
56 		return -1;
57 	}
58 
59 	db->repo = repo;
60 	db->backend = dir;
61 
62 	*out = db;
63 	return 0;
64 }
65 
refdb_free_backend(git_refdb * db)66 static void refdb_free_backend(git_refdb *db)
67 {
68 	if (db->backend)
69 		db->backend->free(db->backend);
70 }
71 
git_refdb_set_backend(git_refdb * db,git_refdb_backend * backend)72 int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
73 {
74 	GIT_ERROR_CHECK_VERSION(backend, GIT_REFDB_BACKEND_VERSION, "git_refdb_backend");
75 
76 	if (!backend->exists || !backend->lookup || !backend->iterator ||
77 	    !backend->write || !backend->rename || !backend->del ||
78 	    !backend->has_log || !backend->ensure_log || !backend->free ||
79 	    !backend->reflog_read || !backend->reflog_write ||
80 	    !backend->reflog_rename || !backend->reflog_delete ||
81 	    (backend->lock && !backend->unlock)) {
82 		git_error_set(GIT_ERROR_REFERENCE, "incomplete refdb backend implementation");
83 		return GIT_EINVALID;
84 	}
85 
86 	refdb_free_backend(db);
87 	db->backend = backend;
88 
89 	return 0;
90 }
91 
git_refdb_compress(git_refdb * db)92 int git_refdb_compress(git_refdb *db)
93 {
94 	GIT_ASSERT_ARG(db);
95 
96 	if (db->backend->compress)
97 		return db->backend->compress(db->backend);
98 
99 	return 0;
100 }
101 
git_refdb__free(git_refdb * db)102 void git_refdb__free(git_refdb *db)
103 {
104 	refdb_free_backend(db);
105 	git__memzero(db, sizeof(*db));
106 	git__free(db);
107 }
108 
git_refdb_free(git_refdb * db)109 void git_refdb_free(git_refdb *db)
110 {
111 	if (db == NULL)
112 		return;
113 
114 	GIT_REFCOUNT_DEC(db, git_refdb__free);
115 }
116 
git_refdb_exists(int * exists,git_refdb * refdb,const char * ref_name)117 int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
118 {
119 	GIT_ASSERT_ARG(exists);
120 	GIT_ASSERT_ARG(refdb);
121 	GIT_ASSERT_ARG(refdb->backend);
122 
123 	return refdb->backend->exists(exists, refdb->backend, ref_name);
124 }
125 
git_refdb_lookup(git_reference ** out,git_refdb * db,const char * ref_name)126 int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
127 {
128 	git_reference *ref;
129 	int error;
130 
131 	GIT_ASSERT_ARG(db);
132 	GIT_ASSERT_ARG(db->backend);
133 	GIT_ASSERT_ARG(out);
134 	GIT_ASSERT_ARG(ref_name);
135 
136 	error = db->backend->lookup(&ref, db->backend, ref_name);
137 	if (error < 0)
138 		return error;
139 
140 	GIT_REFCOUNT_INC(db);
141 	ref->db = db;
142 
143 	*out = ref;
144 	return 0;
145 }
146 
git_refdb_resolve(git_reference ** out,git_refdb * db,const char * ref_name,int max_nesting)147 int git_refdb_resolve(
148 	git_reference **out,
149 	git_refdb *db,
150 	const char *ref_name,
151 	int max_nesting)
152 {
153 	git_reference *ref = NULL;
154 	int error = 0, nesting;
155 
156 	*out = NULL;
157 
158 	if (max_nesting > MAX_NESTING_LEVEL)
159 		max_nesting = MAX_NESTING_LEVEL;
160 	else if (max_nesting < 0)
161 		max_nesting = DEFAULT_NESTING_LEVEL;
162 
163 	if ((error = git_refdb_lookup(&ref, db, ref_name)) < 0)
164 		goto out;
165 
166 	for (nesting = 0; nesting < max_nesting; nesting++) {
167 		git_reference *resolved;
168 
169 		if (ref->type == GIT_REFERENCE_DIRECT)
170 			break;
171 
172 		if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0) {
173 			/* If we found a symbolic reference with a nonexistent target, return it. */
174 			if (error == GIT_ENOTFOUND) {
175 				error = 0;
176 				*out = ref;
177 				ref = NULL;
178 			}
179 			goto out;
180 		}
181 
182 		git_reference_free(ref);
183 		ref = resolved;
184 	}
185 
186 	if (ref->type != GIT_REFERENCE_DIRECT && max_nesting != 0) {
187 		git_error_set(GIT_ERROR_REFERENCE,
188 			"cannot resolve reference (>%u levels deep)", max_nesting);
189 		error = -1;
190 		goto out;
191 	}
192 
193 	*out = ref;
194 	ref = NULL;
195 out:
196 	git_reference_free(ref);
197 	return error;
198 }
199 
git_refdb_iterator(git_reference_iterator ** out,git_refdb * db,const char * glob)200 int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob)
201 {
202 	int error;
203 
204 	if (!db->backend || !db->backend->iterator) {
205 		git_error_set(GIT_ERROR_REFERENCE, "this backend doesn't support iterators");
206 		return -1;
207 	}
208 
209 	if ((error = db->backend->iterator(out, db->backend, glob)) < 0)
210 		return error;
211 
212 	GIT_REFCOUNT_INC(db);
213 	(*out)->db = db;
214 
215 	return 0;
216 }
217 
git_refdb_iterator_next(git_reference ** out,git_reference_iterator * iter)218 int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter)
219 {
220 	int error;
221 
222 	if ((error = iter->next(out, iter)) < 0)
223 		return error;
224 
225 	GIT_REFCOUNT_INC(iter->db);
226 	(*out)->db = iter->db;
227 
228 	return 0;
229 }
230 
git_refdb_iterator_next_name(const char ** out,git_reference_iterator * iter)231 int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter)
232 {
233 	return iter->next_name(out, iter);
234 }
235 
git_refdb_iterator_free(git_reference_iterator * iter)236 void git_refdb_iterator_free(git_reference_iterator *iter)
237 {
238 	GIT_REFCOUNT_DEC(iter->db, git_refdb__free);
239 	iter->free(iter);
240 }
241 
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)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 {
244 	GIT_ASSERT_ARG(db);
245 	GIT_ASSERT_ARG(db->backend);
246 
247 	GIT_REFCOUNT_INC(db);
248 	ref->db = db;
249 
250 	return db->backend->write(db->backend, ref, force, who, message, old_id, old_target);
251 }
252 
git_refdb_rename(git_reference ** out,git_refdb * db,const char * old_name,const char * new_name,int force,const git_signature * who,const char * message)253 int git_refdb_rename(
254 	git_reference **out,
255 	git_refdb *db,
256 	const char *old_name,
257 	const char *new_name,
258 	int force,
259 	const git_signature *who,
260 	const char *message)
261 {
262 	int error;
263 
264 	GIT_ASSERT_ARG(db);
265 	GIT_ASSERT_ARG(db->backend);
266 
267 	error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message);
268 	if (error < 0)
269 		return error;
270 
271 	if (out) {
272 		GIT_REFCOUNT_INC(db);
273 		(*out)->db = db;
274 	}
275 
276 	return 0;
277 }
278 
git_refdb_delete(struct git_refdb * db,const char * ref_name,const git_oid * old_id,const char * old_target)279 int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target)
280 {
281 	GIT_ASSERT_ARG(db);
282 	GIT_ASSERT_ARG(db->backend);
283 
284 	return db->backend->del(db->backend, ref_name, old_id, old_target);
285 }
286 
git_refdb_reflog_read(git_reflog ** out,git_refdb * db,const char * name)287 int git_refdb_reflog_read(git_reflog **out, git_refdb *db,  const char *name)
288 {
289 	int error;
290 
291 	GIT_ASSERT_ARG(db);
292 	GIT_ASSERT_ARG(db->backend);
293 
294 	if ((error = db->backend->reflog_read(out, db->backend, name)) < 0)
295 		return error;
296 
297 	GIT_REFCOUNT_INC(db);
298 	(*out)->db = db;
299 
300 	return 0;
301 }
302 
git_refdb_should_write_reflog(int * out,git_refdb * db,const git_reference * ref)303 int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref)
304 {
305 	int error, logall;
306 
307 	error = git_repository__configmap_lookup(&logall, db->repo, GIT_CONFIGMAP_LOGALLREFUPDATES);
308 	if (error < 0)
309 		return error;
310 
311 	/* Defaults to the opposite of the repo being bare */
312 	if (logall == GIT_LOGALLREFUPDATES_UNSET)
313 		logall = !git_repository_is_bare(db->repo);
314 
315 	*out = 0;
316 	switch (logall) {
317 	case GIT_LOGALLREFUPDATES_FALSE:
318 		*out = 0;
319 		break;
320 
321 	case GIT_LOGALLREFUPDATES_TRUE:
322 		/* Only write if it already has a log,
323 		 * or if it's under heads/, remotes/ or notes/
324 		 */
325 		*out = git_refdb_has_log(db, ref->name) ||
326 			!git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) ||
327 			!git__strcmp(ref->name, GIT_HEAD_FILE) ||
328 			!git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) ||
329 			!git__prefixcmp(ref->name, GIT_REFS_NOTES_DIR);
330 		break;
331 
332 	case GIT_LOGALLREFUPDATES_ALWAYS:
333 		*out = 1;
334 		break;
335 	}
336 
337 	return 0;
338 }
339 
git_refdb_should_write_head_reflog(int * out,git_refdb * db,const git_reference * ref)340 int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref)
341 {
342 	git_reference *head = NULL, *resolved = NULL;
343 	const char *name;
344 	int error;
345 
346 	*out = 0;
347 
348 	if (ref->type == GIT_REFERENCE_SYMBOLIC) {
349 		error = 0;
350 		goto out;
351 	}
352 
353 	if ((error = git_refdb_lookup(&head, db, GIT_HEAD_FILE)) < 0)
354 		goto out;
355 
356 	if (git_reference_type(head) == GIT_REFERENCE_DIRECT)
357 		goto out;
358 
359 	/* 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 		if (error != GIT_ENOTFOUND)
362 			goto out;
363 		error = 0;
364 		name = git_reference_symbolic_target(head);
365 	} else if (git_reference_type(resolved) == GIT_REFERENCE_SYMBOLIC) {
366 		name = git_reference_symbolic_target(resolved);
367 	} else {
368 		name = git_reference_name(resolved);
369 	}
370 
371 	if (strcmp(name, ref->name))
372 		goto out;
373 
374 	*out = 1;
375 
376 out:
377 	git_reference_free(resolved);
378 	git_reference_free(head);
379 	return error;
380 }
381 
git_refdb_has_log(git_refdb * db,const char * refname)382 int git_refdb_has_log(git_refdb *db, const char *refname)
383 {
384 	GIT_ASSERT_ARG(db);
385 	GIT_ASSERT_ARG(refname);
386 
387 	return db->backend->has_log(db->backend, refname);
388 }
389 
git_refdb_ensure_log(git_refdb * db,const char * refname)390 int git_refdb_ensure_log(git_refdb *db, const char *refname)
391 {
392 	GIT_ASSERT_ARG(db);
393 	GIT_ASSERT_ARG(refname);
394 
395 	return db->backend->ensure_log(db->backend, refname);
396 }
397 
git_refdb_init_backend(git_refdb_backend * backend,unsigned int version)398 int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version)
399 {
400 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
401 		backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT);
402 	return 0;
403 }
404 
git_refdb_lock(void ** payload,git_refdb * db,const char * refname)405 int git_refdb_lock(void **payload, git_refdb *db, const char *refname)
406 {
407 	GIT_ASSERT_ARG(payload);
408 	GIT_ASSERT_ARG(db);
409 	GIT_ASSERT_ARG(refname);
410 
411 	if (!db->backend->lock) {
412 		git_error_set(GIT_ERROR_REFERENCE, "backend does not support locking");
413 		return -1;
414 	}
415 
416 	return db->backend->lock(payload, db->backend, refname);
417 }
418 
git_refdb_unlock(git_refdb * db,void * payload,int success,int update_reflog,const git_reference * ref,const git_signature * sig,const char * message)419 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)
420 {
421 	GIT_ASSERT_ARG(db);
422 
423 	return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message);
424 }
425