]>
Commit | Line | Data |
---|---|---|
ab8d9242 CMN |
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 | ||
eae0bfdc PP |
8 | #include "transaction.h" |
9 | ||
ab8d9242 CMN |
10 | #include "repository.h" |
11 | #include "strmap.h" | |
12 | #include "refdb.h" | |
13 | #include "pool.h" | |
14 | #include "reflog.h" | |
15 | #include "signature.h" | |
5340d63d | 16 | #include "config.h" |
ab8d9242 | 17 | |
5c757327 | 18 | #include "git2/transaction.h" |
ab8d9242 CMN |
19 | #include "git2/signature.h" |
20 | #include "git2/sys/refs.h" | |
21 | #include "git2/sys/refdb_backend.h" | |
22 | ||
5340d63d CMN |
23 | typedef enum { |
24 | TRANSACTION_NONE, | |
25 | TRANSACTION_REFS, | |
26 | TRANSACTION_CONFIG, | |
27 | } transaction_t; | |
28 | ||
ab8d9242 CMN |
29 | typedef struct { |
30 | const char *name; | |
31 | void *payload; | |
32 | ||
ac3d33df | 33 | git_reference_t ref_type; |
ab8d9242 CMN |
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 { | |
5340d63d | 48 | transaction_t type; |
ab8d9242 CMN |
49 | git_repository *repo; |
50 | git_refdb *db; | |
5340d63d | 51 | git_config *cfg; |
ab8d9242 CMN |
52 | |
53 | git_strmap *locks; | |
54 | git_pool pool; | |
55 | }; | |
56 | ||
5340d63d CMN |
57 | int git_transaction_config_new(git_transaction **out, git_config *cfg) |
58 | { | |
59 | git_transaction *tx; | |
c25aa7cd PP |
60 | |
61 | GIT_ASSERT_ARG(out); | |
62 | GIT_ASSERT_ARG(cfg); | |
5340d63d CMN |
63 | |
64 | tx = git__calloc(1, sizeof(git_transaction)); | |
ac3d33df | 65 | GIT_ERROR_CHECK_ALLOC(tx); |
5340d63d CMN |
66 | |
67 | tx->type = TRANSACTION_CONFIG; | |
68 | tx->cfg = cfg; | |
69 | *out = tx; | |
70 | return 0; | |
71 | } | |
72 | ||
ab8d9242 CMN |
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 | ||
c25aa7cd PP |
79 | GIT_ASSERT_ARG(out); |
80 | GIT_ASSERT_ARG(repo); | |
ab8d9242 | 81 | |
22a2d3d5 UG |
82 | if ((error = git_pool_init(&pool, 1)) < 0) |
83 | goto on_error; | |
ab8d9242 CMN |
84 | |
85 | tx = git_pool_mallocz(&pool, sizeof(git_transaction)); | |
86 | if (!tx) { | |
87 | error = -1; | |
88 | goto on_error; | |
89 | } | |
90 | ||
22a2d3d5 | 91 | if ((error = git_strmap_new(&tx->locks)) < 0) { |
ab8d9242 CMN |
92 | error = -1; |
93 | goto on_error; | |
94 | } | |
95 | ||
96 | if ((error = git_repository_refdb(&tx->db, repo)) < 0) | |
97 | goto on_error; | |
98 | ||
5340d63d | 99 | tx->type = TRANSACTION_REFS; |
ab8d9242 CMN |
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 | ||
c327d5db | 110 | int git_transaction_lock_ref(git_transaction *tx, const char *refname) |
ab8d9242 CMN |
111 | { |
112 | int error; | |
113 | transaction_node *node; | |
114 | ||
c25aa7cd PP |
115 | GIT_ASSERT_ARG(tx); |
116 | GIT_ASSERT_ARG(refname); | |
ab8d9242 CMN |
117 | |
118 | node = git_pool_mallocz(&tx->pool, sizeof(transaction_node)); | |
ac3d33df | 119 | GIT_ERROR_CHECK_ALLOC(node); |
ab8d9242 CMN |
120 | |
121 | node->name = git_pool_strdup(&tx->pool, refname); | |
ac3d33df | 122 | GIT_ERROR_CHECK_ALLOC(node->name); |
ab8d9242 CMN |
123 | |
124 | if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) | |
125 | return error; | |
126 | ||
22a2d3d5 | 127 | if ((error = git_strmap_set(tx->locks, node->name, node)) < 0) |
ab8d9242 CMN |
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 | { | |
ab8d9242 CMN |
140 | transaction_node *node; |
141 | ||
22a2d3d5 | 142 | if ((node = git_strmap_get(tx->locks, refname)) == NULL) { |
ac3d33df | 143 | git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked"); |
ab8d9242 CMN |
144 | return GIT_ENOTFOUND; |
145 | } | |
146 | ||
ab8d9242 CMN |
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); | |
ac3d33df | 172 | GIT_ERROR_CHECK_ALLOC(node->message); |
ab8d9242 CMN |
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 | ||
c25aa7cd PP |
183 | GIT_ASSERT_ARG(tx); |
184 | GIT_ASSERT_ARG(refname); | |
185 | GIT_ASSERT_ARG(target); | |
ab8d9242 CMN |
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); | |
ac3d33df | 194 | node->ref_type = GIT_REFERENCE_DIRECT; |
ab8d9242 CMN |
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 | ||
c25aa7cd PP |
204 | GIT_ASSERT_ARG(tx); |
205 | GIT_ASSERT_ARG(refname); | |
206 | GIT_ASSERT_ARG(target); | |
ab8d9242 CMN |
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); | |
ac3d33df JK |
215 | GIT_ERROR_CHECK_ALLOC(node->target.symbolic); |
216 | node->ref_type = GIT_REFERENCE_SYMBOLIC; | |
ab8d9242 CMN |
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; | |
ac3d33df | 230 | node->ref_type = GIT_REFERENCE_DIRECT; /* the id will be ignored */ |
ab8d9242 CMN |
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)); | |
ac3d33df | 242 | GIT_ERROR_CHECK_ALLOC(reflog); |
ab8d9242 CMN |
243 | |
244 | reflog->ref_name = git_pool_strdup(pool, in->ref_name); | |
ac3d33df | 245 | GIT_ERROR_CHECK_ALLOC(reflog->ref_name); |
ab8d9242 CMN |
246 | |
247 | len = in->entries.length; | |
248 | reflog->entries.length = len; | |
249 | reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *)); | |
ac3d33df | 250 | GIT_ERROR_CHECK_ALLOC(reflog->entries.contents); |
ab8d9242 CMN |
251 | |
252 | entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry)); | |
ac3d33df | 253 | GIT_ERROR_CHECK_ALLOC(entries); |
ab8d9242 CMN |
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); | |
ac3d33df | 267 | GIT_ERROR_CHECK_ALLOC(tgt->msg); |
ab8d9242 CMN |
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 | ||
c25aa7cd PP |
283 | GIT_ASSERT_ARG(tx); |
284 | GIT_ASSERT_ARG(refname); | |
285 | GIT_ASSERT_ARG(reflog); | |
ab8d9242 CMN |
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 | ||
ac3d33df | 301 | if (node->ref_type == GIT_REFERENCE_DIRECT) { |
ab8d9242 | 302 | ref = git_reference__alloc(node->name, &node->target.id, NULL); |
ac3d33df | 303 | } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { |
ab8d9242 CMN |
304 | ref = git_reference__alloc_symbolic(node->name, node->target.symbolic); |
305 | } else { | |
369b0217 | 306 | abort(); |
ab8d9242 CMN |
307 | } |
308 | ||
ac3d33df | 309 | GIT_ERROR_CHECK_ALLOC(ref); |
ab8d9242 CMN |
310 | update_reflog = node->reflog == NULL; |
311 | ||
312 | if (node->remove) { | |
313 | error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL); | |
ac3d33df | 314 | } else if (node->ref_type == GIT_REFERENCE_DIRECT) { |
ab8d9242 | 315 | error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); |
ac3d33df | 316 | } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { |
ab8d9242 CMN |
317 | error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); |
318 | } else { | |
369b0217 | 319 | abort(); |
ab8d9242 CMN |
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; | |
369b0217 | 331 | int error = 0; |
ab8d9242 | 332 | |
c25aa7cd | 333 | GIT_ASSERT_ARG(tx); |
ab8d9242 | 334 | |
5340d63d CMN |
335 | if (tx->type == TRANSACTION_CONFIG) { |
336 | error = git_config_unlock(tx->cfg, true); | |
5340d63d CMN |
337 | tx->cfg = NULL; |
338 | ||
339 | return error; | |
340 | } | |
341 | ||
9694d9ba | 342 | git_strmap_foreach_value(tx->locks, node, { |
ab8d9242 CMN |
343 | if (node->reflog) { |
344 | if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0) | |
345 | return error; | |
346 | } | |
347 | ||
22a2d3d5 UG |
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 { | |
ab8d9242 CMN |
355 | if ((error = update_target(tx->db, node)) < 0) |
356 | return error; | |
357 | } | |
9694d9ba | 358 | }); |
ab8d9242 CMN |
359 | |
360 | return 0; | |
361 | } | |
362 | ||
363 | void git_transaction_free(git_transaction *tx) | |
364 | { | |
365 | transaction_node *node; | |
366 | git_pool pool; | |
ab8d9242 | 367 | |
c25aa7cd PP |
368 | if (!tx) |
369 | return; | |
ab8d9242 | 370 | |
5340d63d CMN |
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 | ||
ab8d9242 | 381 | /* start by unlocking the ones we've left hanging, if any */ |
9694d9ba | 382 | git_strmap_foreach_value(tx->locks, node, { |
ab8d9242 CMN |
383 | if (node->committed) |
384 | continue; | |
385 | ||
386 | git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); | |
9694d9ba | 387 | }); |
ab8d9242 CMN |
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 | } |