File Coverage

deps/libgit2/src/transaction.c
Criterion Covered Total %
statement 102 157 64.9
branch 55 132 41.6
condition n/a
subroutine n/a
pod n/a
total 157 289 54.3


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