File Coverage

deps/libgit2/src/libgit2/transaction.c
Criterion Covered Total %
statement 108 167 64.6
branch 55 132 41.6
condition n/a
subroutine n/a
pod n/a
total 163 299 54.5


line stmt bran cond sub pod time code
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              
57 0           int git_transaction_config_new(git_transaction **out, git_config *cfg)
58             {
59             git_transaction *tx;
60              
61 0 0         GIT_ASSERT_ARG(out);
62 0 0         GIT_ASSERT_ARG(cfg);
63              
64 0           tx = git__calloc(1, sizeof(git_transaction));
65 0 0         GIT_ERROR_CHECK_ALLOC(tx);
66              
67 0           tx->type = TRANSACTION_CONFIG;
68 0           tx->cfg = cfg;
69 0           *out = tx;
70 0           return 0;
71             }
72              
73 3           int git_transaction_new(git_transaction **out, git_repository *repo)
74             {
75             int error;
76             git_pool pool;
77 3           git_transaction *tx = NULL;
78              
79 3 50         GIT_ASSERT_ARG(out);
80 3 50         GIT_ASSERT_ARG(repo);
81              
82 3 50         if ((error = git_pool_init(&pool, 1)) < 0)
83 0           goto on_error;
84              
85 3           tx = git_pool_mallocz(&pool, sizeof(git_transaction));
86 3 50         if (!tx) {
87 0           error = -1;
88 0           goto on_error;
89             }
90              
91 3 50         if ((error = git_strmap_new(&tx->locks)) < 0) {
92 0           error = -1;
93 0           goto on_error;
94             }
95              
96 3 50         if ((error = git_repository_refdb(&tx->db, repo)) < 0)
97 0           goto on_error;
98              
99 3           tx->type = TRANSACTION_REFS;
100 3           memcpy(&tx->pool, &pool, sizeof(git_pool));
101 3           tx->repo = repo;
102 3           *out = tx;
103 3           return 0;
104              
105             on_error:
106 0           git_pool_clear(&pool);
107 3           return error;
108             }
109              
110 3           int git_transaction_lock_ref(git_transaction *tx, const char *refname)
111             {
112             int error;
113             transaction_node *node;
114              
115 3 50         GIT_ASSERT_ARG(tx);
116 3 50         GIT_ASSERT_ARG(refname);
117              
118 3           node = git_pool_mallocz(&tx->pool, sizeof(transaction_node));
119 3 50         GIT_ERROR_CHECK_ALLOC(node);
120              
121 3           node->name = git_pool_strdup(&tx->pool, refname);
122 3 50         GIT_ERROR_CHECK_ALLOC(node->name);
123              
124 3 50         if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0)
125 0           return error;
126              
127 3 50         if ((error = git_strmap_set(tx->locks, node->name, node)) < 0)
128 0           goto cleanup;
129              
130 3           return 0;
131              
132             cleanup:
133 0           git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
134              
135 0           return error;
136             }
137              
138 6           static int find_locked(transaction_node **out, git_transaction *tx, const char *refname)
139             {
140             transaction_node *node;
141              
142 6 50         if ((node = git_strmap_get(tx->locks, refname)) == NULL) {
143 0           git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked");
144 0           return GIT_ENOTFOUND;
145             }
146              
147 6           *out = node;
148 6           return 0;
149             }
150              
151 2           static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg)
152             {
153 2 50         if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0)
    0          
