On Sun, Jul 11, 2010 at 09:16:26AM +0200, Jakub Narebski wrote: > > The problem is, that when you have 'foo/bar' branch, then you have > 'foo/bar' reflog. When you delete branch 'foo/bar', but do not delete > 'foo/bar' reflog (only add to it branch deletion event), and then you > want to create 'foo' branch, git wouldn't be able to create reflog > fo 'foo' because of directory / file (D/F) conflict: there is 'foo/' > directory preventing file 'foo' from being created. Right, of course. So how about this? Clemens -->o-- Date: Sun, 11 Jul 2010 12:37:06 +0200 Subject: [PATCH/RFC] graveyard for reflogs Instead of removing reflogs together with refs, keep them around and reuse them as a starting point, if a ref of the same name is created again. When a ref is deleted, a ~ is appended to each directory of the corresponding reflog entry, e.g., refs/heads/branch is renamed to refs~/heads~/branch. Since ~ must not be used for ref names, directory/file conflicts are thus avoided. Known issues: - The reflog cannot be accessed while the ref does not exist. - Older git versions will not resurrect the reflog, and therefore leave the renamed reflog behind. - Breaks t7701, because git-expire tries to lock log entries, which fails because ~ is an illegal character for refs. - Breaks t9300. Signed-off-by: Clemens Buchacher <drizzd@xxxxxx> --- reflog-walk.c | 9 +++++-- refs.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- t/t1450-fsck.sh | 1 + 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/reflog-walk.c b/reflog-walk.c index 4879615..9415ac8 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -228,15 +228,18 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) return; } - reflog = &commit_reflog->reflogs->items[commit_reflog->recno]; info->last_commit_reflog = commit_reflog; commit_reflog->recno--; - commit_info->commit = (struct commit *)parse_object(reflog->osha1); - if (!commit_info->commit) { + if (commit_reflog->recno < 0) { commit->parents = NULL; return; } + reflog = &commit_reflog->reflogs->items[commit_reflog->recno]; + commit_info->commit = (struct commit *)parse_object(reflog->nsha1); + if (!commit_info->commit) + die("Invalid reflog entry"); + commit->parents = xcalloc(sizeof(struct commit_list), 1); commit->parents->item = commit_info->commit; commit->object.flags &= ~(ADDED | SEEN | SHOWN); diff --git a/refs.c b/refs.c index b540067..78c48ad 100644 --- a/refs.c +++ b/refs.c @@ -1052,6 +1052,70 @@ static int repack_without_ref(const char *refname) return commit_lock_file(&packlock); } +static void pronounce_dead(struct strbuf *dead_ref, const char *ref) +{ + const char *p = ref; + while (*p) { + if (*p == '/') { + int len = p - ref; + strbuf_add(dead_ref, ref, len); + if (len > 0) + strbuf_addstr(dead_ref, "~/"); + ref = p + 1; + } + p++; + } + strbuf_addstr(dead_ref, ref); +} + +static int bury_log(const char *ref) +{ + struct strbuf dead_ref = STRBUF_INIT; + struct stat loginfo; + + if (lstat(git_path("logs/%s", ref), &loginfo)) + return 0; + + pronounce_dead(&dead_ref, ref); + + if (S_ISLNK(loginfo.st_mode)) + return error("reflog for %s is a symlink", ref); + + if (safe_create_leading_directories(git_path("logs/%s", dead_ref.buf))) + return error("unable to create directory for %s", dead_ref.buf); + + if (rename(git_path("logs/%s", ref), git_path("logs/%s", dead_ref.buf))) + return error("unable to move logfile to logs/%s: %s", + dead_ref.buf, strerror(errno)); + + strbuf_release(&dead_ref); + return 0; +} + +static int resurrect_log(const char *ref) +{ + struct strbuf dead_ref = STRBUF_INIT; + struct stat loginfo; + + pronounce_dead(&dead_ref, ref); + + if (lstat(git_path("logs/%s", dead_ref.buf), &loginfo)) + return 0; + + if (S_ISLNK(loginfo.st_mode)) + return error("reflog for %s is a symlink", dead_ref.buf); + + if (safe_create_leading_directories(git_path("logs/%s", ref))) + return error("unable to create directory for %s", ref); + + if (rename(git_path("logs/%s", dead_ref.buf), git_path("logs/%s", ref))) + return error("unable to move logfile to logs/%s: %s", + ref, strerror(errno)); + + strbuf_release(&dead_ref); + return 0; +} + int delete_ref(const char *refname, const unsigned char *sha1, int delopt) { struct ref_lock *lock; @@ -1084,7 +1148,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) */ ret |= repack_without_ref(refname); - unlink_or_warn(git_path("logs/%s", lock->ref_name)); + bury_log(lock->ref_name); invalidate_cached_refs(); unlock_ref(lock); return ret; @@ -1384,6 +1448,7 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return -1; } + resurrect_log(lock->ref_name); invalidate_cached_refs(); if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 || (strcmp(lock->ref_name, lock->orig_ref_name) && diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 759cf12..65f160e 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -55,6 +55,7 @@ test_expect_success 'object with bad sha1' ' grep "$sha.*corrupt" out && rm -f .git/objects/$new && git update-ref -d refs/heads/bogus && + rm -f .git/logs/refs~/heads~/bogus && git read-tree -u --reset HEAD ' -- 1.7.1.571.gba4d01
Attachment:
signature.asc
Description: Digital signature