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