From 9a13f0b71b887af42c3be854344f185c6dfa1d0d Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:05 -0500 Subject: [PATCH 01/12] make reflog filename independent from struct ref_lock This allows for ref_log_write() to be used in a more flexible way, and is needed for future changes. This is only code reorg with no behavior change. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-reflog.c | 10 ++++++---- refs.c | 40 ++++++++++++++++++++-------------------- refs.h | 1 - 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/builtin-reflog.c b/builtin-reflog.c index b443ed9ef6..b6612a90ed 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -242,7 +242,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, struct cmd_reflog_expire_cb *cmd = cb_data; struct expire_reflog_cb cb; struct ref_lock *lock; - char *newlog_path = NULL; + char *log_file, *newlog_path = NULL; int status = 0; if (strncmp(ref, "refs/", 5)) @@ -255,7 +255,8 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, lock = lock_ref_sha1(ref + 5, sha1); if (!lock) return error("cannot lock ref '%s'", ref); - if (!file_exists(lock->log_file)) + log_file = xstrdup(git_path("logs/%s", ref)); + if (!file_exists(log_file)) goto finish; if (!cmd->dry_run) { newlog_path = xstrdup(git_path("logs/%s.lock", ref)); @@ -271,13 +272,14 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, if (fclose(cb.newlog)) status |= error("%s: %s", strerror(errno), newlog_path); - if (rename(newlog_path, lock->log_file)) { + if (rename(newlog_path, log_file)) { status |= error("cannot rename %s to %s", - newlog_path, lock->log_file); + newlog_path, log_file); unlink(newlog_path); } } free(newlog_path); + free(log_file); unlock_ref(lock); return status; } diff --git a/refs.c b/refs.c index 12e46b8bbe..3b295f3806 100644 --- a/refs.c +++ b/refs.c @@ -680,7 +680,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock->lk = xcalloc(1, sizeof(struct lock_file)); lock->ref_name = xstrdup(ref); - lock->log_file = xstrdup(git_path("logs/%s", ref)); ref_file = git_path("%s", ref); lock->force_write = lstat(ref_file, &st) && errno == ENOENT; @@ -776,10 +775,10 @@ int delete_ref(const char *refname, unsigned char *sha1) */ ret |= repack_without_ref(refname); - err = unlink(lock->log_file); + err = unlink(git_path("logs/%s", lock->ref_name)); if (err && errno != ENOENT) fprintf(stderr, "warning: unlink(%s) failed: %s", - lock->log_file, strerror(errno)); + git_path("logs/%s", lock->ref_name), strerror(errno)); invalidate_cached_refs(); unlock_ref(lock); return ret; @@ -920,47 +919,48 @@ void unlock_ref(struct ref_lock *lock) rollback_lock_file(lock->lk); } free(lock->ref_name); - free(lock->log_file); free(lock); } -static int log_ref_write(struct ref_lock *lock, - const unsigned char *sha1, const char *msg) +static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg) { int logfd, written, oflags = O_APPEND | O_WRONLY; unsigned maxlen, len; int msglen; - char *logrec; + char *log_file, *logrec; const char *committer; if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); + log_file = git_path("logs/%s", ref_name); + if (log_all_ref_updates && - (!strncmp(lock->ref_name, "refs/heads/", 11) || - !strncmp(lock->ref_name, "refs/remotes/", 13))) { - if (safe_create_leading_directories(lock->log_file) < 0) + (!strncmp(ref_name, "refs/heads/", 11) || + !strncmp(ref_name, "refs/remotes/", 13))) { + if (safe_create_leading_directories(log_file) < 0) return error("unable to create directory for %s", - lock->log_file); + log_file); oflags |= O_CREAT; } - logfd = open(lock->log_file, oflags, 0666); + logfd = open(log_file, oflags, 0666); if (logfd < 0) { if (!(oflags & O_CREAT) && errno == ENOENT) return 0; if ((oflags & O_CREAT) && errno == EISDIR) { - if (remove_empty_directories(lock->log_file)) { + if (remove_empty_directories(log_file)) { return error("There are still logs under '%s'", - lock->log_file); + log_file); } - logfd = open(lock->log_file, oflags, 0666); + logfd = open(log_file, oflags, 0666); } if (logfd < 0) return error("Unable to append to %s: %s", - lock->log_file, strerror(errno)); + log_file, strerror(errno)); } msglen = 0; @@ -982,8 +982,8 @@ static int log_ref_write(struct ref_lock *lock, maxlen = strlen(committer) + msglen + 100; logrec = xmalloc(maxlen); len = sprintf(logrec, "%s %s %s\n", - sha1_to_hex(lock->old_sha1), - sha1_to_hex(sha1), + sha1_to_hex(old_sha1), + sha1_to_hex(new_sha1), committer); if (msglen) len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1; @@ -991,7 +991,7 @@ static int log_ref_write(struct ref_lock *lock, free(logrec); close(logfd); if (written != len) - return error("Unable to append to %s", lock->log_file); + return error("Unable to append to %s", log_file); return 0; } @@ -1014,7 +1014,7 @@ int write_ref_sha1(struct ref_lock *lock, return -1; } invalidate_cached_refs(); - if (log_ref_write(lock, sha1, logmsg) < 0) { + if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0) { unlock_ref(lock); return -1; } diff --git a/refs.h b/refs.h index 33450f13e7..2d2ba149ab 100644 --- a/refs.h +++ b/refs.h @@ -3,7 +3,6 @@ struct ref_lock { char *ref_name; - char *log_file; struct lock_file *lk; unsigned char old_sha1[20]; int lock_fd; From 1655707c9ec56847945854f8645ed64f74159e99 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:06 -0500 Subject: [PATCH 02/12] lock_ref_sha1_basic(): remember the original name of a ref when resolving it A ref might be pointing to another ref but only the name of the last ref is remembered. Let's remember about the first name as well. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- refs.c | 2 ++ refs.h | 1 + 2 files changed, 3 insertions(+) diff --git a/refs.c b/refs.c index 3b295f3806..bd76ea65a9 100644 --- a/refs.c +++ b/refs.c @@ -680,6 +680,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock->lk = xcalloc(1, sizeof(struct lock_file)); lock->ref_name = xstrdup(ref); + lock->orig_ref_name = xstrdup(orig_ref); ref_file = git_path("%s", ref); lock->force_write = lstat(ref_file, &st) && errno == ENOENT; @@ -919,6 +920,7 @@ void unlock_ref(struct ref_lock *lock) rollback_lock_file(lock->lk); } free(lock->ref_name); + free(lock->orig_ref_name); free(lock); } diff --git a/refs.h b/refs.h index 2d2ba149ab..94a58b41fb 100644 --- a/refs.h +++ b/refs.h @@ -3,6 +3,7 @@ struct ref_lock { char *ref_name; + char *orig_ref_name; struct lock_file *lk; unsigned char old_sha1[20]; int lock_fd; From bd104db164d7f2a714aa0f1cdf89fd89fee6c00a Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:07 -0500 Subject: [PATCH 03/12] enable separate reflog for HEAD If HEAD is tied to a branch then both logs/HEAD and logs/heads/ are updated. This is also true for any symbolic refs in general, but only HEAD will see its reflog created automatically. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- refs.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index bd76ea65a9..5b2ca086aa 100644 --- a/refs.c +++ b/refs.c @@ -940,7 +940,8 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, if (log_all_ref_updates && (!strncmp(ref_name, "refs/heads/", 11) || - !strncmp(ref_name, "refs/remotes/", 13))) { + !strncmp(ref_name, "refs/remotes/", 13) || + !strcmp(ref_name, "HEAD"))) { if (safe_create_leading_directories(log_file) < 0) return error("unable to create directory for %s", log_file); @@ -1016,7 +1017,9 @@ int write_ref_sha1(struct ref_lock *lock, return -1; } invalidate_cached_refs(); - if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0) { + if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 || + (strcmp(lock->ref_name, lock->orig_ref_name) && + log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) { unlock_ref(lock); return -1; } From e1dde3d06c7caa242dd4b419aebb9a9b7fee2d48 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:08 -0500 Subject: [PATCH 04/12] add reflog entries for HEAD when detached Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- git-checkout.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-checkout.sh b/git-checkout.sh index 8500f51ea2..ac378cdb1e 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -259,8 +259,9 @@ if [ "$?" -eq 0 ]; then # git update-ref --detach HEAD $new # or something like that... # - echo "$detached" >"$GIT_DIR/HEAD.new" && - mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" || + git-rev-parse HEAD >"$GIT_DIR/HEAD.new" && + mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" && + git-update-ref -m "checkout: moving to $arg" HEAD "$detached" || die "Cannot detach HEAD" if test -n "$detach_warn" then From 41b625b047ad5c537ca312d2f07c86bdd783a7b0 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:09 -0500 Subject: [PATCH 05/12] move create_symref() past log_ref_write() This doesn't change the code at all. It is done to make the next patch more obvious. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- refs.c | 94 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/refs.c b/refs.c index 5b2ca086aa..ec6fe29ebb 100644 --- a/refs.c +++ b/refs.c @@ -309,53 +309,6 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * return ref; } -int create_symref(const char *ref_target, const char *refs_heads_master) -{ - const char *lockpath; - char ref[1000]; - int fd, len, written; - const char *git_HEAD = git_path("%s", ref_target); - -#ifndef NO_SYMLINK_HEAD - if (prefer_symlink_refs) { - unlink(git_HEAD); - if (!symlink(refs_heads_master, git_HEAD)) - return 0; - fprintf(stderr, "no symlink - falling back to symbolic ref\n"); - } -#endif - - len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); - if (sizeof(ref) <= len) { - error("refname too long: %s", refs_heads_master); - return -1; - } - lockpath = mkpath("%s.lock", git_HEAD); - fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd < 0) { - error("Unable to open %s for writing", lockpath); - return -5; - } - written = write_in_full(fd, ref, len); - close(fd); - if (written != len) { - unlink(lockpath); - error("Unable to write to %s", lockpath); - return -2; - } - if (rename(lockpath, git_HEAD) < 0) { - unlink(lockpath); - error("Unable to create %s", git_HEAD); - return -3; - } - if (adjust_shared_perm(git_HEAD)) { - unlink(lockpath); - error("Unable to fix permissions on %s", lockpath); - return -4; - } - return 0; -} - int read_ref(const char *ref, unsigned char *sha1) { if (resolve_ref(ref, sha1, 1, NULL)) @@ -1033,6 +986,53 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } +int create_symref(const char *ref_target, const char *refs_heads_master) +{ + const char *lockpath; + char ref[1000]; + int fd, len, written; + const char *git_HEAD = git_path("%s", ref_target); + +#ifndef NO_SYMLINK_HEAD + if (prefer_symlink_refs) { + unlink(git_HEAD); + if (!symlink(refs_heads_master, git_HEAD)) + return 0; + fprintf(stderr, "no symlink - falling back to symbolic ref\n"); + } +#endif + + len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); + if (sizeof(ref) <= len) { + error("refname too long: %s", refs_heads_master); + return -1; + } + lockpath = mkpath("%s.lock", git_HEAD); + fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd < 0) { + error("Unable to open %s for writing", lockpath); + return -5; + } + written = write_in_full(fd, ref, len); + close(fd); + if (written != len) { + unlink(lockpath); + error("Unable to write to %s", lockpath); + return -2; + } + if (rename(lockpath, git_HEAD) < 0) { + unlink(lockpath); + error("Unable to create %s", git_HEAD); + return -3; + } + if (adjust_shared_perm(git_HEAD)) { + unlink(lockpath); + error("Unable to fix permissions on %s", lockpath); + return -4; + } + return 0; +} + static char *ref_msg(const char *line, const char *endp) { const char *ep; From 8b5157e40718a2ee1d645c342d93df4e66335479 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:10 -0500 Subject: [PATCH 06/12] add logref support to git-symbolic-ref Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- Documentation/git-symbolic-ref.txt | 6 +++++- builtin-branch.c | 3 ++- builtin-init-db.c | 2 +- builtin-symbolic-ref.c | 16 ++++++++++++++-- cache.h | 2 +- refs.c | 14 ++++++++++++-- 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index f93b79a85e..a88f722860 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -7,7 +7,7 @@ git-symbolic-ref - Read and modify symbolic refs SYNOPSIS -------- -'git-symbolic-ref' [-q] [] +'git-symbolic-ref' [-q] [-m ] [] DESCRIPTION ----------- @@ -31,6 +31,10 @@ OPTIONS symbolic ref but a detached HEAD; instead exit with non-zero status silently. +-m:: + Update the reflog for with . This is valid only + when creating or updating a symbolic ref. + NOTES ----- In the past, `.git/HEAD` was a symbolic link pointing at diff --git a/builtin-branch.c b/builtin-branch.c index d60690bb08..76f174fd89 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -381,7 +381,8 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (rename_ref(oldref, newref, logmsg)) die("Branch rename failed"); - if (!strcmp(oldname, head) && create_symref("HEAD", newref)) + /* no need to pass logmsg here as HEAD didn't really move */ + if (!strcmp(oldname, head) && create_symref("HEAD", newref, NULL)) die("Branch renamed to %s, but HEAD is not updated!", newname); } diff --git a/builtin-init-db.c b/builtin-init-db.c index 1865489381..12e43d0db4 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -231,7 +231,7 @@ static int create_default_files(const char *git_dir, const char *template_path) strcpy(path + len, "HEAD"); reinit = !read_ref("HEAD", sha1); if (!reinit) { - if (create_symref("HEAD", "refs/heads/master") < 0) + if (create_symref("HEAD", "refs/heads/master", NULL) < 0) exit(1); } diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 227c9d4a62..d41b40640b 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -3,7 +3,7 @@ #include "refs.h" static const char git_symbolic_ref_usage[] = -"git-symbolic-ref [-q] name [ref]"; +"git-symbolic-ref [-q] [-m ] name [ref]"; static void check_symref(const char *HEAD, int quiet) { @@ -25,6 +25,7 @@ static void check_symref(const char *HEAD, int quiet) int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { int quiet = 0; + const char *msg = NULL; git_config(git_default_config); @@ -34,6 +35,17 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) break; else if (!strcmp("-q", arg)) quiet = 1; + else if (!strcmp("-m", arg)) { + argc--; + argv++; + if (argc <= 1) + break; + msg = argv[1]; + if (!*msg) + die("Refusing to perform update with empty message"); + if (strchr(msg, '\n')) + die("Refusing to perform update with \\n in message"); + } else if (!strcmp("--", arg)) { argc--; argv++; @@ -50,7 +62,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[1], quiet); break; case 3: - create_symref(argv[1], argv[2]); + create_symref(argv[1], argv[2], msg); break; default: usage(git_symbolic_ref_usage); diff --git a/cache.h b/cache.h index 9873ee97d9..201704bacf 100644 --- a/cache.h +++ b/cache.h @@ -302,7 +302,7 @@ extern int read_ref(const char *filename, unsigned char *sha1); extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); -extern int create_symref(const char *ref, const char *refs_heads_master); +extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg); extern int validate_headref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); diff --git a/refs.c b/refs.c index ec6fe29ebb..591318501f 100644 --- a/refs.c +++ b/refs.c @@ -986,18 +986,23 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } -int create_symref(const char *ref_target, const char *refs_heads_master) +int create_symref(const char *ref_target, const char *refs_heads_master, + const char *logmsg) { const char *lockpath; char ref[1000]; int fd, len, written; const char *git_HEAD = git_path("%s", ref_target); + unsigned char old_sha1[20], new_sha1[20]; + + if (logmsg && read_ref(ref_target, old_sha1)) + hashclr(old_sha1); #ifndef NO_SYMLINK_HEAD if (prefer_symlink_refs) { unlink(git_HEAD); if (!symlink(refs_heads_master, git_HEAD)) - return 0; + goto done; fprintf(stderr, "no symlink - falling back to symbolic ref\n"); } #endif @@ -1030,6 +1035,11 @@ int create_symref(const char *ref_target, const char *refs_heads_master) error("Unable to fix permissions on %s", lockpath); return -4; } + + done: + if (logmsg && !read_ref(refs_heads_master, new_sha1)) + log_ref_write(ref_target, old_sha1, new_sha1, logmsg); + return 0; } From 47fc52e2876ed3a54cd4b77e0b0c4875fa3317b8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 26 Jan 2007 17:49:00 -0800 Subject: [PATCH 07/12] create_symref(): do not assume pathname from git_path() persists long enough Being lazy to rely on the cycling N buffers mkpath() and friends return is nice in general, but it makes it too easy to introduce new bugs that are "mysterious". Introduction of read_ref() in create_symref() after calling git_path() to get the git_HEAD value (i.e. the path to create a new symref at) consumed more than the available buffers and broke a later call to mkpath() that derives lockpath from it. Signed-off-by: Junio C Hamano --- refs.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/refs.c b/refs.c index 591318501f..4a523086e3 100644 --- a/refs.c +++ b/refs.c @@ -992,7 +992,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master, const char *lockpath; char ref[1000]; int fd, len, written; - const char *git_HEAD = git_path("%s", ref_target); + char *git_HEAD = xstrdup(git_path("%s", ref_target)); unsigned char old_sha1[20], new_sha1[20]; if (logmsg && read_ref(ref_target, old_sha1)) @@ -1010,36 +1010,38 @@ int create_symref(const char *ref_target, const char *refs_heads_master, len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); if (sizeof(ref) <= len) { error("refname too long: %s", refs_heads_master); - return -1; + goto error_free_return; } lockpath = mkpath("%s.lock", git_HEAD); fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd < 0) { error("Unable to open %s for writing", lockpath); - return -5; + goto error_free_return; } written = write_in_full(fd, ref, len); close(fd); if (written != len) { - unlink(lockpath); error("Unable to write to %s", lockpath); - return -2; + goto error_unlink_return; } if (rename(lockpath, git_HEAD) < 0) { - unlink(lockpath); error("Unable to create %s", git_HEAD); - return -3; + goto error_unlink_return; } if (adjust_shared_perm(git_HEAD)) { - unlink(lockpath); error("Unable to fix permissions on %s", lockpath); - return -4; + error_unlink_return: + unlink(lockpath); + error_free_return: + free(git_HEAD); + return -1; } done: if (logmsg && !read_ref(refs_heads_master, new_sha1)) log_ref_write(ref_target, old_sha1, new_sha1, logmsg); + free(git_HEAD); return 0; } From a7e4fbf990290f955da1319ab605a59f85d3c3e2 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:11 -0500 Subject: [PATCH 08/12] add reflog when moving HEAD to a new branch Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- git-checkout.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-checkout.sh b/git-checkout.sh index ac378cdb1e..1349e77f32 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -250,7 +250,7 @@ if [ "$?" -eq 0 ]; then fi if test -n "$branch" then - GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" + GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch" elif test -n "$detached" then # NEEDSWORK: we would want a command to detach the HEAD From eb8381c88518b10d683a29deea1d43ed671f14ec Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 3 Feb 2007 13:25:43 -0500 Subject: [PATCH 09/12] scan reflogs independently from refs Currently, the search for all reflogs depends on the existence of corresponding refs under the .git/refs/ directory. Let's scan the .git/logs/ directory directly instead. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-reflog.c | 7 ++----- fsck-objects.c | 9 +++++++-- reachable.c | 4 ++-- refs.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ refs.h | 6 ++++++ 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/builtin-reflog.c b/builtin-reflog.c index b6612a90ed..bfb169ac04 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -245,14 +245,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, char *log_file, *newlog_path = NULL; int status = 0; - if (strncmp(ref, "refs/", 5)) - return error("not a ref '%s'", ref); - memset(&cb, 0, sizeof(cb)); /* we take the lock for the ref itself to prevent it from * getting updated. */ - lock = lock_ref_sha1(ref + 5, sha1); + lock = lock_any_ref_for_update(ref, sha1); if (!lock) return error("cannot lock ref '%s'", ref); log_file = xstrdup(git_path("logs/%s", ref)); @@ -353,7 +350,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) } if (do_all) - status |= for_each_ref(expire_reflog, &cb); + status |= for_each_reflog(expire_reflog, &cb); while (i < argc) { const char *ref = argv[i++]; unsigned char sha1[20]; diff --git a/fsck-objects.c b/fsck-objects.c index ecfb014fff..c9b4a39807 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -477,6 +477,12 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, return 0; } +static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data) +{ + for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); + return 0; +} + static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -495,14 +501,13 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f obj->used = 1; mark_reachable(obj, REACHABLE); - for_each_reflog_ent(refname, fsck_handle_reflog_ent, NULL); - return 0; } static void get_default_heads(void) { for_each_ref(fsck_handle_ref, NULL); + for_each_reflog(fsck_handle_reflog, NULL); /* * Not having any default heads isn't really fatal, but diff --git a/reachable.c b/reachable.c index a6a334822a..01760d7046 100644 --- a/reachable.c +++ b/reachable.c @@ -188,9 +188,9 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog) /* Add all external refs */ for_each_ref(add_one_ref, revs); - /* Add all reflog info from refs */ + /* Add all reflog info */ if (mark_reflog) - for_each_ref(add_one_reflog, revs); + for_each_reflog(add_one_reflog, revs); /* * Set up the revision walk - this will move all commits diff --git a/refs.c b/refs.c index 4a523086e3..da09e434c7 100644 --- a/refs.c +++ b/refs.c @@ -1201,3 +1201,53 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) return ret; } +static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) +{ + DIR *dir = opendir(git_path("logs/%s", base)); + int retval = errno; + + if (dir) { + struct dirent *de; + int baselen = strlen(base); + char *log = xmalloc(baselen + 257); + + memcpy(log, base, baselen); + if (baselen && base[baselen-1] != '/') + log[baselen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + struct stat st; + int namelen; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(log + baselen, de->d_name, namelen+1); + if (stat(git_path("logs/%s", log), &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + retval = do_for_each_reflog(log, fn, cb_data); + } else { + unsigned char sha1[20]; + if (!resolve_ref(log, sha1, 0, NULL)) + retval = error("bad ref for %s", log); + else + retval = fn(log, sha1, 0, cb_data); + } + if (retval) + break; + } + free(log); + closedir(dir); + } + return retval; +} + +int for_each_reflog(each_ref_fn fn, void *cb_data) +{ + return do_for_each_reflog("", fn, cb_data); +} diff --git a/refs.h b/refs.h index 94a58b41fb..acedffc0e4 100644 --- a/refs.h +++ b/refs.h @@ -48,6 +48,12 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data); +/* + * Calls the specified function for each reflog file until it returns nonzero, + * and returns the value + */ +extern int for_each_reflog(each_ref_fn, void *); + /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); From f2eba66d4d1a664f40bcfd45e0b8a2670df222b3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 3 Feb 2007 21:49:16 -0500 Subject: [PATCH 10/12] Enable HEAD@{...} and make it independent from the current branch Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- sha1_name.c | 62 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index de8caf8cf6..d77f77097f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -235,22 +235,23 @@ static int ambiguous_path(const char *path, int len) return slash; } +static const char *ref_fmt[] = { + "%.*s", + "refs/%.*s", + "refs/tags/%.*s", + "refs/heads/%.*s", + "refs/remotes/%.*s", + "refs/remotes/%.*s/HEAD", + NULL +}; + int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) { - static const char *fmt[] = { - "%.*s", - "refs/%.*s", - "refs/tags/%.*s", - "refs/heads/%.*s", - "refs/remotes/%.*s", - "refs/remotes/%.*s/HEAD", - NULL - }; const char **p, *r; int refs_found = 0; *ref = NULL; - for (p = fmt; *p; p++) { + for (p = ref_fmt; *p; p++) { unsigned char sha1_from_ref[20]; unsigned char *this_result; @@ -266,6 +267,28 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) return refs_found; } +static int dwim_log(const char *str, int len, unsigned char *sha1, char **log) +{ + const char **p; + int logs_found = 0; + + *log = NULL; + for (p = ref_fmt; *p; p++) { + struct stat st; + char *path = mkpath(*p, len, str); + if (!stat(git_path("logs/%s", path), &st) && + S_ISREG(st.st_mode)) { + if (!logs_found++) { + *log = xstrdup(path); + resolve_ref(path, sha1, 0, NULL); + } + if (!warn_ambiguous_refs) + break; + } + } + return logs_found; +} + static int get_sha1_basic(const char *str, int len, unsigned char *sha1) { static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; @@ -295,7 +318,9 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (!len && reflog_len) { /* allow "@{...}" to mean the current branch reflog */ refs_found = dwim_ref("HEAD", 4, sha1, &real_ref); - } else + } else if (reflog_len) + refs_found = dwim_log(str, len, sha1, &real_ref); + else refs_found = dwim_ref(str, len, sha1, &real_ref); if (!refs_found) @@ -310,21 +335,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) unsigned long co_time; int co_tz, co_cnt; - /* - * We'll have an independent reflog for "HEAD" eventually - * which won't be a synonym for the current branch reflog. - * In the mean time prevent people from getting used to - * such a synonym until the work is completed. - */ - if (len && !strncmp("HEAD", str, len) && - !strncmp(real_ref, "refs/", 5)) { - error("reflog for HEAD has not been implemented yet\n" - "Maybe you could try %s%s instead, " - "or just %s for current branch..", - strchr(real_ref+5, '/')+1, str+len, str+len); - exit(-1); - } - /* Is it asking for N-th entry, or approxidate? */ for (i = nth = 0; 0 <= nth && i < reflog_len; i++) { char ch = str[at+2+i]; From dc9195ac7830bdf08ee847ef6a385c0b8f673d69 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 3 Feb 2007 21:50:39 -0500 Subject: [PATCH 11/12] Let git-checkout always drop any detached head We used to refuse leaving a detached HEAD when it wasn't matching an existing ref so not to lose any commit that might have been performed while not on any branch (unless -f was provided). But this protection was completely bogus since it was still possible to move to HEAD^ while still remaining detached but losing the last commit anyway if there was one. Now that we have a proper reflog for HEAD it is best to simply remove that bogus (and admitedly annoying) protection and simply display the last HEAD position instead. If one wants to recover a lost detached state then it can be retrieved from the HEAD reflog. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- git-checkout.sh | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/git-checkout.sh b/git-checkout.sh index 2c8cbe43a6..14835a4aa9 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -164,22 +164,9 @@ If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b " fi -elif test -z "$oldbranch" && test -n "$branch" +elif test -z "$oldbranch" && test -z "$quiet" then - # Coming back... - if test -z "$force" - then - git show-ref -d -s | grep "$old" >/dev/null || { - echo >&2 \ -"You are not on any branch and switching to branch '$new_name' -may lose your changes. At this point, you can do one of two things: - (1) Decide it is Ok and say 'git checkout -f $new_name'; - (2) Start a new branch from the current commit, by saying - 'git checkout -b '. -Leaving your HEAD detached; not switching to branch '$new_name'." - exit 1; - } - fi + echo >&2 "Previous HEAD position was $old" fi if [ "X$old" = X ] From 632ac9fd12a2d4ff2c1a1fcd63492ce24315221f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 3 Feb 2007 23:31:47 -0800 Subject: [PATCH 12/12] show-branch -g: default to the current branch. Now we have a separate reflog on HEAD, show-branch -g without an explicit parameter defaults to the current branch, or HEAD when it is detached from branches. Signed-off-by: Junio C Hamano --- Documentation/git-show-branch.txt | 8 +++++--- builtin-show-branch.c | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index b38633c397..ba5313d51f 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git-show-branch' [--all] [--remotes] [--topo-order] [--current] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]... -'git-show-branch' (-g|--reflog)[=[,]] [--list] +'git-show-branch' (-g|--reflog)[=[,]] [--list] [] DESCRIPTION ----------- @@ -97,11 +97,13 @@ OPTIONS will show the revisions given by "git rev-list {caret}master topic1 topic2" ---reflog[=[,]] :: +--reflog[=[,]] []:: Shows most recent ref-log entries for the given ref. If is given, entries going back from that entry. can be specified as count or date. - `-g` can be used as a short-hand for this option. + `-g` can be used as a short-hand for this option. When + no explicit parameter is given, it defaults to the + current branch (or `HEAD` if it is detached). Note that --more, --list, --independent and --merge-base options are mutually exclusive. diff --git a/builtin-show-branch.c b/builtin-show-branch.c index fa62e487b1..0d94e40df8 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -690,7 +690,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (ac == 0) { static const char *fake_av[2]; - fake_av[0] = "HEAD"; + const char *refname; + + refname = resolve_ref("HEAD", sha1, 1, NULL); + fake_av[0] = xstrdup(refname); fake_av[1] = NULL; av = fake_av; ac = 1;