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 "transaction.h"
9 
10 #include "repository.h"
11 #include "strmap.h"
12 #include "refdb.h"
13 #include "pool.h"
14 #include "reflog.h"
15 #include "signature.h"
16 #include "config.h"
17 
18 #include "git2/transaction.h"
19 #include "git2/signature.h"
20 #include "git2/sys/refs.h"
21 #include "git2/sys/refdb_backend.h"
22 
23 typedef enum {
24 	TRANSACTION_NONE,
25 	TRANSACTION_REFS,
26 	TRANSACTION_CONFIG,
27 } transaction_t;
28 
29 typedef struct {
30 	const char *name;
31 	void *payload;
32 
33 	git_reference_t ref_type;
34 	union {
35 		git_oid id;
36 		char *symbolic;
37 	} target;
38 	git_reflog *reflog;
39 
40 	const char *message;
41 	git_signature *sig;
42 
43 	unsigned int committed :1,
44 		remove :1;
45 } transaction_node;
46 
47 struct git_transaction {
48 	transaction_t type;
49 	git_repository *repo;
50 	git_refdb *db;
51 	git_config *cfg;
52 
53 	git_strmap *locks;
54 	git_pool pool;
55 };
56 
git_transaction_config_new(git_transaction ** out,git_config * cfg)57 int git_transaction_config_new(git_transaction **out, git_config *cfg)
58 {
59 	git_transaction *tx;
60 
61 	GIT_ASSERT_ARG(out);
62 	GIT_ASSERT_ARG(cfg);
63 
64 	tx = git__calloc(1, sizeof(git_transaction));
65 	GIT_ERROR_CHECK_ALLOC(tx);
66 
67 	tx->type = TRANSACTION_CONFIG;
68 	tx->cfg = cfg;
69 	*out = tx;
70 	return 0;
71 }
72 
git_transaction_new(git_transaction ** out,git_repository * repo)73 int git_transaction_new(git_transaction **out, git_repository *repo)
74 {
75 	int error;
76 	git_pool pool;
77 	git_transaction *tx = NULL;
78 
79 	GIT_ASSERT_ARG(out);
80 	GIT_ASSERT_ARG(repo);
81 
82 	if ((error = git_pool_init(&pool, 1)) < 0)
83 		goto on_error;
84 
85 	tx = git_pool_mallocz(&pool, sizeof(git_transaction));
86 	if (!tx) {
87 		error = -1;
88 		goto on_error;
89 	}
90 
91 	if ((error = git_strmap_new(&tx->locks)) < 0) {
92 		error = -1;
93 		goto on_error;
94 	}
95 
96 	if ((error = git_repository_refdb(&tx->db, repo)) < 0)
97 		goto on_error;
98 
99 	tx->type = TRANSACTION_REFS;
100 	memcpy(&tx->pool, &pool, sizeof(git_pool));
101 	tx->repo = repo;
102 	*out = tx;
103 	return 0;
104 
105 on_error:
106 	git_pool_clear(&pool);
107 	return error;
108 }
109 
git_transaction_lock_ref(git_transaction * tx,const char * refname)110 int git_transaction_lock_ref(git_transaction *tx, const char *refname)
111 {
112 	int error;
113 	transaction_node *node;
114 
115 	GIT_ASSERT_ARG(tx);
116 	GIT_ASSERT_ARG(refname);
117 
118 	node = git_pool_mallocz(&tx->pool, sizeof(transaction_node));
119 	GIT_ERROR_CHECK_ALLOC(node);
120 
121 	node->name = git_pool_strdup(&tx->pool, refname);
122 	GIT_ERROR_CHECK_ALLOC(node->name);
123 
124 	if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0)
125 		return error;
126 
127 	if ((error = git_strmap_set(tx->locks, node->name, node)) < 0)
128 		goto cleanup;
129 
130 	return 0;
131 
132 cleanup:
133 	git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
134 
135 	return error;
136 }
137 
find_locked(transaction_node ** out,git_transaction * tx,const char * refname)138 static int find_locked(transaction_node **out, git_transaction *tx, const char *refname)
139 {
140 	transaction_node *node;
141 
142 	if ((node = git_strmap_get(tx->locks, refname)) == NULL) {
143 		git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked");
144 		return GIT_ENOTFOUND;
145 	}
146 
147 	*out = node;
148 	return 0;
149 }
150 
copy_common(transaction_node * node,git_transaction * tx,const git_signature * sig,const char * msg)151 static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg)
152 {
153 	if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0)
154 		return -1;
155 
156 	if (!node->sig) {
157 		git_signature *tmp;
158 		int error;
159 
160 		if (git_reference__log_signature(&tmp, tx->repo) < 0)
161 			return -1;
162 
163 		/* make sure the sig we use is in our pool */
164 		error = git_signature__pdup(&node->sig, tmp, &tx->pool);
165 		git_signature_free(tmp);
166 		if (error < 0)
167 			return error;
168 	}
169 
170 	if (msg) {
171 		node->message = git_pool_strdup(&tx->pool, msg);
172 		GIT_ERROR_CHECK_ALLOC(node->message);
173 	}
174 
175 	return 0;
176 }
177 
git_transaction_set_target(git_transaction * tx,const char * refname,const git_oid * target,const git_signature * sig,const char * msg)178 int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg)
179 {
180 	int error;
181 	transaction_node *node;
182 
183 	GIT_ASSERT_ARG(tx);
184 	GIT_ASSERT_ARG(refname);
185 	GIT_ASSERT_ARG(target);
186 
187 	if ((error = find_locked(&node, tx, refname)) < 0)
188 		return error;
189 
190 	if ((error = copy_common(node, tx, sig, msg)) < 0)
191 		return error;
192 
193 	git_oid_cpy(&node->target.id, target);
194 	node->ref_type = GIT_REFERENCE_DIRECT;
195 
196 	return 0;
197 }
198 
git_transaction_set_symbolic_target(git_transaction * tx,const char * refname,const char * target,const git_signature * sig,const char * msg)199 int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg)
200 {
201 	int error;
202 	transaction_node *node;
203 
204 	GIT_ASSERT_ARG(tx);
205 	GIT_ASSERT_ARG(refname);
206 	GIT_ASSERT_ARG(target);
207 
208 	if ((error = find_locked(&node, tx, refname)) < 0)
209 		return error;
210 
211 	if ((error = copy_common(node, tx, sig, msg)) < 0)
212 		return error;
213 
214 	node->target.symbolic = git_pool_strdup(&tx->pool, target);
215 	GIT_ERROR_CHECK_ALLOC(node->target.symbolic);
216 	node->ref_type = GIT_REFERENCE_SYMBOLIC;
217 
218 	return 0;
219 }
220 
git_transaction_remove(git_transaction * tx,const char * refname)221 int git_transaction_remove(git_transaction *tx, const char *refname)
222 {
223 	int error;
224 	transaction_node *node;
225 
226 	if ((error = find_locked(&node, tx, refname)) < 0)
227 		return error;
228 
229 	node->remove = true;
230 	node->ref_type = GIT_REFERENCE_DIRECT; /* the id will be ignored */
231 
232 	return 0;
233 }
234 
dup_reflog(git_reflog ** out,const git_reflog * in,git_pool * pool)235 static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool)
236 {
237 	git_reflog *reflog;
238 	git_reflog_entry *entries;
239 	size_t len, i;
240 
241 	reflog = git_pool_mallocz(pool, sizeof(git_reflog));
242 	GIT_ERROR_CHECK_ALLOC(reflog);
243 
244 	reflog->ref_name = git_pool_strdup(pool, in->ref_name);
245 	GIT_ERROR_CHECK_ALLOC(reflog->ref_name);
246 
247 	len = in->entries.length;
248 	reflog->entries.length = len;
249 	reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *));
250 	GIT_ERROR_CHECK_ALLOC(reflog->entries.contents);
251 
252 	entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry));
253 	GIT_ERROR_CHECK_ALLOC(entries);
254 
255 	for (i = 0; i < len; i++) {
256 		const git_reflog_entry *src;
257 		git_reflog_entry *tgt;
258 
259 		tgt = &entries[i];
260 		reflog->entries.contents[i] = tgt;
261 
262 		src = git_vector_get(&in->entries, i);
263 		git_oid_cpy(&tgt->oid_old, &src->oid_old);
264 		git_oid_cpy(&tgt->oid_cur, &src->oid_cur);
265 
266 		tgt->msg = git_pool_strdup(pool, src->msg);
267 		GIT_ERROR_CHECK_ALLOC(tgt->msg);
268 
269 		if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0)
270 			return -1;
271 	}
272 
273 
274 	*out = reflog;
275 	return 0;
276 }
277 
git_transaction_set_reflog(git_transaction * tx,const char * refname,const git_reflog * reflog)278 int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog)
279 {
280 	int error;
281 	transaction_node *node;
282 
283 	GIT_ASSERT_ARG(tx);
284 	GIT_ASSERT_ARG(refname);
285 	GIT_ASSERT_ARG(reflog);
286 
287 	if ((error = find_locked(&node, tx, refname)) < 0)
288 		return error;
289 
290 	if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0)
291 		return error;
292 
293 	return 0;
294 }
295 
update_target(git_refdb * db,transaction_node * node)296 static int update_target(git_refdb *db, transaction_node *node)
297 {
298 	git_reference *ref;
299 	int error, update_reflog;
300 
301 	if (node->ref_type == GIT_REFERENCE_DIRECT) {
302 		ref = git_reference__alloc(node->name, &node->target.id, NULL);
303 	} else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) {
304 		ref = git_reference__alloc_symbolic(node->name, node->target.symbolic);
305 	} else {
306 		abort();
307 	}
308 
309 	GIT_ERROR_CHECK_ALLOC(ref);
310 	update_reflog = node->reflog == NULL;
311 
312 	if (node->remove) {
313 		error =  git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL);
314 	} else if (node->ref_type == GIT_REFERENCE_DIRECT) {
315 		error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
316 	} else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) {
317 		error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
318 	} else {
319 		abort();
320 	}
321 
322 	git_reference_free(ref);
323 	node->committed = true;
324 
325 	return error;
326 }
327 
git_transaction_commit(git_transaction * tx)328 int git_transaction_commit(git_transaction *tx)
329 {
330 	transaction_node *node;
331 	int error = 0;
332 
333 	GIT_ASSERT_ARG(tx);
334 
335 	if (tx->type == TRANSACTION_CONFIG) {
336 		error = git_config_unlock(tx->cfg, true);
337 		tx->cfg = NULL;
338 
339 		return error;
340 	}
341 
342 	git_strmap_foreach_value(tx->locks, node, {
343 		if (node->reflog) {
344 			if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0)
345 				return error;
346 		}
347 
348 		if (node->ref_type == GIT_REFERENCE_INVALID) {
349 			/* ref was locked but not modified */
350 			if ((error = git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL)) < 0) {
351 				return error;
352 			}
353 			node->committed = true;
354 		} else {
355 			if ((error = update_target(tx->db, node)) < 0)
356 				return error;
357 		}
358 	});
359 
360 	return 0;
361 }
362 
git_transaction_free(git_transaction * tx)363 void git_transaction_free(git_transaction *tx)
364 {
365 	transaction_node *node;
366 	git_pool pool;
367 
368 	if (!tx)
369 		return;
370 
371 	if (tx->type == TRANSACTION_CONFIG) {
372 		if (tx->cfg) {
373 			git_config_unlock(tx->cfg, false);
374 			git_config_free(tx->cfg);
375 		}
376 
377 		git__free(tx);
378 		return;
379 	}
380 
381 	/* start by unlocking the ones we've left hanging, if any */
382 	git_strmap_foreach_value(tx->locks, node, {
383 		if (node->committed)
384 			continue;
385 
386 		git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
387 	});
388 
389 	git_refdb_free(tx->db);
390 	git_strmap_free(tx->locks);
391 
392 	/* tx is inside the pool, so we need to extract the data */
393 	memcpy(&pool, &tx->pool, sizeof(git_pool));
394 	git_pool_clear(&pool);
395 }
396