]> git.proxmox.com Git - libgit2.git/blob - src/reflog.c
Merge pull request #1159 from rick/be-consistent-be-be-consistent
[libgit2.git] / src / reflog.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
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 "reflog.h"
9 #include "repository.h"
10 #include "filebuf.h"
11 #include "signature.h"
12
13 static int reflog_init(git_reflog **reflog, const git_reference *ref)
14 {
15 git_reflog *log;
16
17 *reflog = NULL;
18
19 log = git__calloc(1, sizeof(git_reflog));
20 GITERR_CHECK_ALLOC(log);
21
22 log->ref_name = git__strdup(ref->name);
23 GITERR_CHECK_ALLOC(log->ref_name);
24
25 if (git_vector_init(&log->entries, 0, NULL) < 0) {
26 git__free(log->ref_name);
27 git__free(log);
28 return -1;
29 }
30
31 log->owner = git_reference_owner(ref);
32 *reflog = log;
33
34 return 0;
35 }
36
37 static int serialize_reflog_entry(
38 git_buf *buf,
39 const git_oid *oid_old,
40 const git_oid *oid_new,
41 const git_signature *committer,
42 const char *msg)
43 {
44 char raw_old[GIT_OID_HEXSZ+1];
45 char raw_new[GIT_OID_HEXSZ+1];
46
47 git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);
48 git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new);
49
50 git_buf_clear(buf);
51
52 git_buf_puts(buf, raw_old);
53 git_buf_putc(buf, ' ');
54 git_buf_puts(buf, raw_new);
55
56 git_signature__writebuf(buf, " ", committer);
57
58 /* drop trailing LF */
59 git_buf_rtrim(buf);
60
61 if (msg) {
62 git_buf_putc(buf, '\t');
63 git_buf_puts(buf, msg);
64 }
65
66 git_buf_putc(buf, '\n');
67
68 return git_buf_oom(buf);
69 }
70
71 static int reflog_entry_new(git_reflog_entry **entry)
72 {
73 git_reflog_entry *e;
74
75 assert(entry);
76
77 e = git__malloc(sizeof(git_reflog_entry));
78 GITERR_CHECK_ALLOC(e);
79
80 memset(e, 0, sizeof(git_reflog_entry));
81
82 *entry = e;
83
84 return 0;
85 }
86
87 static void reflog_entry_free(git_reflog_entry *entry)
88 {
89 git_signature_free(entry->committer);
90
91 git__free(entry->msg);
92 git__free(entry);
93 }
94
95 static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
96 {
97 const char *ptr;
98 git_reflog_entry *entry;
99
100 #define seek_forward(_increase) do { \
101 if (_increase >= buf_size) { \
102 giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \
103 goto fail; \
104 } \
105 buf += _increase; \
106 buf_size -= _increase; \
107 } while (0)
108
109 while (buf_size > GIT_REFLOG_SIZE_MIN) {
110 if (reflog_entry_new(&entry) < 0)
111 return -1;
112
113 entry->committer = git__malloc(sizeof(git_signature));
114 GITERR_CHECK_ALLOC(entry->committer);
115
116 if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0)
117 goto fail;
118 seek_forward(GIT_OID_HEXSZ + 1);
119
120 if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0)
121 goto fail;
122 seek_forward(GIT_OID_HEXSZ + 1);
123
124 ptr = buf;
125
126 /* Seek forward to the end of the signature. */
127 while (*buf && *buf != '\t' && *buf != '\n')
128 seek_forward(1);
129
130 if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0)
131 goto fail;
132
133 if (*buf == '\t') {
134 /* We got a message. Read everything till we reach LF. */
135 seek_forward(1);
136 ptr = buf;
137
138 while (*buf && *buf != '\n')
139 seek_forward(1);
140
141 entry->msg = git__strndup(ptr, buf - ptr);
142 GITERR_CHECK_ALLOC(entry->msg);
143 } else
144 entry->msg = NULL;
145
146 while (*buf && *buf == '\n' && buf_size > 1)
147 seek_forward(1);
148
149 if (git_vector_insert(&log->entries, entry) < 0)
150 goto fail;
151 }
152
153 return 0;
154
155 #undef seek_forward
156
157 fail:
158 if (entry)
159 reflog_entry_free(entry);
160
161 return -1;
162 }
163
164 void git_reflog_free(git_reflog *reflog)
165 {
166 unsigned int i;
167 git_reflog_entry *entry;
168
169 if (reflog == NULL)
170 return;
171
172 for (i=0; i < reflog->entries.length; i++) {
173 entry = git_vector_get(&reflog->entries, i);
174
175 reflog_entry_free(entry);
176 }
177
178 git_vector_free(&reflog->entries);
179 git__free(reflog->ref_name);
180 git__free(reflog);
181 }
182
183 static int retrieve_reflog_path(git_buf *path, const git_reference *ref)
184 {
185 return git_buf_join_n(path, '/', 3,
186 git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name);
187 }
188
189 static int create_new_reflog_file(const char *filepath)
190 {
191 int fd, error;
192
193 if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)
194 return error;
195
196 if ((fd = p_open(filepath,
197 O_WRONLY | O_CREAT | O_TRUNC,
198 GIT_REFLOG_FILE_MODE)) < 0)
199 return -1;
200
201 return p_close(fd);
202 }
203
204 int git_reflog_read(git_reflog **reflog, const git_reference *ref)
205 {
206 int error = -1;
207 git_buf log_path = GIT_BUF_INIT;
208 git_buf log_file = GIT_BUF_INIT;
209 git_reflog *log = NULL;
210
211 assert(reflog && ref);
212
213 *reflog = NULL;
214
215 if (reflog_init(&log, ref) < 0)
216 return -1;
217
218 if (retrieve_reflog_path(&log_path, ref) < 0)
219 goto cleanup;
220
221 error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
222 if (error < 0 && error != GIT_ENOTFOUND)
223 goto cleanup;
224
225 if ((error == GIT_ENOTFOUND) &&
226 ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
227 goto cleanup;
228
229 if ((error = reflog_parse(log,
230 git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
231 goto cleanup;
232
233 *reflog = log;
234 goto success;
235
236 cleanup:
237 git_reflog_free(log);
238
239 success:
240 git_buf_free(&log_file);
241 git_buf_free(&log_path);
242
243 return error;
244 }
245
246 int git_reflog_write(git_reflog *reflog)
247 {
248 int error = -1;
249 unsigned int i;
250 git_reflog_entry *entry;
251 git_buf log_path = GIT_BUF_INIT;
252 git_buf log = GIT_BUF_INIT;
253 git_filebuf fbuf = GIT_FILEBUF_INIT;
254
255 assert(reflog);
256
257
258 if (git_buf_join_n(&log_path, '/', 3,
259 git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0)
260 return -1;
261
262 if (!git_path_isfile(git_buf_cstr(&log_path))) {
263 giterr_set(GITERR_INVALID,
264 "Log file for reference '%s' doesn't exist.", reflog->ref_name);
265 goto cleanup;
266 }
267
268 if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0)
269 goto cleanup;
270
271 git_vector_foreach(&reflog->entries, i, entry) {
272 if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)
273 goto cleanup;
274
275 if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
276 goto cleanup;
277 }
278
279 error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);
280 goto success;
281
282 cleanup:
283 git_filebuf_cleanup(&fbuf);
284
285 success:
286 git_buf_free(&log);
287 git_buf_free(&log_path);
288 return error;
289 }
290
291 int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,
292 const git_signature *committer, const char *msg)
293 {
294 git_reflog_entry *entry;
295 const git_reflog_entry *previous;
296 const char *newline;
297
298 assert(reflog && new_oid && committer);
299
300 if (reflog_entry_new(&entry) < 0)
301 return -1;
302
303 if ((entry->committer = git_signature_dup(committer)) == NULL)
304 goto cleanup;
305
306 if (msg != NULL) {
307 if ((entry->msg = git__strdup(msg)) == NULL)
308 goto cleanup;
309
310 newline = strchr(msg, '\n');
311
312 if (newline) {
313 if (newline[1] != '\0') {
314 giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");
315 goto cleanup;
316 }
317
318 entry->msg[newline - msg] = '\0';
319 }
320 }
321
322 previous = git_reflog_entry_byindex(reflog, 0);
323
324 if (previous == NULL)
325 git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO);
326 else
327 git_oid_cpy(&entry->oid_old, &previous->oid_cur);
328
329 git_oid_cpy(&entry->oid_cur, new_oid);
330
331 if (git_vector_insert(&reflog->entries, entry) < 0)
332 goto cleanup;
333
334 return 0;
335
336 cleanup:
337 reflog_entry_free(entry);
338 return -1;
339 }
340
341 int git_reflog_rename(git_reference *ref, const char *new_name)
342 {
343 int error, fd;
344 git_buf old_path = GIT_BUF_INIT;
345 git_buf new_path = GIT_BUF_INIT;
346 git_buf temp_path = GIT_BUF_INIT;
347 git_buf normalized = GIT_BUF_INIT;
348
349 assert(ref && new_name);
350
351 if ((git_reference__normalize_name(
352 &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
353 return -1;
354
355 error = -1;
356
357 if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0)
358 goto cleanup;
359
360 if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0)
361 goto cleanup;
362
363 if (git_buf_joinpath(&new_path,
364 git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
365 goto cleanup;
366
367 /*
368 * Move the reflog to a temporary place. This two-phase renaming is required
369 * in order to cope with funny renaming use cases when one tries to move a reference
370 * to a partially colliding namespace:
371 * - a/b -> a/b/c
372 * - a/b/c/d -> a/b/c
373 */
374 if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
375 goto cleanup;
376
377 if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0)
378 goto cleanup;
379 p_close(fd);
380
381 if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0)
382 goto cleanup;
383
384 if (git_path_isdir(git_buf_cstr(&new_path)) &&
385 (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0))
386 goto cleanup;
387
388 if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0)
389 goto cleanup;
390
391 error = p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path));
392
393 cleanup:
394 git_buf_free(&temp_path);
395 git_buf_free(&old_path);
396 git_buf_free(&new_path);
397 git_buf_free(&normalized);
398
399 return error;
400 }
401
402 int git_reflog_delete(git_reference *ref)
403 {
404 int error;
405 git_buf path = GIT_BUF_INIT;
406
407 error = retrieve_reflog_path(&path, ref);
408
409 if (!error && git_path_exists(path.ptr))
410 error = p_unlink(path.ptr);
411
412 git_buf_free(&path);
413
414 return error;
415 }
416
417 size_t git_reflog_entrycount(git_reflog *reflog)
418 {
419 assert(reflog);
420 return reflog->entries.length;
421 }
422
423 GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total)
424 {
425 return (total - 1) - idx;
426 }
427
428 const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx)
429 {
430 assert(reflog);
431
432 if (idx >= reflog->entries.length)
433 return NULL;
434
435 return git_vector_get(
436 &reflog->entries, reflog_inverse_index(idx, reflog->entries.length));
437 }
438
439 const git_oid * git_reflog_entry_id_old(const git_reflog_entry *entry)
440 {
441 assert(entry);
442 return &entry->oid_old;
443 }
444
445 const git_oid * git_reflog_entry_id_new(const git_reflog_entry *entry)
446 {
447 assert(entry);
448 return &entry->oid_cur;
449 }
450
451 const git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
452 {
453 assert(entry);
454 return entry->committer;
455 }
456
457 const char * git_reflog_entry_message(const git_reflog_entry *entry)
458 {
459 assert(entry);
460 return entry->msg;
461 }
462
463 int git_reflog_drop(
464 git_reflog *reflog,
465 size_t idx,
466 int rewrite_previous_entry)
467 {
468 size_t entrycount;
469 git_reflog_entry *entry, *previous;
470
471 assert(reflog);
472
473 entrycount = git_reflog_entrycount(reflog);
474
475 entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
476
477 if (entry == NULL)
478 return GIT_ENOTFOUND;
479
480 reflog_entry_free(entry);
481
482 if (git_vector_remove(
483 &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0)
484 return -1;
485
486 if (!rewrite_previous_entry)
487 return 0;
488
489 /* No need to rewrite anything when removing the most recent entry */
490 if (idx == 0)
491 return 0;
492
493 /* Have the latest entry just been dropped? */
494 if (entrycount == 1)
495 return 0;
496
497 entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1);
498
499 /* If the oldest entry has just been removed... */
500 if (idx == entrycount - 1) {
501 /* ...clear the oid_old member of the "new" oldest entry */
502 if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)
503 return -1;
504
505 return 0;
506 }
507
508 previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
509 git_oid_cpy(&entry->oid_old, &previous->oid_cur);
510
511 return 0;
512 }