]>
Commit | Line | Data |
---|---|---|
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 | 13 | static 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 | 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) | |
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 | 71 | static 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 | 87 | static 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 | ||
95 | static 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 | 157 | fail: |
40c75652 | 158 | if (entry) |
159 | reflog_entry_free(entry); | |
59341a5d | 160 | |
40c75652 | 161 | return -1; |
59341a5d | 162 | } |
163 | ||
27df4275 MS |
164 | void 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 | 183 | static 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 | 189 | static 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 | 204 | int 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 | ||
236 | cleanup: | |
237 | git_reflog_free(log); | |
a4c291ef | 238 | |
ae833178 | 239 | success: |
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 | 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 | ||
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 | ||
281 | cleanup: | |
282 | git_filebuf_cleanup(&fbuf); | |
283 | ||
284 | success: | |
285 | git_buf_free(&log); | |
286 | git_buf_free(&log_path); | |
287 | return error; | |
288 | } | |
289 | ||
40c75652 | 290 | int 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 | 335 | cleanup: |
40c75652 | 336 | reflog_entry_free(entry); |
337 | return -1; | |
27df4275 MS |
338 | } |
339 | ||
b7c93a66 MS |
340 | int 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 | |
402 | cleanup: | |
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 | ||
411 | int 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 | 426 | size_t git_reflog_entrycount(git_reflog *reflog) |
27df4275 MS |
427 | { |
428 | assert(reflog); | |
2508cc66 | 429 | return reflog->entries.length; |
27df4275 MS |
430 | } |
431 | ||
a8122b5d | 432 | GIT_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 |
437 | const 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 | 448 | const 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 | 454 | const 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 | 460 | const git_signature * git_reflog_entry_committer(const git_reflog_entry *entry) |
27df4275 MS |
461 | { |
462 | assert(entry); | |
463 | return entry->committer; | |
464 | } | |
465 | ||
2508cc66 | 466 | const char * git_reflog_entry_message(const git_reflog_entry *entry) |
27df4275 MS |
467 | { |
468 | assert(entry); | |
469 | return entry->msg; | |
470 | } | |
59341a5d | 471 | |
b84f75c3 | 472 | int 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 | } |