154 0           return -1;
155              
156 2 50         if (!node->sig) {
157             git_signature *tmp;
158             int error;
159              
160 2 50         if (git_reference__log_signature(&tmp, tx->repo) < 0)
161 0           return -1;
162              
163             /* make sure the sig we use is in our pool */
164 2           error = git_signature__pdup(&node->sig, tmp, &tx->pool);
165 2           git_signature_free(tmp);
166 2 50         if (error < 0)
167 2           return error;
168             }
169              
170 2 50         if (msg) {
171 0           node->message = git_pool_strdup(&tx->pool, msg);
172 0 0         GIT_ERROR_CHECK_ALLOC(node->message);
173             }
174              
175 2           return 0;
176             }
177              
178 2           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 2 50         GIT_ASSERT_ARG(tx);
184 2 50         GIT_ASSERT_ARG(refname);
185 2 50         GIT_ASSERT_ARG(target);
186              
187 2 50         if ((error = find_locked(&node, tx, refname)) < 0)
188 0           return error;
189              
190 2 50         if ((error = copy_common(node, tx, sig, msg)) < 0)
191 0           return error;
192              
193 2           git_oid_cpy(&node->target.id, target);
194 2           node->ref_type = GIT_REFERENCE_DIRECT;
195              
196 2           return 0;
197             }
198              
199 0           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 0 0         GIT_ASSERT_ARG(tx);
205 0 0         GIT_ASSERT_ARG(refname);
206 0 0         GIT_ASSERT_ARG(target);
207              
208 0 0         if ((error = find_locked(&node, tx, refname)) < 0)
209 0           return error;
210              
211 0 0         if ((error = copy_common(node, tx, sig, msg)) < 0)
212 0           return error;
213              
214 0           node->target.symbolic = git_pool_strdup(&tx->pool, target);
215 0 0         GIT_ERROR_CHECK_ALLOC(node->target.symbolic);
216 0           node->ref_type = GIT_REFERENCE_SYMBOLIC;
217              
218 0           return 0;
219             }
220              
221 1           int git_transaction_remove(git_transaction *tx, const char *refname)
222             {
223             int error;
224             transaction_node *node;
225              
226 1 50         if ((error = find_locked(&node, tx, refname)) < 0)
227 0           return error;
228              
229 1           node->remove = true;
230 1           node->ref_type = GIT_REFERENCE_DIRECT; /* the id will be ignored */
231              
232 1           return 0;
233             }
234              
235 3           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 3           reflog = git_pool_mallocz(pool, sizeof(git_reflog));
242 3 50         GIT_ERROR_CHECK_ALLOC(reflog);
243              
244 3           reflog->ref_name = git_pool_strdup(pool, in->ref_name);
245 3 50         GIT_ERROR_CHECK_ALLOC(reflog->ref_name);
246              
247 3           len = in->entries.length;
248 3           reflog->entries.length = len;
249 3           reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *));
250 3 50         GIT_ERROR_CHECK_ALLOC(reflog->entries.contents);
251              
252 3           entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry));
253 3 50         GIT_ERROR_CHECK_ALLOC(entries);
254              
255 5 100         for (i = 0; i < len; i++) {
256             const git_reflog_entry *src;
257             git_reflog_entry *tgt;
258              
259 2           tgt = &entries[i];
260 2           reflog->entries.contents[i] = tgt;
261              
262 2           src = git_vector_get(&in->entries, i);
263 2           git_oid_cpy(&tgt->oid_old, &src->oid_old);
264 2           git_oid_cpy(&tgt->oid_cur, &src->oid_cur);
265              
266 2           tgt->msg = git_pool_strdup(pool, src->msg);
267 2 50         GIT_ERROR_CHECK_ALLOC(tgt->msg);
268              
269 2 50         if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0)
270 0           return -1;
271             }
272              
273              
274 3           *out = reflog;
275 3           return 0;
276             }
277              
278 3           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 3 50         GIT_ASSERT_ARG(tx);
284 3 50         GIT_ASSERT_ARG(refname);
285 3 50         GIT_ASSERT_ARG(reflog);
286              
287 3 50         if ((error = find_locked(&node, tx, refname)) < 0)
288 0           return error;
289              
290 3 50         if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0)
291 0           return error;
292              
293 3           return 0;
294             }
295              
296 3           static int update_target(git_refdb *db, transaction_node *node)
297             {
298             git_reference *ref;
299             int error, update_reflog;
300              
301 3 50         if (node->ref_type == GIT_REFERENCE_DIRECT) {
302 3           ref = git_reference__alloc(node->name, &node->target.id, NULL);
303 0 0         } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) {
304 0           ref = git_reference__alloc_symbolic(node->name, node->target.symbolic);
305             } else {
306 0           abort();
307             }
308              
309 3 50         GIT_ERROR_CHECK_ALLOC(ref);
310 3           update_reflog = node->reflog == NULL;
311              
312 3 100         if (node->remove) {
313 1           error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL);
314 2 50         } else if (node->ref_type == GIT_REFERENCE_DIRECT) {
315 2           error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
316 0 0         } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) {
317 0           error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
318             } else {
319 0           abort();
320             }
321              
322 3           git_reference_free(ref);
323 3           node->committed = true;
324              
325 3           return error;
326             }
327              
328 3           int git_transaction_commit(git_transaction *tx)
329             {
330             transaction_node *node;
331 3           int error = 0;
332              
333 3 50         GIT_ASSERT_ARG(tx);
334              
335 3 50         if (tx->type == TRANSACTION_CONFIG) {
336 0           error = git_config_unlock(tx->cfg, true);
337 0           tx->cfg = NULL;
338              
339 0           return error;
340             }
341              
342 9 50         git_strmap_foreach_value(tx->locks, node, {
    50          
    50          
    0          
    50          
    100          
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 3           return 0;
361             }
362              
363 3           void git_transaction_free(git_transaction *tx)
364             {
365             transaction_node *node;
366             git_pool pool;
367              
368 3 50         if (!tx)
369 0           return;
370              
371 3 50         if (tx->type == TRANSACTION_CONFIG) {
372 0 0         if (tx->cfg) {
373 0           git_config_unlock(tx->cfg, false);
374 0           git_config_free(tx->cfg);
375             }
376              
377 0           git__free(tx);
378 0           return;
379             }
380              
381             /* start by unlocking the ones we've left hanging, if any */
382 6 50         git_strmap_foreach_value(tx->locks, node, {
    100          
383             if (node->committed)
384             continue;
385              
386             git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
387             });
388              
389 3           git_refdb_free(tx->db);
390 3           git_strmap_free(tx->locks);
391              
392             /* tx is inside the pool, so we need to extract the data */
393 3           memcpy(&pool, &tx->pool, sizeof(git_pool));
394 3           git_pool_clear(&pool);
395             }