From 4431fcc4b134ae501e3e57dc568ae4f031e57898 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Sep 2006 01:09:18 -0700 Subject: [PATCH 1/4] Clean-up lock-ref implementation This drops "mustexist" parameter lock_ref_sha1() and lock_any_ref_forupdate() functions take. Signed-off-by: Junio C Hamano --- builtin-pack-refs.c | 2 +- builtin-update-ref.c | 2 +- fetch.c | 2 +- refs.c | 13 ++++++------- refs.h | 4 ++-- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index db57fee72d..4093973a05 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -53,7 +53,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, /* make sure nobody touched the ref, and unlink */ static void prune_ref(struct ref_to_prune *r) { - struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1, 1); + struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1); if (lock) { unlink(git_path("%s", r->name)); diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 90a3da53ad..ab528337aa 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) if (oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); - lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0); + lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL); if (!lock) return 1; if (write_ref_sha1(lock, sha1, msg) < 0) diff --git a/fetch.c b/fetch.c index a2cbdfba82..c426c04997 100644 --- a/fetch.c +++ b/fetch.c @@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref, if (!write_ref || !write_ref[i]) continue; - lock[i] = lock_ref_sha1(write_ref[i], NULL, 0); + lock[i] = lock_ref_sha1(write_ref[i], NULL); if (!lock[i]) { error("Can't lock ref %s", write_ref[i]); goto unlock_and_fail; diff --git a/refs.c b/refs.c index 2cef2b4f0e..9a1bc0db59 100644 --- a/refs.c +++ b/refs.c @@ -447,12 +447,13 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int mustexist) +static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1) { char *ref_file; const char *orig_ref = ref; struct ref_lock *lock; struct stat st; + int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; @@ -480,20 +481,18 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; } -struct ref_lock *lock_ref_sha1(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) { char refpath[PATH_MAX]; if (check_ref_format(ref)) return NULL; strcpy(refpath, mkpath("refs/%s", ref)); - return lock_ref_sha1_basic(refpath, old_sha1, mustexist); + return lock_ref_sha1_basic(refpath, old_sha1); } -struct ref_lock *lock_any_ref_for_update(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1) { - return lock_ref_sha1_basic(ref, old_sha1, mustexist); + return lock_ref_sha1_basic(ref, old_sha1); } void unlock_ref(struct ref_lock *lock) diff --git a/refs.h b/refs.h index 305d408690..0d4d79e03f 100644 --- a/refs.h +++ b/refs.h @@ -27,10 +27,10 @@ extern int for_each_remote_ref(each_ref_fn, void *); extern int get_ref_sha1(const char *ref, unsigned char *sha1); /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ -extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1); /** Locks any ref (for 'HEAD' type refs). */ -extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1); /** Release any lock taken but not written. **/ extern void unlock_ref(struct ref_lock *lock); From ac5409e420e5fdd7c4a381f873ffcedfb83d7117 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Sep 2006 01:58:57 -0700 Subject: [PATCH 2/4] update-ref: -d flag and ref creation safety. This adds -d flag to update-ref to allow safe deletion of ref. Before deleting it, the command checks if the given still matches the value the caller thought the ref contained. Similarly, it also accepts 0{40} or an empty string as to allow safe creation of a new ref. Signed-off-by: Junio C Hamano --- Documentation/git-update-ref.txt | 10 ++++++++-- builtin-update-ref.c | 18 +++++++++++++++--- cache.h | 1 + refs.c | 26 ++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index e062030e91..71bcb7954f 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely SYNOPSIS -------- -'git-update-ref' [-m ] [] +'git-update-ref' [-m ] (-d | []) DESCRIPTION ----------- @@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that the current value of the matches . E.g. `git-update-ref refs/heads/master ` updates the master branch head to only if its current -value is . +value is . You can specify 40 "0" or an empty string +as to make sure that the ref you are creating does +not exist. It also allows a "ref" file to be a symbolic pointer to another ref file by starting with the four-byte header sequence of @@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a ref symlink to some other tree, if you have copied a whole archive by creating a symlink tree). +With `-d` flag, it deletes the named after verifying it +still contains . + + Logging Updates --------------- If config parameter "core.logAllRefUpdates" is true or the file diff --git a/builtin-update-ref.c b/builtin-update-ref.c index ab528337aa..b34e5987dd 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -3,15 +3,16 @@ #include "builtin.h" static const char git_update_ref_usage[] = -"git-update-ref [] [-m ]"; +"git-update-ref [-m ] (-d | [])"; int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL; struct ref_lock *lock; unsigned char sha1[20], oldsha1[20]; - int i; + int i, delete; + delete = 0; setup_ident(); git_config(git_default_config); @@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with \\n in message."); continue; } + if (!strcmp("-d", argv[i])) { + delete = 1; + continue; + } if (!refname) { refname = argv[i]; continue; @@ -44,8 +49,15 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (get_sha1(value, sha1)) die("%s: not a valid SHA1", value); + + if (delete) { + if (oldval) + usage(git_update_ref_usage); + return delete_ref(refname, sha1); + } + hashclr(oldsha1); - if (oldval && get_sha1(oldval, oldsha1)) + if (oldval && *oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL); diff --git a/cache.h b/cache.h index 6def155162..6e004505be 100644 --- a/cache.h +++ b/cache.h @@ -179,6 +179,7 @@ struct lock_file { extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); +extern int delete_ref(const char *, unsigned char *sha1); /* Environment bits from configuration mechanism */ extern int use_legacy_headers; diff --git a/refs.c b/refs.c index 9a1bc0db59..3d4cdd1eb9 100644 --- a/refs.c +++ b/refs.c @@ -378,6 +378,32 @@ int get_ref_sha1(const char *ref, unsigned char *sha1) return read_ref(mkpath("refs/%s", ref), sha1); } +int delete_ref(const char *refname, unsigned char *sha1) +{ + struct ref_lock *lock; + int err, i, ret = 0; + + lock = lock_any_ref_for_update(refname, sha1); + if (!lock) + return 1; + i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; + err = unlink(lock->lk->filename); + if (err) { + ret = 1; + error("unlink(%s) failed: %s", + lock->lk->filename, strerror(errno)); + } + lock->lk->filename[i] = '.'; + + err = unlink(lock->log_file); + if (err && errno != ENOENT) + fprintf(stderr, "warning: unlink(%s) failed: %s", + lock->log_file, strerror(errno)); + + return ret; +} + /* * Make sure "ref" is something reasonable to have under ".git/refs/"; * We do not like it if: From cede7526534c47436de17977eb39e55aa8c1e646 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Sep 2006 02:06:31 -0700 Subject: [PATCH 3/4] update a few Porcelain-ish for ref lock safety. This updates the use of git-update-ref in git-branch, git-tag and git-commit to make them safer in a few corner cases as demonstration. - git-tag makes sure that the named tag does not exist, allows you to edit tag message and then creates the tag. If a tag with the same name was created by somebody else in the meantime, it used to happily overwrote it. Now it notices the situation. - git-branch -d and git-commit (for the initial commit) had the same issue but with smaller race window, which is plugged with this. Signed-off-by: Junio C Hamano --- git-branch.sh | 7 ++++--- git-commit.sh | 6 +++--- git-tag.sh | 9 ++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/git-branch.sh b/git-branch.sh index 2600e9c4a8..4379a07210 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -42,8 +42,7 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'." esac ;; esac - rm -f "$GIT_DIR/logs/refs/heads/$branch_name" - rm -f "$GIT_DIR/refs/heads/$branch_name" + git update-ref -d "refs/heads/$branch_name" "$branch" echo "Deleted branch $branch_name." done exit 0 @@ -112,6 +111,7 @@ rev=$(git-rev-parse --verify "$head") || exit git-check-ref-format "heads/$branchname" || die "we do not like '$branchname' as a branch name." +prev='' if git-show-ref --verify --quiet -- "refs/heads/$branchname" then if test '' = "$force" @@ -121,10 +121,11 @@ then then die "cannot force-update the current branch." fi + prev=`git rev-parse --verify "refs/heads/$branchname"` fi if test "$create_log" = 'yes' then mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname") touch "$GIT_DIR/logs/refs/heads/$branchname" fi -git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev +git update-ref -m "branch: Created from $head" "refs/heads/$branchname" "$rev" "$prev" diff --git a/git-commit.sh b/git-commit.sh index 5a4c659b6f..ee5a165e74 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -547,15 +547,15 @@ then PARENTS=$(git-cat-file commit HEAD | sed -n -e '/^$/q' -e 's/^parent /-p /p') fi - current=$(git-rev-parse --verify HEAD) + current="$(git-rev-parse --verify HEAD)" else if [ -z "$(git-ls-files)" ]; then echo >&2 Nothing to commit exit 1 fi PARENTS="" - current= rloga='commit (initial)' + current='' fi if test -z "$no_edit" @@ -631,7 +631,7 @@ then fi && commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && - git-update-ref -m "$rloga: $rlogm" HEAD $commit $current && + git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" && rm -f -- "$GIT_DIR/MERGE_HEAD" && if test -f "$NEXT_INDEX" then diff --git a/git-tag.sh b/git-tag.sh index a0afa25821..2bde3c05fc 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -63,8 +63,11 @@ done name="$1" [ "$name" ] || usage -if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then - die "tag '$name' already exists" +prev=0000000000000000000000000000000000000000 +if test -e "$GIT_DIR/refs/tags/$name" +then + test -n "$force" || die "tag '$name' already exists" + prev=`git rev-parse "refs/tags/$name"` fi shift git-check-ref-format "tags/$name" || @@ -109,4 +112,4 @@ fi leading=`expr "refs/tags/$name" : '\(.*\)/'` && mkdir -p "$GIT_DIR/$leading" && -echo $object > "$GIT_DIR/refs/tags/$name" +GIT_DIR="$GIT_DIR" git update-ref "refs/tags/$name" "$object" "$prev" From 3159c8dc2da4c88dcecabb95404fe88edc5441e4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Sep 2006 02:40:06 -0700 Subject: [PATCH 4/4] Teach receive-pack about ref-log This converts receive-pack to use the standard ref locking code instead of its own. As a side effect, it automatically records the "push" event to ref-log if enabled. Signed-off-by: Junio C Hamano --- receive-pack.c | 88 +++++++++----------------------------------------- 1 file changed, 15 insertions(+), 73 deletions(-) diff --git a/receive-pack.c b/receive-pack.c index abbcb6af0b..f0b4cb47c7 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -41,34 +41,6 @@ struct command { static struct command *commands; -static int is_all_zeroes(const char *hex) -{ - int i; - for (i = 0; i < 40; i++) - if (*hex++ != '0') - return 0; - return 1; -} - -static int verify_old_ref(const char *name, char *hex_contents) -{ - int fd, ret; - char buffer[60]; - - if (is_all_zeroes(hex_contents)) - return 0; - fd = open(name, O_RDONLY); - if (fd < 0) - return -1; - ret = read(fd, buffer, 40); - close(fd); - if (ret != 40) - return -1; - if (memcmp(buffer, hex_contents, 40)) - return -1; - return 0; -} - static char update_hook[] = "hooks/update"; static int run_update_hook(const char *refname, @@ -105,8 +77,8 @@ static int update(struct command *cmd) const char *name = cmd->ref_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; - char new_hex[60], *old_hex, *lock_name; - int newfd, namelen, written; + char new_hex[41], old_hex[41]; + struct ref_lock *lock; cmd->error_string = NULL; if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) { @@ -115,59 +87,27 @@ static int update(struct command *cmd) name); } - namelen = strlen(name); - lock_name = xmalloc(namelen + 10); - memcpy(lock_name, name, namelen); - memcpy(lock_name + namelen, ".lock", 6); - strcpy(new_hex, sha1_to_hex(new_sha1)); - old_hex = sha1_to_hex(old_sha1); + strcpy(old_hex, sha1_to_hex(old_sha1)); if (!has_sha1_file(new_sha1)) { cmd->error_string = "bad pack"; return error("unpack should have generated %s, " "but I can't find it!", new_hex); } - safe_create_leading_directories(lock_name); - - newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (newfd < 0) { - cmd->error_string = "can't lock"; - return error("unable to create %s (%s)", - lock_name, strerror(errno)); - } - - /* Write the ref with an ending '\n' */ - new_hex[40] = '\n'; - new_hex[41] = 0; - written = write(newfd, new_hex, 41); - /* Remove the '\n' again */ - new_hex[40] = 0; - - close(newfd); - if (written != 41) { - unlink(lock_name); - cmd->error_string = "can't write"; - return error("unable to write %s", lock_name); - } - if (verify_old_ref(name, old_hex) < 0) { - unlink(lock_name); - cmd->error_string = "raced"; - return error("%s changed during push", name); - } if (run_update_hook(name, old_hex, new_hex)) { - unlink(lock_name); cmd->error_string = "hook declined"; return error("hook declined to update %s", name); } - else if (rename(lock_name, name) < 0) { - unlink(lock_name); - cmd->error_string = "can't rename"; - return error("unable to replace %s", name); - } - else { - fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); - return 0; + + lock = lock_any_ref_for_update(name, old_sha1); + if (!lock) { + cmd->error_string = "failed to lock"; + return error("failed to lock %s", name); } + write_ref_sha1(lock, new_sha1, "push"); + + fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); + return 0; } static char update_post_hook[] = "hooks/post-update"; @@ -318,9 +258,11 @@ int main(int argc, char **argv) if (!dir) usage(receive_pack_usage); - if(!enter_repo(dir, 0)) + if (!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); + git_config(git_default_config); + write_head_info(); /* EOF */