From eca35a25a92a1ad725af2a549fc9158488c4cc43 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sun, 26 Oct 2008 03:33:56 +0100 Subject: [PATCH 1/5] Fix git branch -m for symrefs. This had two problems with symrefs. First, it copied the actual sha1 instead of the "pointer", second it failed to remove the old ref after a successful rename. Given that till now delete_ref() always dereferenced symrefs, a new parameters has been introduced to delete_ref() to allow deleting refs without a dereference. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-branch.c | 2 +- builtin-remote.c | 4 +-- builtin-reset.c | 2 +- builtin-send-pack.c | 2 +- builtin-tag.c | 2 +- builtin-update-ref.c | 2 +- cache.h | 2 +- receive-pack.c | 2 +- refs.c | 59 +++++++++++++++++++++++++++----------------- t/t3200-branch.sh | 9 +++++++ 10 files changed, 55 insertions(+), 31 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index b1a2ad7a6b..4b4abfd363 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -160,7 +160,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) continue; } - if (delete_ref(name, sha1)) { + if (delete_ref(name, sha1, 0)) { error("Error deleting %sbranch '%s'", remote, argv[i]); ret = 1; diff --git a/builtin-remote.c b/builtin-remote.c index 90a4e35828..584280fbf5 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -340,7 +340,7 @@ static int remove_branches(struct string_list *branches) const char *refname = item->string; unsigned char *sha1 = item->util; - if (delete_ref(refname, sha1)) + if (delete_ref(refname, sha1, 0)) result |= error("Could not remove branch %s", refname); } return result; @@ -570,7 +570,7 @@ static int prune(int argc, const char **argv) const char *refname = states.stale.items[i].util; if (!dry_run) - result |= delete_ref(refname, NULL); + result |= delete_ref(refname, NULL, 0); printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", abbrev_ref(refname, "refs/remotes/")); diff --git a/builtin-reset.c b/builtin-reset.c index 16e6bb20f1..9514b77f8c 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -279,7 +279,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); } else if (old_orig) - delete_ref("ORIG_HEAD", old_orig); + delete_ref("ORIG_HEAD", old_orig, 0); prepend_reflog_action("updating HEAD", msg, sizeof(msg)); update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR); diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 2af9f29341..ea1ad6e35f 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -226,7 +226,7 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref) if (args.verbose) fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); if (ref->deletion) { - delete_ref(rs.dst, NULL); + delete_ref(rs.dst, NULL, 0); } else update_ref("update by push", rs.dst, ref->new_sha1, NULL, 0, 0); diff --git a/builtin-tag.c b/builtin-tag.c index f2853d08c7..5b141c5846 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -125,7 +125,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) static int delete_tag(const char *name, const char *ref, const unsigned char *sha1) { - if (delete_ref(ref, sha1)) + if (delete_ref(ref, sha1, 0)) return 1; printf("Deleted tag '%s'\n", name); return 0; diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 56a0b1b39c..d8f3142c06 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -48,7 +48,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("%s: not a valid old SHA1", oldval); if (delete) - return delete_ref(refname, oldval ? oldsha1 : NULL); + return delete_ref(refname, oldval ? oldsha1 : NULL, 0); else return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, no_deref ? REF_NODEREF : 0, DIE_ON_ERR); diff --git a/cache.h b/cache.h index 884fae826c..715348a066 100644 --- a/cache.h +++ b/cache.h @@ -420,7 +420,7 @@ extern int commit_locked_index(struct lock_file *); extern void set_alternate_index_output(const char *); extern int close_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); -extern int delete_ref(const char *, const unsigned char *sha1); +extern int delete_ref(const char *, const unsigned char *sha1, int delopt); /* Environment bits from configuration mechanism */ extern int trust_executable_bit; diff --git a/receive-pack.c b/receive-pack.c index d44c19e6b5..f0145bd901 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -222,7 +222,7 @@ static const char *update(struct command *cmd) warning ("Allowing deletion of corrupt ref."); old_sha1 = NULL; } - if (delete_ref(name, old_sha1)) { + if (delete_ref(name, old_sha1, 0)) { error("failed to delete %s", name); return "failed to delete"; } diff --git a/refs.c b/refs.c index 39a3b23804..b929301752 100644 --- a/refs.c +++ b/refs.c @@ -912,25 +912,33 @@ static int repack_without_ref(const char *refname) return commit_lock_file(&packlock); } -int delete_ref(const char *refname, const unsigned char *sha1) +int delete_ref(const char *refname, const unsigned char *sha1, int delopt) { struct ref_lock *lock; - int err, i, ret = 0, flag = 0; + int err, i = 0, ret = 0, flag = 0; lock = lock_ref_sha1_basic(refname, sha1, 0, &flag); if (!lock) return 1; if (!(flag & REF_ISPACKED)) { /* loose */ - i = strlen(lock->lk->filename) - 5; /* .lock */ - lock->lk->filename[i] = 0; - err = unlink(lock->lk->filename); + const char *path; + + if (!(delopt & REF_NODEREF)) { + i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; + path = lock->lk->filename; + } else { + path = git_path(refname); + } + err = unlink(path); if (err && errno != ENOENT) { ret = 1; error("unlink(%s) failed: %s", - lock->lk->filename, strerror(errno)); + path, strerror(errno)); } - lock->lk->filename[i] = '.'; + if (!(delopt & REF_NODEREF)) + lock->lk->filename[i] = '.'; } /* removing the loose one could have resurrected an earlier * packed one. Also, if it was not loose we need to repack @@ -955,11 +963,16 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) struct ref_lock *lock; struct stat loginfo; int log = !lstat(git_path("logs/%s", oldref), &loginfo); + const char *symref = NULL; + int is_symref = 0; if (S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldref); - if (!resolve_ref(oldref, orig_sha1, 1, &flag)) + symref = resolve_ref(oldref, orig_sha1, 1, &flag); + if (flag & REF_ISSYMREF) + is_symref = 1; + if (!symref) return error("refname %s not found", oldref); if (!is_refname_available(newref, oldref, get_packed_refs(), 0)) @@ -979,12 +992,12 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) return error("unable to move logfile logs/%s to tmp-renamed-log: %s", oldref, strerror(errno)); - if (delete_ref(oldref, orig_sha1)) { + if (delete_ref(oldref, orig_sha1, REF_NODEREF)) { error("unable to delete old %s", oldref); goto rollback; } - if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) { + if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1, REF_NODEREF)) { if (errno==EISDIR) { if (remove_empty_directories(git_path("%s", newref))) { error("Directory not empty: %s", newref); @@ -1022,18 +1035,20 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) } logmoved = log; - lock = lock_ref_sha1_basic(newref, NULL, 0, NULL); - if (!lock) { - error("unable to lock %s for update", newref); - goto rollback; - } - - lock->force_write = 1; - hashcpy(lock->old_sha1, orig_sha1); - if (write_ref_sha1(lock, orig_sha1, logmsg)) { - error("unable to write current sha1 into %s", newref); - goto rollback; - } + if (!is_symref) { + lock = lock_ref_sha1_basic(newref, NULL, 0, NULL); + if (!lock) { + error("unable to lock %s for update", newref); + goto rollback; + } + lock->force_write = 1; + hashcpy(lock->old_sha1, orig_sha1); + if (write_ref_sha1(lock, orig_sha1, logmsg)) { + error("unable to write current sha1 into %s", newref); + goto rollback; + } + } else + create_symref(newref, symref, logmsg); return 0; diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 2147eacc50..fdeb1f529c 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -112,6 +112,15 @@ test_expect_success 'config information was renamed, too' \ "test $(git config branch.s.dummy) = Hello && test_must_fail git config branch.s/s/dummy" +test_expect_success 'renaming a symref' \ +' + git symbolic-ref refs/heads/master2 refs/heads/master && + git branch -m master2 master3 && + git symbolic-ref refs/heads/master3 && + test -f .git/refs/heads/master && + ! test -f .git/refs/heads/master2 +' + test_expect_success \ 'git branch -m u v should fail when the reflog for u is a symlink' ' git branch -l u && From 450d4c0f5a966b3f5835107ec4d8c344c8c25908 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sun, 26 Oct 2008 03:33:57 +0100 Subject: [PATCH 2/5] rename_ref(): handle the case when the reflog of a ref does not exist We tried to check if a reflog of a ref is a symlink without first checking if it exists, which is a bug. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- refs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refs.c b/refs.c index b929301752..b39e6f2c2d 100644 --- a/refs.c +++ b/refs.c @@ -966,7 +966,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) const char *symref = NULL; int is_symref = 0; - if (S_ISLNK(loginfo.st_mode)) + if (log && S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldref); symref = resolve_ref(oldref, orig_sha1, 1, &flag); From 569740bdd0533ef5cf032edd6233710161a35725 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sun, 26 Oct 2008 03:33:58 +0100 Subject: [PATCH 3/5] Fix git update-ref --no-deref -d. Till now --no-deref was just ignored when deleting refs, fix this. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-update-ref.c | 8 +++++--- t/t1400-update-ref.sh | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/builtin-update-ref.c b/builtin-update-ref.c index d8f3142c06..378dc1b7a6 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -13,7 +13,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname, *oldval, *msg=NULL; unsigned char sha1[20], oldsha1[20]; - int delete = 0, no_deref = 0; + int delete = 0, no_deref = 0, flags = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"), OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"), @@ -47,9 +47,11 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (oldval && *oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); + if (no_deref) + flags = REF_NODEREF; if (delete) - return delete_ref(refname, oldval ? oldsha1 : NULL, 0); + return delete_ref(refname, oldval ? oldsha1 : NULL, flags); else return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, - no_deref ? REF_NODEREF : 0, DIE_ON_ERR); + flags, DIE_ON_ERR); } diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 04c2b164bc..8139cd6cc9 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -75,6 +75,13 @@ test_expect_success "delete $m (by HEAD)" ' ' rm -f .git/$m +cp -f .git/HEAD .git/HEAD.orig +test_expect_success "delete symref without dereference" ' + git update-ref --no-deref -d HEAD && + ! test -f .git/HEAD +' +cp -f .git/HEAD.orig .git/HEAD + test_expect_success '(not) create HEAD with old sha1' " test_must_fail git update-ref HEAD $A $B " From fa58186c9ba50514b36ac5ef192cd7e0bc4d7780 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 29 Oct 2008 01:05:27 +0100 Subject: [PATCH 4/5] git branch -m: forbid renaming of a symref There may be cases where one would really want to rename the symbolic ref without changing its value, but "git branch -m" is not such a use-case. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- refs.c | 29 +++++++++++++---------------- t/t3200-branch.sh | 8 ++++---- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/refs.c b/refs.c index b39e6f2c2d..8a38e0822f 100644 --- a/refs.c +++ b/refs.c @@ -964,14 +964,14 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) struct stat loginfo; int log = !lstat(git_path("logs/%s", oldref), &loginfo); const char *symref = NULL; - int is_symref = 0; if (log && S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldref); symref = resolve_ref(oldref, orig_sha1, 1, &flag); if (flag & REF_ISSYMREF) - is_symref = 1; + return error("refname %s is a symbolic ref, renaming it is not supported", + oldref); if (!symref) return error("refname %s not found", oldref); @@ -1035,20 +1035,17 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) } logmoved = log; - if (!is_symref) { - lock = lock_ref_sha1_basic(newref, NULL, 0, NULL); - if (!lock) { - error("unable to lock %s for update", newref); - goto rollback; - } - lock->force_write = 1; - hashcpy(lock->old_sha1, orig_sha1); - if (write_ref_sha1(lock, orig_sha1, logmsg)) { - error("unable to write current sha1 into %s", newref); - goto rollback; - } - } else - create_symref(newref, symref, logmsg); + lock = lock_ref_sha1_basic(newref, NULL, 0, NULL); + if (!lock) { + error("unable to lock %s for update", newref); + goto rollback; + } + lock->force_write = 1; + hashcpy(lock->old_sha1, orig_sha1); + if (write_ref_sha1(lock, orig_sha1, logmsg)) { + error("unable to write current sha1 into %s", newref); + goto rollback; + } return 0; diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index fdeb1f529c..25e9971fd8 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -112,13 +112,13 @@ test_expect_success 'config information was renamed, too' \ "test $(git config branch.s.dummy) = Hello && test_must_fail git config branch.s/s/dummy" -test_expect_success 'renaming a symref' \ +test_expect_success 'renaming a symref is not allowed' \ ' git symbolic-ref refs/heads/master2 refs/heads/master && - git branch -m master2 master3 && - git symbolic-ref refs/heads/master3 && + test_must_fail git branch -m master2 master3 && + git symbolic-ref refs/heads/master2 && test -f .git/refs/heads/master && - ! test -f .git/refs/heads/master2 + ! test -f .git/refs/heads/master3 ' test_expect_success \ From 045a476f91a9a308c823a2709977163238baa3fd Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sat, 1 Nov 2008 00:25:44 +0100 Subject: [PATCH 5/5] update-ref --no-deref -d: handle the case when the pointed ref is packed In this case we did nothing in the past, but we should delete the reference in fact. The problem was that when the symref is not packed but the referenced ref is packed, then we assumed that the symref is packed as well, but symrefs are never packed. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- refs.c | 2 +- t/t1400-update-ref.sh | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/refs.c b/refs.c index 8a38e0822f..0d239e15b0 100644 --- a/refs.c +++ b/refs.c @@ -920,7 +920,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) lock = lock_ref_sha1_basic(refname, sha1, 0, &flag); if (!lock) return 1; - if (!(flag & REF_ISPACKED)) { + if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { /* loose */ const char *path; diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 8139cd6cc9..bd589268fc 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -82,6 +82,17 @@ test_expect_success "delete symref without dereference" ' ' cp -f .git/HEAD.orig .git/HEAD +test_expect_success "delete symref without dereference when the referred ref is packed" ' + echo foo >foo.c && + git add foo.c && + git commit -m foo && + git pack-refs --all && + git update-ref --no-deref -d HEAD && + ! test -f .git/HEAD +' +cp -f .git/HEAD.orig .git/HEAD +git update-ref -d $m + test_expect_success '(not) create HEAD with old sha1' " test_must_fail git update-ref HEAD $A $B "