File Coverage

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