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