2 * Copyright (C) 2009-2012 the libgit2 contributors
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.
9 #include "repository.h"
11 #include "signature.h"
13 static int reflog_init(git_reflog
**reflog
, const git_reference
*ref
)
19 log
= git__calloc(1, sizeof(git_reflog
));
20 GITERR_CHECK_ALLOC(log
);
22 log
->ref_name
= git__strdup(ref
->name
);
23 GITERR_CHECK_ALLOC(log
->ref_name
);
25 if (git_vector_init(&log
->entries
, 0, NULL
) < 0) {
26 git__free(log
->ref_name
);
31 log
->owner
= git_reference_owner(ref
);
37 static int serialize_reflog_entry(
39 const git_oid
*oid_old
,
40 const git_oid
*oid_new
,
41 const git_signature
*committer
,
44 char raw_old
[GIT_OID_HEXSZ
+1];
45 char raw_new
[GIT_OID_HEXSZ
+1];
47 git_oid_tostr(raw_old
, GIT_OID_HEXSZ
+1, oid_old
);
48 git_oid_tostr(raw_new
, GIT_OID_HEXSZ
+1, oid_new
);
52 git_buf_puts(buf
, raw_old
);
53 git_buf_putc(buf
, ' ');
54 git_buf_puts(buf
, raw_new
);
56 git_signature__writebuf(buf
, " ", committer
);
58 /* drop trailing LF */
62 git_buf_putc(buf
, '\t');
63 git_buf_puts(buf
, msg
);
66 git_buf_putc(buf
, '\n');
68 return git_buf_oom(buf
);
71 static int reflog_entry_new(git_reflog_entry
**entry
)
77 e
= git__malloc(sizeof(git_reflog_entry
));
78 GITERR_CHECK_ALLOC(e
);
80 memset(e
, 0, sizeof(git_reflog_entry
));
87 static void reflog_entry_free(git_reflog_entry
*entry
)
89 git_signature_free(entry
->committer
);
91 git__free(entry
->msg
);
95 static int reflog_parse(git_reflog
*log
, const char *buf
, size_t buf_size
)
98 git_reflog_entry
*entry
;
100 #define seek_forward(_increase) do { \
101 if (_increase >= buf_size) { \
102 giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \
106 buf_size -= _increase; \
109 while (buf_size
> GIT_REFLOG_SIZE_MIN
) {
110 if (reflog_entry_new(&entry
) < 0)
113 entry
->committer
= git__malloc(sizeof(git_signature
));
114 GITERR_CHECK_ALLOC(entry
->committer
);
116 if (git_oid_fromstrn(&entry
->oid_old
, buf
, GIT_OID_HEXSZ
) < 0)
118 seek_forward(GIT_OID_HEXSZ
+ 1);
120 if (git_oid_fromstrn(&entry
->oid_cur
, buf
, GIT_OID_HEXSZ
) < 0)
122 seek_forward(GIT_OID_HEXSZ
+ 1);
126 /* Seek forward to the end of the signature. */
127 while (*buf
&& *buf
!= '\t' && *buf
!= '\n')
130 if (git_signature__parse(entry
->committer
, &ptr
, buf
+ 1, NULL
, *buf
) < 0)
134 /* We got a message. Read everything till we reach LF. */
138 while (*buf
&& *buf
!= '\n')
141 entry
->msg
= git__strndup(ptr
, buf
- ptr
);
142 GITERR_CHECK_ALLOC(entry
->msg
);
146 while (*buf
&& *buf
== '\n' && buf_size
> 1)
149 if (git_vector_insert(&log
->entries
, entry
) < 0)
159 reflog_entry_free(entry
);
164 void git_reflog_free(git_reflog
*reflog
)
167 git_reflog_entry
*entry
;
172 for (i
=0; i
< reflog
->entries
.length
; i
++) {
173 entry
= git_vector_get(&reflog
->entries
, i
);
175 reflog_entry_free(entry
);
178 git_vector_free(&reflog
->entries
);
179 git__free(reflog
->ref_name
);
183 static int retrieve_reflog_path(git_buf
*path
, const git_reference
*ref
)
185 return git_buf_join_n(path
, '/', 3,
186 git_reference_owner(ref
)->path_repository
, GIT_REFLOG_DIR
, ref
->name
);
189 static int create_new_reflog_file(const char *filepath
)
193 if ((error
= git_futils_mkpath2file(filepath
, GIT_REFLOG_DIR_MODE
)) < 0)
196 if ((fd
= p_open(filepath
,
197 O_WRONLY
| O_CREAT
| O_TRUNC
,
198 GIT_REFLOG_FILE_MODE
)) < 0)
204 int git_reflog_read(git_reflog
**reflog
, const git_reference
*ref
)
207 git_buf log_path
= GIT_BUF_INIT
;
208 git_buf log_file
= GIT_BUF_INIT
;
209 git_reflog
*log
= NULL
;
211 assert(reflog
&& ref
);
215 if (reflog_init(&log
, ref
) < 0)
218 if (retrieve_reflog_path(&log_path
, ref
) < 0)
221 error
= git_futils_readbuffer(&log_file
, git_buf_cstr(&log_path
));
222 if (error
< 0 && error
!= GIT_ENOTFOUND
)
225 if ((error
== GIT_ENOTFOUND
) &&
226 ((error
= create_new_reflog_file(git_buf_cstr(&log_path
))) < 0))
229 if ((error
= reflog_parse(log
,
230 git_buf_cstr(&log_file
), git_buf_len(&log_file
))) < 0)
237 git_reflog_free(log
);
240 git_buf_free(&log_file
);
241 git_buf_free(&log_path
);
246 int git_reflog_write(git_reflog
*reflog
)
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
;
258 if (git_buf_join_n(&log_path
, '/', 3,
259 git_repository_path(reflog
->owner
), GIT_REFLOG_DIR
, reflog
->ref_name
) < 0)
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
);
268 if ((error
= git_filebuf_open(&fbuf
, git_buf_cstr(&log_path
), 0)) < 0)
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)
275 if ((error
= git_filebuf_write(&fbuf
, log
.ptr
, log
.size
)) < 0)
279 error
= git_filebuf_commit(&fbuf
, GIT_REFLOG_FILE_MODE
);
283 git_filebuf_cleanup(&fbuf
);
287 git_buf_free(&log_path
);
291 int git_reflog_append(git_reflog
*reflog
, const git_oid
*new_oid
,
292 const git_signature
*committer
, const char *msg
)
294 git_reflog_entry
*entry
;
295 const git_reflog_entry
*previous
;
298 assert(reflog
&& new_oid
&& committer
);
300 if (reflog_entry_new(&entry
) < 0)
303 if ((entry
->committer
= git_signature_dup(committer
)) == NULL
)
307 if ((entry
->msg
= git__strdup(msg
)) == NULL
)
310 newline
= strchr(msg
, '\n');
313 if (newline
[1] != '\0') {
314 giterr_set(GITERR_INVALID
, "Reflog message cannot contain newline");
318 entry
->msg
[newline
- msg
] = '\0';
322 previous
= git_reflog_entry_byindex(reflog
, 0);
324 if (previous
== NULL
)
325 git_oid_fromstr(&entry
->oid_old
, GIT_OID_HEX_ZERO
);
327 git_oid_cpy(&entry
->oid_old
, &previous
->oid_cur
);
329 git_oid_cpy(&entry
->oid_cur
, new_oid
);
331 if (git_vector_insert(&reflog
->entries
, entry
) < 0)
337 reflog_entry_free(entry
);
341 int git_reflog_rename(git_reference
*ref
, const char *new_name
)
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
;
349 assert(ref
&& new_name
);
351 if ((git_reference__normalize_name(
352 &normalized
, new_name
, GIT_REF_FORMAT_ALLOW_ONELEVEL
)) < 0)
357 if (git_buf_joinpath(&temp_path
, git_reference_owner(ref
)->path_repository
, GIT_REFLOG_DIR
) < 0)
360 if (git_buf_joinpath(&old_path
, git_buf_cstr(&temp_path
), ref
->name
) < 0)
363 if (git_buf_joinpath(&new_path
,
364 git_buf_cstr(&temp_path
), git_buf_cstr(&normalized
)) < 0)
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:
374 if (git_buf_joinpath(&temp_path
, git_buf_cstr(&temp_path
), "temp_reflog") < 0)
377 if ((fd
= git_futils_mktmp(&temp_path
, git_buf_cstr(&temp_path
))) < 0)
381 if (p_rename(git_buf_cstr(&old_path
), git_buf_cstr(&temp_path
)) < 0)
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))
388 if (git_futils_mkpath2file(git_buf_cstr(&new_path
), GIT_REFLOG_DIR_MODE
) < 0)
391 error
= p_rename(git_buf_cstr(&temp_path
), git_buf_cstr(&new_path
));
394 git_buf_free(&temp_path
);
395 git_buf_free(&old_path
);
396 git_buf_free(&new_path
);
397 git_buf_free(&normalized
);
402 int git_reflog_delete(git_reference
*ref
)
405 git_buf path
= GIT_BUF_INIT
;
407 error
= retrieve_reflog_path(&path
, ref
);
409 if (!error
&& git_path_exists(path
.ptr
))
410 error
= p_unlink(path
.ptr
);
417 size_t git_reflog_entrycount(git_reflog
*reflog
)
420 return reflog
->entries
.length
;
423 GIT_INLINE(size_t) reflog_inverse_index(size_t idx
, size_t total
)
425 return (total
- 1) - idx
;
428 const git_reflog_entry
* git_reflog_entry_byindex(git_reflog
*reflog
, size_t idx
)
432 if (idx
>= reflog
->entries
.length
)
435 return git_vector_get(
436 &reflog
->entries
, reflog_inverse_index(idx
, reflog
->entries
.length
));
439 const git_oid
* git_reflog_entry_id_old(const git_reflog_entry
*entry
)
442 return &entry
->oid_old
;
445 const git_oid
* git_reflog_entry_id_new(const git_reflog_entry
*entry
)
448 return &entry
->oid_cur
;
451 const git_signature
* git_reflog_entry_committer(const git_reflog_entry
*entry
)
454 return entry
->committer
;
457 const char * git_reflog_entry_message(const git_reflog_entry
*entry
)
466 int rewrite_previous_entry
)
469 git_reflog_entry
*entry
, *previous
;
473 entrycount
= git_reflog_entrycount(reflog
);
475 entry
= (git_reflog_entry
*)git_reflog_entry_byindex(reflog
, idx
);
478 return GIT_ENOTFOUND
;
480 reflog_entry_free(entry
);
482 if (git_vector_remove(
483 &reflog
->entries
, reflog_inverse_index(idx
, entrycount
)) < 0)
486 if (!rewrite_previous_entry
)
489 /* No need to rewrite anything when removing the most recent entry */
493 /* Have the latest entry just been dropped? */
497 entry
= (git_reflog_entry
*)git_reflog_entry_byindex(reflog
, idx
- 1);
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)
508 previous
= (git_reflog_entry
*)git_reflog_entry_byindex(reflog
, idx
);
509 git_oid_cpy(&entry
->oid_old
, &previous
->oid_cur
);