]> git.proxmox.com Git - libgit2.git/blob - src/transaction.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / transaction.c
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 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
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
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
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
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
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
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
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
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
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
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
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
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 }