From 2b2b1e4d27b4e44c0c46d4857c76b8391d303af3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Wed, 10 Sep 2014 14:01:46 -0700 Subject: [PATCH 01/25] mv test: recreate mod/ directory instead of relying on stale copy The tests for 'git mv moves a submodule' functionality often run commands like git mv sub mod/sub to move a submodule into a subdirectory. Just like plain /bin/mv, this is supposed to succeed if the mod/ parent directory exists and fail if it doesn't exist. Usually these tests mkdir the parent directory beforehand, but some instead rely on it being left behind by previous tests. More precisely, when 'git reset --hard' tries to move to a state where mod/sub is not present any more, it would perform the following operations: rmdir("mod/sub") rmdir("mod") The first fails with ENOENT because the test script removed mod/sub with "rm -rf" already, so 'reset --hard' doesn't bother to move on to the second, and the mod/ directory is kept around. Better to explicitly remove and re-create the mod/ directory so later tests don't have to depend on the directory left behind by the earlier ones at all (making it easier to rearrange or skip some tests in the file or to tweak 'reset --hard' behavior without breaking unrelated tests). Noticed while testing a patch that fixes the reset --hard behavior described above. Signed-off-by: Jonathan Nieder Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- t/t7001-mv.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 54d78079e8..69f11bd40d 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -350,10 +350,11 @@ test_expect_success 'git mv moves a submodule with a .git directory and .gitmodu ' test_expect_success 'git mv moves a submodule with gitfile' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && entry="$(git ls-files --stage sub | cut -f 1)" && + mkdir mod && ( cd mod && git mv ../sub/ . @@ -372,11 +373,12 @@ test_expect_success 'git mv moves a submodule with gitfile' ' ' test_expect_success 'mv does not complain when no .gitmodules file is found' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && git rm .gitmodules && entry="$(git ls-files --stage sub | cut -f 1)" && + mkdir mod && git mv sub mod/sub 2>actual.err && ! test -s actual.err && ! test -e sub && @@ -390,11 +392,12 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' ' ' test_expect_success 'mv will error out on a modified .gitmodules file unless staged' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && git config -f .gitmodules foo.bar true && entry="$(git ls-files --stage sub | cut -f 1)" && + mkdir mod && test_must_fail git mv sub mod/sub 2>actual.err && test -s actual.err && test -e sub && @@ -413,13 +416,14 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta ' test_expect_success 'mv issues a warning when section is not found in .gitmodules' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && git config -f .gitmodules --remove-section submodule.sub && git add .gitmodules && entry="$(git ls-files --stage sub | cut -f 1)" && echo "warning: Could not find section in .gitmodules where path=sub" >expect.err && + mkdir mod && git mv sub mod/sub 2>actual.err && test_i18ncmp expect.err actual.err && ! test -e sub && @@ -433,9 +437,10 @@ test_expect_success 'mv issues a warning when section is not found in .gitmodule ' test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && + mkdir mod && git mv -n sub mod/sub 2>actual.err && test -f sub/.git && git diff-index --exit-code HEAD && From 1054af7d04aef64378d69a0496b45cdbf6a0bef2 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 16 Jul 2014 11:01:18 -0700 Subject: [PATCH 02/25] wrapper.c: remove/unlink_or_warn: simplify, treat ENOENT as success Simplify the function warn_if_unremovable slightly. Additionally, change behaviour slightly. If we failed to remove the object because the object does not exist, we can still return success back to the caller since none of the callers depend on "fail if the file did not exist". Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- git-compat-util.h | 7 +++++-- refs.c | 2 +- wrapper.c | 14 ++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/git-compat-util.h b/git-compat-util.h index fb41118c07..d67592fd96 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -777,11 +777,14 @@ void git_qsort(void *base, size_t nmemb, size_t size, /* * Preserves errno, prints a message, but gives no warning for ENOENT. - * Always returns the return value of unlink(2). + * Returns 0 on success, which includes trying to unlink an object that does + * not exist. */ int unlink_or_warn(const char *path); /* - * Likewise for rmdir(2). + * Preserves errno, prints a message, but gives no warning for ENOENT. + * Returns 0 on success, which includes trying to remove a directory that does + * not exist. */ int rmdir_or_warn(const char *path); /* diff --git a/refs.c b/refs.c index a77458f2f6..2dcf6c6e03 100644 --- a/refs.c +++ b/refs.c @@ -2607,7 +2607,7 @@ static int delete_ref_loose(struct ref_lock *lock, int flag) char *loose_filename = get_locked_file_path(lock->lk); int err = unlink_or_warn(loose_filename); free(loose_filename); - if (err && errno != ENOENT) + if (err) return 1; } return 0; diff --git a/wrapper.c b/wrapper.c index 5b77d2a1ae..8d4be66e66 100644 --- a/wrapper.c +++ b/wrapper.c @@ -466,14 +466,12 @@ int xmkstemp_mode(char *template, int mode) static int warn_if_unremovable(const char *op, const char *file, int rc) { - if (rc < 0) { - int err = errno; - if (ENOENT != err) { - warning("unable to %s %s: %s", - op, file, strerror(errno)); - errno = err; - } - } + int err; + if (!rc || errno == ENOENT) + return 0; + err = errno; + warning("unable to %s %s: %s", op, file, strerror(errno)); + errno = err; return rc; } From 3c93c847cacea68e520f6099b18c69f406ab9407 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 2 Oct 2014 07:59:02 -0700 Subject: [PATCH 03/25] refs.c: lock_ref_sha1_basic is used for all refs lock_ref_sha1_basic is used to lock refs that sit directly in the .git dir such as HEAD and MERGE_HEAD in addition to the more ordinary refs under "refs/". Remove the note claiming otherwise. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder 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 2dcf6c6e03..4f2564dffa 100644 --- a/refs.c +++ b/refs.c @@ -2134,7 +2134,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) } /* - * Locks a "refs/" ref returning the lock on success and NULL on failure. + * Locks a ref returning the lock on success and NULL on failure. * On failure errno is set to something meaningful. */ static struct ref_lock *lock_ref_sha1_basic(const char *refname, From 9ccc0c089667e6dccc888590376f138521784e5e Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 16 Jul 2014 11:20:36 -0700 Subject: [PATCH 04/25] wrapper.c: add a new function unlink_or_msg This behaves like unlink_or_warn except that on failure it writes the message to its 'err' argument, which the caller can display in an appropriate way or ignore. Signed-off-by: Ronnie Sahlberg Reviewed-by: Michael Haggerty Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- git-compat-util.h | 9 +++++++++ wrapper.c | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index d67592fd96..59ecf218cb 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -326,6 +326,8 @@ static inline char *git_find_last_dir_sep(const char *path) #include "wildmatch.h" +struct strbuf; + /* General helper functions */ extern void vreportf(const char *prefix, const char *err, va_list params); extern void vwritef(int fd, const char *prefix, const char *err, va_list params); @@ -781,6 +783,13 @@ void git_qsort(void *base, size_t nmemb, size_t size, * not exist. */ int unlink_or_warn(const char *path); + /* + * Tries to unlink file. Returns 0 if unlink succeeded + * or the file already didn't exist. Returns -1 and + * appends a message to err suitable for + * 'error("%s", err->buf)' on error. + */ +int unlink_or_msg(const char *file, struct strbuf *err); /* * Preserves errno, prints a message, but gives no warning for ENOENT. * Returns 0 on success, which includes trying to remove a directory that does diff --git a/wrapper.c b/wrapper.c index 8d4be66e66..007ec0d8ea 100644 --- a/wrapper.c +++ b/wrapper.c @@ -475,6 +475,20 @@ static int warn_if_unremovable(const char *op, const char *file, int rc) return rc; } +int unlink_or_msg(const char *file, struct strbuf *err) +{ + int rc = unlink(file); + + assert(err); + + if (!rc || errno == ENOENT) + return 0; + + strbuf_addf(err, "unable to unlink %s: %s", + file, strerror(errno)); + return -1; +} + int unlink_or_warn(const char *file) { return warn_if_unremovable("unlink", file, unlink(file)); From dbdcac7d5c5eea314ec4c318a5e88cff427e9f0e Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 15 May 2014 08:25:23 -0700 Subject: [PATCH 05/25] refs.c: add an err argument to delete_ref_loose Add an err argument to delete_ref_loose so that we can pass a descriptive error string back to the caller. Pass the err argument from transaction commit to this function so that transaction users will have a nice error string if the transaction failed due to delete_ref_loose. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- refs.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/refs.c b/refs.c index 4f2564dffa..430857b475 100644 --- a/refs.c +++ b/refs.c @@ -2597,7 +2597,7 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) return ret; } -static int delete_ref_loose(struct ref_lock *lock, int flag) +static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) { if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { /* @@ -2605,9 +2605,9 @@ static int delete_ref_loose(struct ref_lock *lock, int flag) * lockfile name, minus ".lock": */ char *loose_filename = get_locked_file_path(lock->lk); - int err = unlink_or_warn(loose_filename); + int res = unlink_or_msg(loose_filename, err); free(loose_filename); - if (err) + if (res) return 1; } return 0; @@ -3658,7 +3658,8 @@ int ref_transaction_commit(struct ref_transaction *transaction, struct ref_update *update = updates[i]; if (update->lock) { - ret |= delete_ref_loose(update->lock, update->type); + ret |= delete_ref_loose(update->lock, update->type, + err); if (!(update->flags & REF_ISPRUNING)) delnames[delnum++] = update->lock->ref_name; } From db7516ab9f435e3ce86b257c6631fb4d2dfb12ae Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 30 Apr 2014 12:22:42 -0700 Subject: [PATCH 06/25] refs.c: pass the ref log message to _create/delete/update instead of _commit Change the ref transaction API so that we pass the reflog message to the create/delete/update functions instead of to ref_transaction_commit. This allows different reflog messages for each ref update in a multi-ref transaction. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- branch.c | 4 ++-- builtin/commit.c | 4 ++-- builtin/receive-pack.c | 5 +++-- builtin/replace.c | 5 +++-- builtin/tag.c | 4 ++-- builtin/update-ref.c | 13 +++++++------ fast-import.c | 8 ++++---- refs.c | 34 +++++++++++++++++++++------------- refs.h | 12 ++++++------ sequencer.c | 4 ++-- walker.c | 5 ++--- 11 files changed, 54 insertions(+), 44 deletions(-) diff --git a/branch.c b/branch.c index 9a2228ebb4..884c62c287 100644 --- a/branch.c +++ b/branch.c @@ -285,8 +285,8 @@ void create_branch(const char *head, transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, sha1, - null_sha1, 0, !forcing, &err) || - ref_transaction_commit(transaction, msg, &err)) + null_sha1, 0, !forcing, msg, &err) || + ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&err); diff --git a/builtin/commit.c b/builtin/commit.c index 81dc622a3b..a7857ab560 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1811,8 +1811,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) ref_transaction_update(transaction, "HEAD", sha1, current_head ? current_head->object.sha1 : NULL, - 0, !!current_head, &err) || - ref_transaction_commit(transaction, sb.buf, &err)) { + 0, !!current_head, sb.buf, &err) || + ref_transaction_commit(transaction, &err)) { rollback_index_files(); die("%s", err.buf); } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index f2f6c67359..df6c337d69 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -842,8 +842,9 @@ static const char *update(struct command *cmd, struct shallow_info *si) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, namespaced_name, - new_sha1, old_sha1, 0, 1, &err) || - ref_transaction_commit(transaction, "push", &err)) { + new_sha1, old_sha1, 0, 1, "push", + &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); rp_error("%s", err.buf); diff --git a/builtin/replace.c b/builtin/replace.c index 8020db8500..85d39b58d8 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -171,8 +171,9 @@ static int replace_object_sha1(const char *object_ref, transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) || - ref_transaction_commit(transaction, NULL, &err)) + ref_transaction_update(transaction, ref, repl, prev, + 0, 1, NULL, &err) || + ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/tag.c b/builtin/tag.c index a81b9e4174..e633f4efdb 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -733,8 +733,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, - 0, 1, &err) || - ref_transaction_commit(transaction, NULL, &err)) + 0, 1, NULL, &err) || + ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); if (force && !is_null_sha1(prev) && hashcmp(prev, object)) diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 54a48c0cfa..6c9be05128 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = { static char line_termination = '\n'; static int update_flags; +static const char *msg; /* * Parse one whitespace- or NUL-terminated, possibly C-quoted argument @@ -198,7 +199,7 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, die("update %s: extra input: %s", refname, next); if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, have_old, &err)) + update_flags, have_old, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -229,7 +230,7 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, die("create %s: extra input: %s", refname, next); if (ref_transaction_create(transaction, refname, new_sha1, - update_flags, &err)) + update_flags, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -264,7 +265,7 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, die("delete %s: extra input: %s", refname, next); if (ref_transaction_delete(transaction, refname, old_sha1, - update_flags, have_old, &err)) + update_flags, have_old, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -300,7 +301,7 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, die("verify %s: extra input: %s", refname, next); if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, have_old, &err)) + update_flags, have_old, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -354,7 +355,7 @@ static void update_refs_stdin(struct ref_transaction *transaction) int cmd_update_ref(int argc, const char **argv, const char *prefix) { - const char *refname, *oldval, *msg = NULL; + const char *refname, *oldval; unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; struct option options[] = { @@ -385,7 +386,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (end_null) line_termination = '\0'; update_refs_stdin(transaction); - if (ref_transaction_commit(transaction, msg, &err)) + if (ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&err); diff --git a/fast-import.c b/fast-import.c index fee7906e51..d0bd285a16 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1716,8 +1716,8 @@ static int update_branch(struct branch *b) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, b->name, b->sha1, old_sha1, - 0, 1, &err) || - ref_transaction_commit(transaction, msg, &err)) { + 0, 1, msg, &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); strbuf_release(&err); @@ -1757,12 +1757,12 @@ static void dump_tags(void) strbuf_addf(&ref_name, "refs/tags/%s", t->name); if (ref_transaction_update(transaction, ref_name.buf, t->sha1, - NULL, 0, 0, &err)) { + NULL, 0, 0, msg, &err)) { failure |= error("%s", err.buf); goto cleanup; } } - if (ref_transaction_commit(transaction, msg, &err)) + if (ref_transaction_commit(transaction, &err)) failure |= error("%s", err.buf); cleanup: diff --git a/refs.c b/refs.c index 430857b475..f5d7bd79f2 100644 --- a/refs.c +++ b/refs.c @@ -2445,8 +2445,8 @@ static void prune_ref(struct ref_to_prune *r) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, r->name, r->sha1, - REF_ISPRUNING, 1, &err) || - ref_transaction_commit(transaction, NULL, &err)) { + REF_ISPRUNING, 1, NULL, &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); strbuf_release(&err); @@ -2621,8 +2621,8 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, refname, sha1, delopt, - sha1 && !is_null_sha1(sha1), &err) || - ref_transaction_commit(transaction, NULL, &err)) { + sha1 && !is_null_sha1(sha1), NULL, &err) || + ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&err); @@ -3404,6 +3404,7 @@ struct ref_update { int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ struct ref_lock *lock; int type; + char *msg; const char refname[FLEX_ARRAY]; }; @@ -3446,9 +3447,10 @@ void ref_transaction_free(struct ref_transaction *transaction) if (!transaction) return; - for (i = 0; i < transaction->nr; i++) + for (i = 0; i < transaction->nr; i++) { + free(transaction->updates[i]->msg); free(transaction->updates[i]); - + } free(transaction->updates); free(transaction); } @@ -3469,7 +3471,7 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err) { struct ref_update *update; @@ -3486,13 +3488,15 @@ int ref_transaction_update(struct ref_transaction *transaction, update->have_old = have_old; if (have_old) hashcpy(update->old_sha1, old_sha1); + if (msg) + update->msg = xstrdup(msg); return 0; } int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, - int flags, + int flags, const char *msg, struct strbuf *err) { struct ref_update *update; @@ -3509,13 +3513,15 @@ int ref_transaction_create(struct ref_transaction *transaction, hashclr(update->old_sha1); update->flags = flags; update->have_old = 1; + if (msg) + update->msg = xstrdup(msg); return 0; } int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err) { struct ref_update *update; @@ -3533,6 +3539,8 @@ int ref_transaction_delete(struct ref_transaction *transaction, assert(!is_null_sha1(old_sha1)); hashcpy(update->old_sha1, old_sha1); } + if (msg) + update->msg = xstrdup(msg); return 0; } @@ -3546,8 +3554,8 @@ int update_ref(const char *action, const char *refname, t = ref_transaction_begin(&err); if (!t || ref_transaction_update(t, refname, sha1, oldval, flags, - !!oldval, &err) || - ref_transaction_commit(t, action, &err)) { + !!oldval, action, &err) || + ref_transaction_commit(t, &err)) { const char *str = "update_ref failed for ref '%s': %s"; ref_transaction_free(t); @@ -3593,7 +3601,7 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n, } int ref_transaction_commit(struct ref_transaction *transaction, - const char *msg, struct strbuf *err) + struct strbuf *err) { int ret = 0, delnum = 0, i; const char **delnames; @@ -3642,7 +3650,7 @@ int ref_transaction_commit(struct ref_transaction *transaction, if (!is_null_sha1(update->new_sha1)) { ret = write_ref_sha1(update->lock, update->new_sha1, - msg); + update->msg); update->lock = NULL; /* freed by write_ref_sha1 */ if (ret) { if (err) diff --git a/refs.h b/refs.h index 2328f06e77..1a55cabb40 100644 --- a/refs.h +++ b/refs.h @@ -274,8 +274,8 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err); * The following functions add a reference check or update to a * ref_transaction. In all of them, refname is the name of the * reference to be affected. The functions make internal copies of - * refname, so the caller retains ownership of the parameter. flags - * can be REF_NODEREF; it is passed to update_ref_lock(). + * refname and msg, so the caller retains ownership of these parameters. + * flags can be REF_NODEREF; it is passed to update_ref_lock(). */ /* @@ -292,7 +292,7 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err); /* @@ -307,7 +307,7 @@ int ref_transaction_update(struct ref_transaction *transaction, int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, - int flags, + int flags, const char *msg, struct strbuf *err); /* @@ -321,7 +321,7 @@ int ref_transaction_create(struct ref_transaction *transaction, int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err); /* @@ -330,7 +330,7 @@ int ref_transaction_delete(struct ref_transaction *transaction, * problem. */ int ref_transaction_commit(struct ref_transaction *transaction, - const char *msg, struct strbuf *err); + struct strbuf *err); /* * Free an existing transaction and all associated data. diff --git a/sequencer.c b/sequencer.c index 1b9a35e587..47aac754bc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -252,8 +252,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, if (!transaction || ref_transaction_update(transaction, "HEAD", to, unborn ? null_sha1 : from, - 0, 1, &err) || - ref_transaction_commit(transaction, sb.buf, &err)) { + 0, 1, sb.buf, &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); strbuf_release(&sb); diff --git a/walker.c b/walker.c index 18a67d33cb..f149371e71 100644 --- a/walker.c +++ b/walker.c @@ -300,14 +300,13 @@ int walker_fetch(struct walker *walker, int targets, char **target, strbuf_addf(&refname, "refs/%s", write_ref[i]); if (ref_transaction_update(transaction, refname.buf, &sha1[20 * i], NULL, 0, 0, + msg ? msg : "fetch (unknown)", &err)) { error("%s", err.buf); goto done; } } - if (ref_transaction_commit(transaction, - msg ? msg : "fetch (unknown)", - &err)) { + if (ref_transaction_commit(transaction, &err)) { error("%s", err.buf); goto done; } From 7522e3dbcc3946d481e6b6ac59037b884cc0a2a6 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 30 Apr 2014 12:41:04 -0700 Subject: [PATCH 07/25] rename_ref: don't ask read_ref_full where the ref came from We call read_ref_full with a pointer to flags from rename_ref but since we never actually use the returned flags we can just pass NULL here instead. Signed-off-by: Ronnie Sahlberg Reviewed-by: Michael Haggerty Signed-off-by: Jonathan Nieder 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 f5d7bd79f2..3c45615c22 100644 --- a/refs.c +++ b/refs.c @@ -2721,7 +2721,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms goto rollback; } - if (!read_ref_full(newrefname, sha1, 1, &flag) && + if (!read_ref_full(newrefname, sha1, 1, NULL) && delete_ref(newrefname, sha1, REF_NODEREF)) { if (errno==EISDIR) { if (remove_empty_directories(git_path("%s", newrefname))) { From 8a9df90d9a726fa9b1a1ebd13b9e43409e18c606 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 1 May 2014 10:40:10 -0700 Subject: [PATCH 08/25] refs.c: refuse to lock badly named refs in lock_ref_sha1_basic Move the check for check_refname_format from lock_any_ref_for_update to lock_ref_sha1_basic. At some later stage we will get rid of lock_any_ref_for_update completely. This has no visible impact to callers except for the inability to lock badly named refs, which is not possible today already for other reasons.(*) Keep lock_any_ref_for_update as a no-op wrapper. It is the public facing version of this interface and keeping it as a separate function will make it easier to experiment with the internal lock_ref_sha1_basic signature. (*) For example, if lock_ref_sha1_basic checks the refname format and refuses to lock badly named refs, it will not be possible to delete such refs because the first step of deletion is to lock the ref. We currently already fail in that case because these refs are not recognized to exist: $ cp .git/refs/heads/master .git/refs/heads/echo...\*\* $ git branch -D .git/refs/heads/echo...\*\* error: branch '.git/refs/heads/echo...**' not found. This has been broken for a while. Later patches in the series will start repairing the handling of badly named refs. Signed-off-by: Ronnie Sahlberg Reviewed-by: Michael Haggerty Signed-off-by: Jonathan Nieder 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 3c45615c22..9c0162378f 100644 --- a/refs.c +++ b/refs.c @@ -2150,6 +2150,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, int missing = 0; int attempts_remaining = 3; + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + errno = EINVAL; + return NULL; + } + lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; @@ -2241,8 +2246,6 @@ struct ref_lock *lock_any_ref_for_update(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) { - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) - return NULL; return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); } From 5d94a1b03318d14847a40f233560c2a87fb16cfa Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 1 May 2014 10:43:39 -0700 Subject: [PATCH 09/25] refs.c: call lock_ref_sha1_basic directly from commit Skip using the lock_any_ref_for_update wrapper and call lock_ref_sha1_basic directly from the commit function. Signed-off-by: Ronnie Sahlberg Reviewed-by: Michael Haggerty Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- refs.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/refs.c b/refs.c index 9c0162378f..b591b9c321 100644 --- a/refs.c +++ b/refs.c @@ -3632,12 +3632,12 @@ int ref_transaction_commit(struct ref_transaction *transaction, for (i = 0; i < n; i++) { struct ref_update *update = updates[i]; - update->lock = lock_any_ref_for_update(update->refname, - (update->have_old ? - update->old_sha1 : - NULL), - update->flags, - &update->type); + update->lock = lock_ref_sha1_basic(update->refname, + (update->have_old ? + update->old_sha1 : + NULL), + update->flags, + &update->type); if (!update->lock) { if (err) strbuf_addf(err, "Cannot lock the ref '%s'.", From 5fe7d825da8af83a9a3b9bc7f3295f836b6e3a75 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 1 May 2014 11:16:07 -0700 Subject: [PATCH 10/25] refs.c: pass a list of names to skip to is_refname_available Change is_refname_available to take a list of strings to exclude when checking for conflicts instead of just one single name. We can already exclude a single name for the sake of renames. This generalizes that support. ref_transaction_commit already tracks a set of refs that are being deleted in an array. This array is then used to exclude refs from being written to the packed-refs file. At some stage we will want to change this array to a struct string_list and then we can pass it to is_refname_available via the call to lock_ref_sha1_basic. That will allow us to perform transactions that perform multiple renames as long as there are no conflicts within the starting or ending state. For example, that would allow a single transaction that contains two renames that are both individually conflicting: m -> n/n n -> m/m No functional change intended yet. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- refs.c | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/refs.c b/refs.c index b591b9c321..a007cf3257 100644 --- a/refs.c +++ b/refs.c @@ -787,13 +787,13 @@ static void prime_ref_dir(struct ref_dir *dir) } } -static int entry_matches(struct ref_entry *entry, const char *refname) +static int entry_matches(struct ref_entry *entry, const struct string_list *list) { - return refname && !strcmp(entry->name, refname); + return list && string_list_has_string(list, entry->name); } struct nonmatching_ref_data { - const char *skip; + const struct string_list *skip; struct ref_entry *found; }; @@ -817,16 +817,19 @@ static void report_refname_conflict(struct ref_entry *entry, /* * Return true iff a reference named refname could be created without * conflicting with the name of an existing reference in dir. If - * oldrefname is non-NULL, ignore potential conflicts with oldrefname - * (e.g., because oldrefname is scheduled for deletion in the same + * skip is non-NULL, ignore potential conflicts with refs in skip + * (e.g., because they are scheduled for deletion in the same * operation). * * Two reference names conflict if one of them exactly matches the * leading components of the other; e.g., "foo/bar" conflicts with * both "foo" and with "foo/bar/baz" but not with "foo/bar" or * "foo/barbados". + * + * skip must be sorted. */ -static int is_refname_available(const char *refname, const char *oldrefname, +static int is_refname_available(const char *refname, + const struct string_list *skip, struct ref_dir *dir) { const char *slash; @@ -840,12 +843,12 @@ static int is_refname_available(const char *refname, const char *oldrefname, * looking for a conflict with a leaf entry. * * If we find one, we still must make sure it is - * not "oldrefname". + * not in "skip". */ pos = search_ref_dir(dir, refname, slash - refname); if (pos >= 0) { struct ref_entry *entry = dir->entries[pos]; - if (entry_matches(entry, oldrefname)) + if (entry_matches(entry, skip)) return 1; report_refname_conflict(entry, refname); return 0; @@ -878,13 +881,13 @@ static int is_refname_available(const char *refname, const char *oldrefname, /* * We found a directory named "refname". It is a * problem iff it contains any ref that is not - * "oldrefname". + * in "skip". */ struct ref_entry *entry = dir->entries[pos]; struct ref_dir *dir = get_ref_dir(entry); struct nonmatching_ref_data data; - data.skip = oldrefname; + data.skip = skip; sort_ref_dir(dir); if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) return 1; @@ -2139,6 +2142,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) */ static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, + const struct string_list *skip, int flags, int *type_p) { char *ref_file; @@ -2188,7 +2192,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, * name is a proper prefix of our refname. */ if (missing && - !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) { + !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) { last_errno = ENOTDIR; goto error_return; } @@ -2246,7 +2250,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) { - return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); + return lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p); } /* @@ -2690,6 +2694,18 @@ static int rename_tmp_log(const char *newrefname) return 0; } +static int rename_ref_available(const char *oldname, const char *newname) +{ + struct string_list skip = STRING_LIST_INIT_NODUP; + int ret; + + string_list_insert(&skip, oldname); + ret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache)) + && is_refname_available(newname, &skip, get_loose_refs(&ref_cache)); + string_list_clear(&skip, 0); + return ret; +} + int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) { unsigned char sha1[20], orig_sha1[20]; @@ -2709,10 +2725,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms if (!symref) return error("refname %s not found", oldrefname); - if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache))) - return 1; - - if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache))) + if (!rename_ref_available(oldrefname, newrefname)) return 1; if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG))) @@ -2742,7 +2755,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms logmoved = log; - lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL); + lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL); if (!lock) { error("unable to lock %s for update", newrefname); goto rollback; @@ -2757,7 +2770,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms return 0; rollback: - lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL); + lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL); if (!lock) { error("unable to lock %s for rollback", oldrefname); goto rollbacklog; @@ -3636,6 +3649,7 @@ int ref_transaction_commit(struct ref_transaction *transaction, (update->have_old ? update->old_sha1 : NULL), + NULL, update->flags, &update->type); if (!update->lock) { From 28e6a97e39edb84599693db971e36d793d36e413 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Fri, 16 May 2014 14:14:38 -0700 Subject: [PATCH 11/25] refs.c: ref_transaction_commit: distinguish name conflicts from other errors In _commit, ENOTDIR can happen in the call to lock_ref_sha1_basic, either when we lstat the new refname or if the name checking function reports that the same type of conflict happened. In both cases, it means that we can not create the new ref due to a name conflict. Start defining specific return codes for _commit. TRANSACTION_NAME_CONFLICT refers to a failure to create a ref due to a name conflict with another ref. TRANSACTION_GENERIC_ERROR is for all other errors. When "git fetch" is creating refs, name conflicts differ from other errors in that they are likely to be resolved by running "git remote prune ". "git fetch" currently inspects errno to decide whether to give that advice. Once it switches to the transaction API, it can check for TRANSACTION_NAME_CONFLICT instead. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- refs.c | 26 ++++++++++++++++---------- refs.h | 9 +++++++-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/refs.c b/refs.c index a007cf3257..9d9bbebfb9 100644 --- a/refs.c +++ b/refs.c @@ -3637,9 +3637,10 @@ int ref_transaction_commit(struct ref_transaction *transaction, /* Copy, sort, and reject duplicate refs */ qsort(updates, n, sizeof(*updates), ref_update_compare); - ret = ref_update_reject_duplicates(updates, n, err); - if (ret) + if (ref_update_reject_duplicates(updates, n, err)) { + ret = TRANSACTION_GENERIC_ERROR; goto cleanup; + } /* Acquire all locks while verifying old values */ for (i = 0; i < n; i++) { @@ -3653,10 +3654,12 @@ int ref_transaction_commit(struct ref_transaction *transaction, update->flags, &update->type); if (!update->lock) { + ret = (errno == ENOTDIR) + ? TRANSACTION_NAME_CONFLICT + : TRANSACTION_GENERIC_ERROR; if (err) strbuf_addf(err, "Cannot lock the ref '%s'.", update->refname); - ret = 1; goto cleanup; } } @@ -3666,15 +3669,16 @@ int ref_transaction_commit(struct ref_transaction *transaction, struct ref_update *update = updates[i]; if (!is_null_sha1(update->new_sha1)) { - ret = write_ref_sha1(update->lock, update->new_sha1, - update->msg); - update->lock = NULL; /* freed by write_ref_sha1 */ - if (ret) { + if (write_ref_sha1(update->lock, update->new_sha1, + update->msg)) { + update->lock = NULL; /* freed by write_ref_sha1 */ if (err) strbuf_addf(err, "Cannot update the ref '%s'.", update->refname); + ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } + update->lock = NULL; /* freed by write_ref_sha1 */ } } @@ -3683,14 +3687,16 @@ int ref_transaction_commit(struct ref_transaction *transaction, struct ref_update *update = updates[i]; if (update->lock) { - ret |= delete_ref_loose(update->lock, update->type, - err); + if (delete_ref_loose(update->lock, update->type, err)) + ret = TRANSACTION_GENERIC_ERROR; + if (!(update->flags & REF_ISPRUNING)) delnames[delnum++] = update->lock->ref_name; } } - ret |= repack_without_refs(delnames, delnum, err); + if (repack_without_refs(delnames, delnum, err)) + ret = TRANSACTION_GENERIC_ERROR; for (i = 0; i < delnum; i++) unlink_or_warn(git_path("logs/%s", delnames[i])); clear_loose_ref_cache(&ref_cache); diff --git a/refs.h b/refs.h index 1a55cabb40..50b115acc1 100644 --- a/refs.h +++ b/refs.h @@ -326,9 +326,14 @@ int ref_transaction_delete(struct ref_transaction *transaction, /* * Commit all of the changes that have been queued in transaction, as - * atomically as possible. Return a nonzero value if there is a - * problem. + * atomically as possible. + * + * Returns 0 for success, or one of the below error codes for errors. */ +/* Naming conflict (for example, the ref names A and A/B conflict). */ +#define TRANSACTION_NAME_CONFLICT -1 +/* All other errors. */ +#define TRANSACTION_GENERIC_ERROR -2 int ref_transaction_commit(struct ref_transaction *transaction, struct strbuf *err); From cd94f765720e32ea0bf72996c8dc7dd0b1cc30d0 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Mon, 28 Apr 2014 13:49:07 -0700 Subject: [PATCH 12/25] fetch.c: change s_update_ref to use a ref transaction Change s_update_ref to use a ref transaction for the ref update. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/fetch.c | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/builtin/fetch.c b/builtin/fetch.c index 159fb7e916..6ffd02388b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -404,23 +404,37 @@ static int s_update_ref(const char *action, { char msg[1024]; char *rla = getenv("GIT_REFLOG_ACTION"); - static struct ref_lock *lock; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + int ret, df_conflict = 0; if (dry_run) return 0; if (!rla) rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); - lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, - 0, NULL); - if (!lock) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; - if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; + + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, ref->name, ref->new_sha1, + ref->old_sha1, 0, check_old, msg, &err)) + goto fail; + + ret = ref_transaction_commit(transaction, &err); + if (ret) { + df_conflict = (ret == TRANSACTION_NAME_CONFLICT); + goto fail; + } + + ref_transaction_free(transaction); + strbuf_release(&err); return 0; +fail: + ref_transaction_free(transaction); + error("%s", err.buf); + strbuf_release(&err); + return df_conflict ? STORE_REF_ERROR_DF_CONFLICT + : STORE_REF_ERROR_OTHER; } #define REFCOL_WIDTH 10 From aae383db8c384dc46abe199899235f67c838e601 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Mon, 28 Apr 2014 15:36:58 -0700 Subject: [PATCH 13/25] refs.c: make write_ref_sha1 static No external users call write_ref_sha1 any more so let's declare it static. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- refs.c | 10 ++++++++-- refs.h | 3 --- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/refs.c b/refs.c index 9d9bbebfb9..6b4236a67f 100644 --- a/refs.c +++ b/refs.c @@ -2706,6 +2706,9 @@ static int rename_ref_available(const char *oldname, const char *newname) return ret; } +static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, + const char *logmsg); + int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) { unsigned char sha1[20], orig_sha1[20]; @@ -2950,8 +2953,11 @@ int is_branch(const char *refname) return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); } -/* This function must return a meaningful errno */ -int write_ref_sha1(struct ref_lock *lock, +/* + * Write sha1 into the ref specified by the lock. Make sure that errno + * is sane on error. + */ +static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *logmsg) { static char term = '\n'; diff --git a/refs.h b/refs.h index 50b115acc1..eea104478f 100644 --- a/refs.h +++ b/refs.h @@ -197,9 +197,6 @@ extern int commit_ref(struct ref_lock *lock); /** Release any lock taken but not written. **/ extern void unlock_ref(struct ref_lock *lock); -/** Writes sha1 into the ref specified by the lock. **/ -extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); - /* * Setup reflog before using. Set errno to something meaningful on failure. */ From 7695d118e5a3c9c6fcb4cb15eb766a1c57422aed Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Tue, 15 Jul 2014 12:59:36 -0700 Subject: [PATCH 14/25] refs.c: change resolve_ref_unsafe reading argument to be a flags field resolve_ref_unsafe takes a boolean argument for reading (a nonexistent ref resolves successfully for writing but not for reading). Change this to be a flags field instead, and pass the new constant RESOLVE_REF_READING when we want this behaviour. While at it, swap two of the arguments in the function to put output arguments at the end. As a nice side effect, this ensures that we can catch callers that were unaware of the new API so they can be audited. Give the wrapper functions resolve_refdup and read_ref_full the same treatment for consistency. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- branch.c | 2 +- builtin/blame.c | 2 +- builtin/branch.c | 9 ++-- builtin/checkout.c | 6 +-- builtin/clone.c | 2 +- builtin/commit.c | 2 +- builtin/fmt-merge-msg.c | 2 +- builtin/for-each-ref.c | 6 ++- builtin/fsck.c | 2 +- builtin/log.c | 3 +- builtin/merge.c | 2 +- builtin/notes.c | 2 +- builtin/receive-pack.c | 4 +- builtin/remote.c | 5 ++- builtin/show-branch.c | 7 +++- builtin/symbolic-ref.c | 2 +- bundle.c | 2 +- cache.h | 23 +++++----- http-backend.c | 4 +- notes-merge.c | 2 +- reflog-walk.c | 5 ++- refs.c | 93 +++++++++++++++++++++++------------------ remote.c | 11 +++-- sequencer.c | 4 +- transport-helper.c | 5 ++- transport.c | 5 ++- upload-pack.c | 2 +- wt-status.c | 2 +- 28 files changed, 124 insertions(+), 92 deletions(-) diff --git a/branch.c b/branch.c index 884c62c287..4bab55a9a8 100644 --- a/branch.c +++ b/branch.c @@ -170,7 +170,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref, const char *head; unsigned char sha1[20]; - head = resolve_ref_unsafe("HEAD", sha1, 0, NULL); + head = resolve_ref_unsafe("HEAD", 0, sha1, NULL); if (!is_bare_repository() && head && !strcmp(head, ref->buf)) die(_("Cannot force update the current branch.")); } diff --git a/builtin/blame.c b/builtin/blame.c index 3838be2b02..303e217ae9 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2286,7 +2286,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, commit->date = now; parent_tail = &commit->parents; - if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) die("no such ref: HEAD"); parent_tail = append_parent(parent_tail, head_sha1); diff --git a/builtin/branch.c b/builtin/branch.c index 67850975e7..6491bacd69 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -131,7 +131,8 @@ static int branch_merged(int kind, const char *name, branch->merge[0] && branch->merge[0]->dst && (reference_name = reference_name_to_free = - resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) + resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING, + sha1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } if (!reference_rev) @@ -235,7 +236,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); name = mkpathdup(fmt, bname.buf); - target = resolve_ref_unsafe(name, sha1, 0, &flags); + target = resolve_ref_unsafe(name, 0, sha1, &flags); if (!target || (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) { error(remote_branch @@ -299,7 +300,7 @@ static char *resolve_symref(const char *src, const char *prefix) int flag; const char *dst; - dst = resolve_ref_unsafe(src, sha1, 0, &flag); + dst = resolve_ref_unsafe(src, 0, sha1, &flag); if (!(dst && (flag & REF_ISSYMREF))) return NULL; if (prefix) @@ -869,7 +870,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head = resolve_refdup("HEAD", head_sha1, 0, NULL); + head = resolve_refdup("HEAD", 0, head_sha1, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) diff --git a/builtin/checkout.c b/builtin/checkout.c index b4decd5b19..5410dacea0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -355,7 +355,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - read_ref_full("HEAD", rev, 0, &flag); + read_ref_full("HEAD", 0, rev, &flag); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); @@ -775,7 +775,7 @@ static int switch_branches(const struct checkout_opts *opts, unsigned char rev[20]; int flag, writeout_error = 0; memset(&old, 0, sizeof(old)); - old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); + old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag); old.commit = lookup_commit_reference_gently(rev, 1); if (!(flag & REF_ISSYMREF)) old.path = NULL; @@ -1072,7 +1072,7 @@ static int checkout_branch(struct checkout_opts *opts, unsigned char rev[20]; int flag; - if (!read_ref_full("HEAD", rev, 0, &flag) && + if (!read_ref_full("HEAD", 0, rev, &flag) && (flag & REF_ISSYMREF) && is_null_sha1(rev)) return switch_unborn_to_new_branch(opts); } diff --git a/builtin/clone.c b/builtin/clone.c index d3bf9532d6..7f509d06a8 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -623,7 +623,7 @@ static int checkout(void) if (option_no_checkout) return 0; - head = resolve_refdup("HEAD", sha1, 1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " "unable to checkout.\n")); diff --git a/builtin/commit.c b/builtin/commit.c index a7857ab560..aa0940e38f 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1515,7 +1515,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); - head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL); + head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL); if (!strcmp(head, "HEAD")) head = _("detached HEAD"); else diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 79df05ef52..37177c6c29 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, /* get current branch */ current_branch = current_branch_to_free = - resolve_refdup("HEAD", head_sha1, 1, NULL); + resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); if (!current_branch) die("No current branch"); if (starts_with(current_branch, "refs/heads/")) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index fda0f04712..492265d9f9 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -635,7 +635,8 @@ static void populate_value(struct refinfo *ref) if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { unsigned char unused1[20]; - ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL); + ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, + unused1, NULL); if (!ref->symref) ref->symref = ""; } @@ -693,7 +694,8 @@ static void populate_value(struct refinfo *ref) const char *head; unsigned char sha1[20]; - head = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + sha1, NULL); if (!strcmp(ref->refname, head)) v->s = "*"; else diff --git a/builtin/fsck.c b/builtin/fsck.c index e9ba576c1f..a27515aeaa 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -556,7 +556,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag); + head_points_at = resolve_ref_unsafe("HEAD", 0, head_sha1, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) diff --git a/builtin/log.c b/builtin/log.c index 1202eba8b6..83c0b92707 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1400,7 +1400,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (check_head) { unsigned char sha1[20]; const char *ref, *v; - ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + sha1, NULL); if (ref && skip_prefix(ref, "refs/heads/", &v)) branch_name = xstrdup(v); else diff --git a/builtin/merge.c b/builtin/merge.c index 4513fadc5f..bebbe5b308 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1101,7 +1101,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag); + branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag); if (branch && starts_with(branch, "refs/heads/")) branch += 11; if (!branch || is_null_sha1(head_sha1)) diff --git a/builtin/notes.c b/builtin/notes.c index 67d0bb14f8..68b6cd8cc1 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -702,7 +702,7 @@ static int merge_commit(struct notes_merge_options *o) init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = - resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL); + resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL); if (!o->local_ref) die("Failed to resolve NOTES_MERGE_REF"); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index df6c337d69..27e9dc2c5b 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -909,7 +909,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag); + dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag); strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) @@ -1070,7 +1070,7 @@ static void execute_commands(struct command *commands, check_aliased_updates(commands); free(head_name_to_free); - head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL); + head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL); checked_connectivity = 1; for (cmd = commands; cmd; cmd = cmd->next) { diff --git a/builtin/remote.c b/builtin/remote.c index 9a4640dbf0..42a533aec3 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -567,7 +567,8 @@ static int read_remote_branches(const char *refname, strbuf_addf(&buf, "refs/remotes/%s/", rename->old); if (starts_with(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); - symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, + orig_sha1, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); else @@ -703,7 +704,7 @@ static int mv(int argc, const char **argv) int flag = 0; unsigned char sha1[20]; - read_ref_full(item->string, sha1, 1, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(item->string, NULL, REF_NODEREF)) diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 199b081e9b..270e39c6c1 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -728,7 +728,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (ac == 0) { static const char *fake_av[2]; - fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL); + fake_av[0] = resolve_refdup("HEAD", + RESOLVE_REF_READING, + sha1, NULL); fake_av[1] = NULL; av = fake_av; ac = 1; @@ -789,7 +791,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } } - head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL); + head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + head_sha1, NULL); if (head_p) { head_len = strlen(head_p); memcpy(head, head_p, head_len + 1); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index b6a711d319..29fb3f1c20 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print) { unsigned char sha1[20]; int flag; - const char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag); + const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag); if (!refname) die("No such ref: %s", HEAD); diff --git a/bundle.c b/bundle.c index 9a22ab5c02..fa67057e60 100644 --- a/bundle.c +++ b/bundle.c @@ -310,7 +310,7 @@ int create_bundle(struct bundle_header *header, const char *path, continue; if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) continue; - if (read_ref_full(e->name, sha1, 1, &flag)) + if (read_ref_full(e->name, RESOLVE_REF_READING, sha1, &flag)) flag = 0; display_ref = (flag & REF_ISSYMREF) ? e->name : ref; diff --git a/cache.h b/cache.h index 5b86065815..720063968b 100644 --- a/cache.h +++ b/cache.h @@ -950,8 +950,8 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ -extern int read_ref_full(const char *refname, unsigned char *sha1, - int reading, int *flags); +extern int read_ref_full(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); extern int read_ref(const char *refname, unsigned char *sha1); /* @@ -963,20 +963,20 @@ extern int read_ref(const char *refname, unsigned char *sha1); * or the input ref. * * If the reference cannot be resolved to an object, the behavior - * depends on the "reading" argument: + * depends on the RESOLVE_REF_READING flag: * - * - If reading is set, return NULL. + * - If RESOLVE_REF_READING is set, return NULL. * - * - If reading is not set, clear sha1 and return the name of the last - * reference name in the chain, which will either be a non-symbolic + * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of + * the last reference name in the chain, which will either be a non-symbolic * reference or an undefined reference. If this is a prelude to * "writing" to the ref, the return value is the name of the ref * that will actually be created or changed. * - * If flag is non-NULL, set the value that it points to the + * If flags is non-NULL, set the value that it points to the * combination of REF_ISPACKED (if the reference was found among the - * packed references) and REF_ISSYMREF (if the initial reference was a - * symbolic reference). + * packed references), REF_ISSYMREF (if the initial reference was a + * symbolic reference) and REF_ISBROKEN (if the ref is malformed). * * If ref is not a properly-formatted, normalized reference, return * NULL. If more than MAXDEPTH recursive symbolic lookups are needed, @@ -984,8 +984,9 @@ extern int read_ref(const char *refname, unsigned char *sha1); * * errno is set to something meaningful on error. */ -extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag); -extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag); +#define RESOLVE_REF_READING 0x01 +extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); +extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); diff --git a/http-backend.c b/http-backend.c index 404e682593..9977c5d499 100644 --- a/http-backend.c +++ b/http-backend.c @@ -412,7 +412,9 @@ static int show_head_ref(const char *refname, const unsigned char *sha1, if (flag & REF_ISSYMREF) { unsigned char unused[20]; - const char *target = resolve_ref_unsafe(refname, unused, 1, NULL); + const char *target = resolve_ref_unsafe(refname, + RESOLVE_REF_READING, + unused, NULL); const char *target_nons = strip_namespace(target); strbuf_addf(buf, "ref: %s\n", target_nons); diff --git a/notes-merge.c b/notes-merge.c index fd5fae255d..7eb9d7a010 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -549,7 +549,7 @@ int notes_merge(struct notes_merge_options *o, o->local_ref, o->remote_ref); /* Dereference o->local_ref into local_sha1 */ - if (read_ref_full(o->local_ref, local_sha1, 0, NULL)) + if (read_ref_full(o->local_ref, 0, local_sha1, NULL)) die("Failed to resolve local notes ref '%s'", o->local_ref); else if (!check_refname_format(o->local_ref, 0) && is_null_sha1(local_sha1)) diff --git a/reflog-walk.c b/reflog-walk.c index 0e5174b605..222de762eb 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -48,7 +48,8 @@ static struct complete_reflogs *read_complete_reflog(const char *ref) unsigned char sha1[20]; const char *name; void *name_to_free; - name = name_to_free = resolve_refdup(ref, sha1, 1, NULL); + name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING, + sha1, NULL); if (name) { for_each_reflog_ent(name, read_one_reflog, reflogs); free(name_to_free); @@ -174,7 +175,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info, if (*branch == '\0') { unsigned char sha1[20]; free(branch); - branch = resolve_refdup("HEAD", sha1, 0, NULL); + branch = resolve_refdup("HEAD", 0, sha1, NULL); if (!branch) die ("No current branch"); diff --git a/refs.c b/refs.c index 6b4236a67f..f8d59ab7f0 100644 --- a/refs.c +++ b/refs.c @@ -1251,7 +1251,9 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) hashclr(sha1); flag |= REF_ISBROKEN; } - } else if (read_ref_full(refname.buf, sha1, 1, &flag)) { + } else if (read_ref_full(refname.buf, + RESOLVE_REF_READING, + sha1, &flag)) { hashclr(sha1); flag |= REF_ISBROKEN; } @@ -1376,9 +1378,9 @@ static struct ref_entry *get_packed_ref(const char *refname) * options are forwarded from resolve_safe_unsafe(). */ static const char *handle_missing_loose_ref(const char *refname, + int resolve_flags, unsigned char *sha1, - int reading, - int *flag) + int *flags) { struct ref_entry *entry; @@ -1389,12 +1391,12 @@ static const char *handle_missing_loose_ref(const char *refname, entry = get_packed_ref(refname); if (entry) { hashcpy(sha1, entry->u.value.sha1); - if (flag) - *flag |= REF_ISPACKED; + if (flags) + *flags |= REF_ISPACKED; return refname; } /* The reference is not a packed reference, either. */ - if (reading) { + if (resolve_flags & RESOLVE_REF_READING) { return NULL; } else { hashclr(sha1); @@ -1403,21 +1405,20 @@ static const char *handle_missing_loose_ref(const char *refname, } /* This function needs to return a meaningful errno on failure */ -const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) +const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { int depth = MAXDEPTH; ssize_t len; char buffer[256]; static char refname_buffer[256]; - if (flag) - *flag = 0; + if (flags) + *flags = 0; if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { errno = EINVAL; return NULL; } - for (;;) { char path[PATH_MAX]; struct stat st; @@ -1443,8 +1444,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea stat_ref: if (lstat(path, &st) < 0) { if (errno == ENOENT) - return handle_missing_loose_ref(refname, sha1, - reading, flag); + return handle_missing_loose_ref(refname, + resolve_flags, sha1, flags); else return NULL; } @@ -1464,8 +1465,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea !check_refname_format(buffer, 0)) { strcpy(refname_buffer, buffer); refname = refname_buffer; - if (flag) - *flag |= REF_ISSYMREF; + if (flags) + *flags |= REF_ISSYMREF; continue; } } @@ -1510,21 +1511,21 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea */ if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) { - if (flag) - *flag |= REF_ISBROKEN; + if (flags) + *flags |= REF_ISBROKEN; errno = EINVAL; return NULL; } return refname; } - if (flag) - *flag |= REF_ISSYMREF; + if (flags) + *flags |= REF_ISSYMREF; buf = buffer + 4; while (isspace(*buf)) buf++; if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { - if (flag) - *flag |= REF_ISBROKEN; + if (flags) + *flags |= REF_ISBROKEN; errno = EINVAL; return NULL; } @@ -1532,9 +1533,9 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea } } -char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag) +char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags) { - const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag); + const char *ret = resolve_ref_unsafe(ref, resolve_flags, sha1, flags); return ret ? xstrdup(ret) : NULL; } @@ -1545,22 +1546,22 @@ struct ref_filter { void *cb_data; }; -int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags) +int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { - if (resolve_ref_unsafe(refname, sha1, reading, flags)) + if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags)) return 0; return -1; } int read_ref(const char *refname, unsigned char *sha1) { - return read_ref_full(refname, sha1, 1, NULL); + return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL); } int ref_exists(const char *refname) { unsigned char sha1[20]; - return !!resolve_ref_unsafe(refname, sha1, 1, NULL); + return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL); } static int filter_refs(const char *refname, const unsigned char *sha1, int flags, @@ -1673,7 +1674,7 @@ int peel_ref(const char *refname, unsigned char *sha1) return 0; } - if (read_ref_full(refname, base, 1, &flag)) + if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag)) return -1; /* @@ -1714,7 +1715,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha if (!(flags & REF_ISSYMREF)) return 0; - resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL); + resolves_to = resolve_ref_unsafe(refname, 0, junk, NULL); if (!resolves_to || (d->refname ? strcmp(resolves_to, d->refname) @@ -1839,7 +1840,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) return 0; } - if (!read_ref_full("HEAD", sha1, 1, &flag)) + if (!read_ref_full("HEAD", RESOLVE_REF_READING, sha1, &flag)) return fn("HEAD", sha1, flag, cb_data); return 0; @@ -1919,7 +1920,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data) int flag; strbuf_addf(&buf, "%sHEAD", get_git_namespace()); - if (!read_ref_full(buf.buf, sha1, 1, &flag)) + if (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag)) ret = fn(buf.buf, sha1, flag, cb_data); strbuf_release(&buf); @@ -2014,7 +2015,9 @@ int refname_match(const char *abbrev_name, const char *full_name) static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + if (read_ref_full(lock->ref_name, + mustexist ? RESOLVE_REF_READING : 0, + lock->old_sha1, NULL)) { int save_errno = errno; error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); @@ -2087,7 +2090,8 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) this_result = refs_found ? sha1_from_ref : sha1; mksnpath(fullref, sizeof(fullref), *p, len, str); - r = resolve_ref_unsafe(fullref, this_result, 1, &flag); + r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING, + this_result, &flag); if (r) { if (!refs_found++) *ref = xstrdup(r); @@ -2116,7 +2120,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) const char *ref, *it; mksnpath(path, sizeof(path), *p, len, str); - ref = resolve_ref_unsafe(path, hash, 1, NULL); + ref = resolve_ref_unsafe(path, RESOLVE_REF_READING, + hash, NULL); if (!ref) continue; if (reflog_exists(path)) @@ -2151,6 +2156,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, int last_errno = 0; int type, lflags; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); + int resolve_flags = 0; int missing = 0; int attempts_remaining = 3; @@ -2162,7 +2168,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type); + if (mustexist) + resolve_flags |= RESOLVE_REF_READING; + + refname = resolve_ref_unsafe(refname, resolve_flags, + lock->old_sha1, &type); if (!refname && errno == EISDIR) { /* we are trying to lock foo but we used to * have foo/bar which now does not exist; @@ -2175,7 +2185,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, error("there are still refs under '%s'", orig_refname); goto error_return; } - refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type); + refname = resolve_ref_unsafe(orig_refname, resolve_flags, + lock->old_sha1, &type); } if (type_p) *type_p = type; @@ -2517,7 +2528,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data) unsigned char sha1[20]; int flags; - if (read_ref_full(entry->name, sha1, 0, &flags)) + if (read_ref_full(entry->name, 0, sha1, &flags)) /* We should at least have found the packed ref. */ die("Internal error"); if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) { @@ -2721,7 +2732,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms if (log && S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldrefname); - symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING, + orig_sha1, &flag); if (flag & REF_ISSYMREF) return error("refname %s is a symbolic ref, renaming it is not supported", oldrefname); @@ -2740,7 +2752,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms goto rollback; } - if (!read_ref_full(newrefname, sha1, 1, NULL) && + if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) && delete_ref(newrefname, sha1, REF_NODEREF)) { if (errno==EISDIR) { if (remove_empty_directories(git_path("%s", newrefname))) { @@ -3018,7 +3030,8 @@ static int write_ref_sha1(struct ref_lock *lock, unsigned char head_sha1[20]; int head_flag; const char *head_ref; - head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag); + head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + head_sha1, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) log_ref_write("HEAD", lock->old_sha1, sha1, logmsg); @@ -3389,7 +3402,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data retval = do_for_each_reflog(name, fn, cb_data); } else { unsigned char sha1[20]; - if (read_ref_full(name->buf, sha1, 0, NULL)) + if (read_ref_full(name->buf, 0, sha1, NULL)) retval = error("bad ref for %s", name->buf); else retval = fn(name->buf, sha1, 0, cb_data); diff --git a/remote.c b/remote.c index ce785f8953..f62421702f 100644 --- a/remote.c +++ b/remote.c @@ -508,7 +508,7 @@ static void read_config(void) return; default_remote_name = "origin"; current_branch = NULL; - head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag); + head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag); if (head_ref && (flag & REF_ISSYMREF) && skip_prefix(head_ref, "refs/heads/", &head_ref)) { current_branch = make_branch(head_ref, 0); @@ -1138,7 +1138,8 @@ static char *guess_ref(const char *name, struct ref *peer) struct strbuf buf = STRBUF_INIT; unsigned char sha1[20]; - const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL); + const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING, + sha1, NULL); if (!r) return NULL; @@ -1199,7 +1200,9 @@ static int match_explicit(struct ref *src, struct ref *dst, unsigned char sha1[20]; int flag; - dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag); + dst_value = resolve_ref_unsafe(matched_src->name, + RESOLVE_REF_READING, + sha1, &flag); if (!dst_value || ((flag & REF_ISSYMREF) && !starts_with(dst_value, "refs/heads/"))) @@ -1673,7 +1676,7 @@ static int ignore_symref_update(const char *refname) unsigned char sha1[20]; int flag; - if (!resolve_ref_unsafe(refname, sha1, 0, &flag)) + if (!resolve_ref_unsafe(refname, 0, sha1, &flag)) return 0; /* non-existing refs are OK */ return (flag & REF_ISSYMREF); } diff --git a/sequencer.c b/sequencer.c index 47aac754bc..a03d4fa252 100644 --- a/sequencer.c +++ b/sequencer.c @@ -331,7 +331,7 @@ static int is_index_unchanged(void) unsigned char head_sha1[20]; struct commit *head_commit; - if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) return error(_("Could not resolve HEAD commit\n")); head_commit = lookup_commit(head_sha1); @@ -871,7 +871,7 @@ static int rollback_single_pick(void) if (!file_exists(git_path("CHERRY_PICK_HEAD")) && !file_exists(git_path("REVERT_HEAD"))) return error(_("no cherry-pick or revert in progress")); - if (read_ref_full("HEAD", head_sha1, 0, NULL)) + if (read_ref_full("HEAD", 0, head_sha1, NULL)) return error(_("cannot resolve HEAD")); if (is_null_sha1(head_sha1)) return error(_("cannot abort from a branch yet to be born")); diff --git a/transport-helper.c b/transport-helper.c index 2b24d51a24..9b2962041b 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -897,7 +897,10 @@ static int push_refs_with_export(struct transport *transport, int flag; /* Follow symbolic refs (mainly for HEAD). */ - name = resolve_ref_unsafe(ref->peer_ref->name, sha1, 1, &flag); + name = resolve_ref_unsafe( + ref->peer_ref->name, + RESOLVE_REF_READING, + sha1, &flag); if (!name || !(flag & REF_ISSYMREF)) name = ref->peer_ref->name; diff --git a/transport.c b/transport.c index 055d2a27d9..b56620ed7f 100644 --- a/transport.c +++ b/transport.c @@ -168,7 +168,8 @@ static void set_upstreams(struct transport *transport, struct ref *refs, /* Follow symbolic refs (mainly for HEAD). */ localname = ref->peer_ref->name; remotename = ref->name; - tmp = resolve_ref_unsafe(localname, sha, 1, &flag); + tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING, + sha, &flag); if (tmp && flag & REF_ISSYMREF && starts_with(tmp, "refs/heads/")) localname = tmp; @@ -743,7 +744,7 @@ void transport_print_push_status(const char *dest, struct ref *refs, unsigned char head_sha1[20]; char *head; - head = resolve_refdup("HEAD", head_sha1, 1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); if (verbose) { for (ref = refs; ref; ref = ref->next) diff --git a/upload-pack.c b/upload-pack.c index c789ec0050..ac9ac1592d 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -744,7 +744,7 @@ static int find_symref(const char *refname, const unsigned char *sha1, int flag, if ((flag & REF_ISSYMREF) == 0) return 0; - symref_target = resolve_ref_unsafe(refname, unused, 0, &flag); + symref_target = resolve_ref_unsafe(refname, 0, unused, &flag); if (!symref_target || (flag & REF_ISSYMREF) == 0) die("'%s' is a symref but it is not?", refname); item = string_list_append(cb_data, refname); diff --git a/wt-status.c b/wt-status.c index 1bf5d72545..f367066f92 100644 --- a/wt-status.c +++ b/wt-status.c @@ -128,7 +128,7 @@ void wt_status_prepare(struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->use_color = -1; s->relative_paths = 1; - s->branch = resolve_refdup("HEAD", sha1, 0, NULL); + s->branch = resolve_refdup("HEAD", 0, sha1, NULL); s->reference = "HEAD"; s->fp = stdout; s->index_file = get_index_file(); From 014e7db3f5563a61cefbe1f6f5747931c799a37e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Sep 2014 10:52:25 -0700 Subject: [PATCH 15/25] reflog test: test interaction with detached HEAD A proposed patch produced broken HEAD reflog entries when checking out anything other than a branch. The testsuite still passed, so it took a few days for the bug to be noticed. Add tests checking the content of the reflog after detaching and reattaching HEAD so we don't have to rely on manual testing to catch such problems in the future. [jn: using 'log -g --format=%H' instead of parsing --oneline output, resetting state in each test so they can be safely reordered or skipped] Signed-off-by: Jonathan Nieder Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- t/t1413-reflog-detach.sh | 70 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100755 t/t1413-reflog-detach.sh diff --git a/t/t1413-reflog-detach.sh b/t/t1413-reflog-detach.sh new file mode 100755 index 0000000000..c730600d8a --- /dev/null +++ b/t/t1413-reflog-detach.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='Test reflog interaction with detached HEAD' +. ./test-lib.sh + +reset_state () { + git checkout master && + cp saved_reflog .git/logs/HEAD +} + +test_expect_success setup ' + test_tick && + git commit --allow-empty -m initial && + git branch side && + test_tick && + git commit --allow-empty -m second && + cat .git/logs/HEAD >saved_reflog +' + +test_expect_success baseline ' + reset_state && + git rev-parse master master^ >expect && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'switch to branch' ' + reset_state && + git rev-parse side master master^ >expect && + git checkout side && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'detach to other' ' + reset_state && + git rev-parse master side master master^ >expect && + git checkout side && + git checkout master^0 && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'detach to self' ' + reset_state && + git rev-parse master master master^ >expect && + git checkout master^0 && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'attach to self' ' + reset_state && + git rev-parse master master master master^ >expect && + git checkout master^0 && + git checkout master && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'attach to other' ' + reset_state && + git rev-parse side master master master^ >expect && + git checkout master^0 && + git checkout side && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_done From 62a2d52514aed2b684409cb48e40e0cd14335d1b Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Wed, 10 Sep 2014 18:22:48 -0700 Subject: [PATCH 16/25] branch -d: avoid repeated symref resolution If a repository gets in a broken state with too much symref nesting, it cannot be repaired with "git branch -d": $ git symbolic-ref refs/heads/nonsense refs/heads/nonsense $ git branch -d nonsense error: branch 'nonsense' not found. Worse, "git update-ref --no-deref -d" doesn't work for such repairs either: $ git update-ref -d refs/heads/nonsense error: unable to resolve reference refs/heads/nonsense: Too many levels of symbolic links Fix both by teaching resolve_ref_unsafe a new RESOLVE_REF_NO_RECURSE flag and passing it when appropriate. Callers can still read the value of a symref (for example to print a message about it) with that flag set --- resolve_ref_unsafe will resolve one level of symrefs and stop there. Signed-off-by: Jonathan Nieder Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- builtin/branch.c | 3 ++- cache.h | 6 ++++++ refs.c | 17 +++++++++++++++-- refs.h | 2 ++ t/t1400-update-ref.sh | 34 ++++++++++++++++++++++++++++++++++ t/t3200-branch.sh | 9 +++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 6491bacd69..2ad2d0b44a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -236,7 +236,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); name = mkpathdup(fmt, bname.buf); - target = resolve_ref_unsafe(name, 0, sha1, &flags); + target = resolve_ref_unsafe(name, RESOLVE_REF_NO_RECURSE, + sha1, &flags); if (!target || (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) { error(remote_branch diff --git a/cache.h b/cache.h index 720063968b..b735f3f1e3 100644 --- a/cache.h +++ b/cache.h @@ -973,6 +973,11 @@ extern int read_ref(const char *refname, unsigned char *sha1); * "writing" to the ref, the return value is the name of the ref * that will actually be created or changed. * + * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one + * level of symbolic reference. The value stored in sha1 for a symbolic + * reference will always be null_sha1 in this case, and the return + * value is the reference that the symref refers to directly. + * * If flags is non-NULL, set the value that it points to the * combination of REF_ISPACKED (if the reference was found among the * packed references), REF_ISSYMREF (if the initial reference was a @@ -985,6 +990,7 @@ extern int read_ref(const char *refname, unsigned char *sha1); * errno is set to something meaningful on error. */ #define RESOLVE_REF_READING 0x01 +#define RESOLVE_REF_NO_RECURSE 0x02 extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); diff --git a/refs.c b/refs.c index f8d59ab7f0..4fe263ef9a 100644 --- a/refs.c +++ b/refs.c @@ -1467,6 +1467,10 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned refname = refname_buffer; if (flags) *flags |= REF_ISSYMREF; + if (resolve_flags & RESOLVE_REF_NO_RECURSE) { + hashclr(sha1); + return refname; + } continue; } } @@ -1523,13 +1527,17 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned buf = buffer + 4; while (isspace(*buf)) buf++; + refname = strcpy(refname_buffer, buf); + if (resolve_flags & RESOLVE_REF_NO_RECURSE) { + hashclr(sha1); + return refname; + } if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { if (flags) *flags |= REF_ISBROKEN; errno = EINVAL; return NULL; } - refname = strcpy(refname_buffer, buf); } } @@ -2170,6 +2178,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, if (mustexist) resolve_flags |= RESOLVE_REF_READING; + if (flags & REF_NODEREF && flags & REF_DELETING) + resolve_flags |= RESOLVE_REF_NO_RECURSE; refname = resolve_ref_unsafe(refname, resolve_flags, lock->old_sha1, &type); @@ -3664,13 +3674,16 @@ int ref_transaction_commit(struct ref_transaction *transaction, /* Acquire all locks while verifying old values */ for (i = 0; i < n; i++) { struct ref_update *update = updates[i]; + int flags = update->flags; + if (is_null_sha1(update->new_sha1)) + flags |= REF_DELETING; update->lock = lock_ref_sha1_basic(update->refname, (update->have_old ? update->old_sha1 : NULL), NULL, - update->flags, + flags, &update->type); if (!update->lock) { ret = (errno == ENOTDIR) diff --git a/refs.h b/refs.h index eea104478f..79802d79c0 100644 --- a/refs.h +++ b/refs.h @@ -177,10 +177,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1); * ref_transaction_create(), etc. * REF_NODEREF: act on the ref directly, instead of dereferencing * symbolic references. + * REF_DELETING: tolerate broken refs * * Flags >= 0x100 are reserved for internal use. */ #define REF_NODEREF 0x01 +#define REF_DELETING 0x02 /* * This function sets errno to something meaningful on failure. */ diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 0218e96366..7c8c41a1a0 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -110,6 +110,40 @@ test_expect_success "delete symref without dereference when the referred ref is cp -f .git/HEAD.orig .git/HEAD git update-ref -d $m +test_expect_success 'update-ref -d is not confused by self-reference' ' + git symbolic-ref refs/heads/self refs/heads/self && + test_when_finished "rm -f .git/refs/heads/self" && + test_path_is_file .git/refs/heads/self && + test_must_fail git update-ref -d refs/heads/self && + test_path_is_file .git/refs/heads/self +' + +test_expect_success 'update-ref --no-deref -d can delete self-reference' ' + git symbolic-ref refs/heads/self refs/heads/self && + test_when_finished "rm -f .git/refs/heads/self" && + test_path_is_file .git/refs/heads/self && + git update-ref --no-deref -d refs/heads/self && + test_path_is_missing .git/refs/heads/self +' + +test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' ' + >.git/refs/heads/bad && + test_when_finished "rm -f .git/refs/heads/bad" && + git symbolic-ref refs/heads/ref-to-bad refs/heads/bad && + test_when_finished "rm -f .git/refs/heads/ref-to-bad" && + test_path_is_file .git/refs/heads/ref-to-bad && + git update-ref --no-deref -d refs/heads/ref-to-bad && + test_path_is_missing .git/refs/heads/ref-to-bad +' + +test_expect_success 'update-ref --no-deref -d can delete reference to broken name' ' + git symbolic-ref refs/heads/badname refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/badname" && + test_path_is_file .git/refs/heads/badname && + git update-ref --no-deref -d refs/heads/badname && + test_path_is_missing .git/refs/heads/badname +' + test_expect_success '(not) create HEAD with old sha1' " test_must_fail git update-ref HEAD $A $B " diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index ac31b711f2..432921b6b8 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -285,6 +285,15 @@ test_expect_success 'deleting a dangling symref' ' test_i18ncmp expect actual ' +test_expect_success 'deleting a self-referential symref' ' + git symbolic-ref refs/heads/self-reference refs/heads/self-reference && + test_path_is_file .git/refs/heads/self-reference && + echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect && + git branch -d self-reference >actual && + test_path_is_missing .git/refs/heads/self-reference && + test_i18ncmp expect actual +' + test_expect_success 'renaming a symref is not allowed' ' git symbolic-ref refs/heads/master2 refs/heads/master && test_must_fail git branch -m master2 master3 && From 18f29fc61ed88145a0664657c3cea9e9732ea5e8 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 11 Sep 2014 10:34:36 -0700 Subject: [PATCH 17/25] branch -d: simplify by using RESOLVE_REF_READING When "git branch -d" reads the branch it is about to delete, it used to avoid passing the RESOLVE_REF_READING ('treat missing ref as error') flag because a symref pointing to a nonexistent ref would show up as missing instead of as something that could be deleted. To check if a ref is actually missing, we then check - is it a symref? - if not, did it resolve to null_sha1? Now we pass RESOLVE_REF_NO_RECURSE and the correct information is returned for a symref even when it points to a missing ref. Simplify by relying on RESOLVE_REF_READING. No functional change intended. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/branch.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 2ad2d0b44a..0c7aac0878 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -236,10 +236,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); name = mkpathdup(fmt, bname.buf); - target = resolve_ref_unsafe(name, RESOLVE_REF_NO_RECURSE, + target = resolve_ref_unsafe(name, + RESOLVE_REF_READING + | RESOLVE_REF_NO_RECURSE, sha1, &flags); - if (!target || - (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) { + if (!target) { error(remote_branch ? _("remote branch '%s' not found.") : _("branch '%s' not found."), bname.buf); From f3cc52d840418c1a38bb4ae9a09a479e77d95e77 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 26 Sep 2014 12:22:22 -0700 Subject: [PATCH 18/25] packed-ref cache: forbid dot-components in refnames Since v1.7.9-rc1~10^2 (write_head_info(): handle "extra refs" locally, 2012-01-06), this trick to keep track of ".have" refs that are only valid on the wire and not on the filesystem is not needed any more. Simplify by removing support for the REFNAME_DOT_COMPONENT flag. This means we'll be slightly stricter with invalid refs found in a packed-refs file or during clone. read_loose_refs() already checks for and skips refnames with .components so it is not affected. Signed-off-by: Jonathan Nieder Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- refs.c | 14 +++----------- refs.h | 6 +----- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/refs.c b/refs.c index 4fe263ef9a..fe1352a344 100644 --- a/refs.c +++ b/refs.c @@ -70,16 +70,8 @@ static int check_refname_component(const char *refname, int flags) out: if (cp == refname) return 0; /* Component has zero length. */ - if (refname[0] == '.') { - if (!(flags & REFNAME_DOT_COMPONENT)) - return -1; /* Component starts with '.'. */ - /* - * Even if leading dots are allowed, don't allow "." - * as a component (".." is prevented by a rule above). - */ - if (refname[1] == '\0') - return -1; /* Component equals ".". */ - } + if (refname[0] == '.') + return -1; /* Component starts with '.'. */ if (cp - refname >= LOCK_SUFFIX_LEN && !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN)) return -1; /* Refname ends with ".lock". */ @@ -290,7 +282,7 @@ static struct ref_entry *create_ref_entry(const char *refname, struct ref_entry *ref; if (check_name && - check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) die("Reference has invalid format: '%s'", refname); len = strlen(refname) + 1; ref = xmalloc(sizeof(struct ref_entry) + len); diff --git a/refs.h b/refs.h index 79802d79c0..c61b8f442f 100644 --- a/refs.h +++ b/refs.h @@ -229,7 +229,6 @@ extern int for_each_reflog(each_ref_fn, void *); #define REFNAME_ALLOW_ONELEVEL 1 #define REFNAME_REFSPEC_PATTERN 2 -#define REFNAME_DOT_COMPONENT 4 /* * Return 0 iff refname has the correct format for a refname according @@ -237,10 +236,7 @@ extern int for_each_reflog(each_ref_fn, void *); * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level * reference names. If REFNAME_REFSPEC_PATTERN is set in flags, then * allow a "*" wildcard character in place of one of the name - * components. No leading or repeated slashes are accepted. If - * REFNAME_DOT_COMPONENT is set in flags, then allow refname - * components to start with "." (but not a whole component equal to - * "." or ".."). + * components. No leading or repeated slashes are accepted. */ extern int check_refname_format(const char *refname, int flags); From 8159f4af7d21e106ed04d69c428b395701b94205 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 25 Sep 2014 15:02:39 -0700 Subject: [PATCH 19/25] test: put tests for handling of bad ref names in one place There's no straightforward way to grep for all tests dealing with invalid refs. Put them in a single test script so it is easy to see what functionality has not been exercised with bad ref names yet. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- t/t1400-update-ref.sh | 44 --------------------- t/t1430-bad-ref-name.sh | 84 +++++++++++++++++++++++++++++++++++++++++ t/t9300-fast-import.sh | 30 --------------- 3 files changed, 84 insertions(+), 74 deletions(-) create mode 100755 t/t1430-bad-ref-name.sh diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 7c8c41a1a0..7b4707b776 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -136,14 +136,6 @@ test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' ' test_path_is_missing .git/refs/heads/ref-to-bad ' -test_expect_success 'update-ref --no-deref -d can delete reference to broken name' ' - git symbolic-ref refs/heads/badname refs/heads/broken...ref && - test_when_finished "rm -f .git/refs/heads/badname" && - test_path_is_file .git/refs/heads/badname && - git update-ref --no-deref -d refs/heads/badname && - test_path_is_missing .git/refs/heads/badname -' - test_expect_success '(not) create HEAD with old sha1' " test_must_fail git update-ref HEAD $A $B " @@ -408,12 +400,6 @@ test_expect_success 'stdin fails create with no ref' ' grep "fatal: create: missing " err ' -test_expect_success 'stdin fails create with bad ref name' ' - echo "create ~a $m" >stdin && - test_must_fail git update-ref --stdin err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin fails create with no new value' ' echo "create $a" >stdin && test_must_fail git update-ref --stdin err && @@ -432,12 +418,6 @@ test_expect_success 'stdin fails update with no ref' ' grep "fatal: update: missing " err ' -test_expect_success 'stdin fails update with bad ref name' ' - echo "update ~a $m" >stdin && - test_must_fail git update-ref --stdin err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin fails update with no new value' ' echo "update $a" >stdin && test_must_fail git update-ref --stdin err && @@ -456,12 +436,6 @@ test_expect_success 'stdin fails delete with no ref' ' grep "fatal: delete: missing " err ' -test_expect_success 'stdin fails delete with bad ref name' ' - echo "delete ~a $m" >stdin && - test_must_fail git update-ref --stdin err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin fails delete with too many arguments' ' echo "delete $a $m $m" >stdin && test_must_fail git update-ref --stdin err && @@ -734,12 +708,6 @@ test_expect_success 'stdin -z fails create with no ref' ' grep "fatal: create: missing " err ' -test_expect_success 'stdin -z fails create with bad ref name' ' - printf $F "create ~a " "$m" >stdin && - test_must_fail git update-ref -z --stdin err && - grep "fatal: invalid ref format: ~a " err -' - test_expect_success 'stdin -z fails create with no new value' ' printf $F "create $a" >stdin && test_must_fail git update-ref -z --stdin err && @@ -764,12 +732,6 @@ test_expect_success 'stdin -z fails update with too few args' ' grep "fatal: update $a: unexpected end of input when reading " err ' -test_expect_success 'stdin -z fails update with bad ref name' ' - printf $F "update ~a" "$m" "" >stdin && - test_must_fail git update-ref -z --stdin err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin -z emits warning with empty new value' ' git update-ref $a $m && printf $F "update $a" "" "" >stdin && @@ -802,12 +764,6 @@ test_expect_success 'stdin -z fails delete with no ref' ' grep "fatal: delete: missing " err ' -test_expect_success 'stdin -z fails delete with bad ref name' ' - printf $F "delete ~a" "$m" >stdin && - test_must_fail git update-ref -z --stdin err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin -z fails delete with no old value' ' printf $F "delete $a" >stdin && test_must_fail git update-ref -z --stdin err && diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh new file mode 100755 index 0000000000..c7b19b0548 --- /dev/null +++ b/t/t1430-bad-ref-name.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +test_description='Test handling of ref names that check-ref-format rejects' +. ./test-lib.sh + +test_expect_success setup ' + test_commit one +' + +test_expect_success 'fast-import: fail on invalid branch name ".badbranchname"' ' + test_when_finished "rm -f .git/objects/pack_* .git/objects/index_*" && + cat >input <<-INPUT_END && + commit .badbranchname + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <input <<-INPUT_END && + commit bad[branch]name + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <stdin && + test_must_fail git update-ref --stdin err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin fails update with bad ref name' ' + echo "update ~a refs/heads/master" >stdin && + test_must_fail git update-ref --stdin err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin fails delete with bad ref name' ' + echo "delete ~a refs/heads/master" >stdin && + test_must_fail git update-ref --stdin err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin -z fails create with bad ref name' ' + printf "%s\0" "create ~a " refs/heads/master >stdin && + test_must_fail git update-ref -z --stdin err && + grep "fatal: invalid ref format: ~a " err +' + +test_expect_success 'update-ref --stdin -z fails update with bad ref name' ' + printf "%s\0" "update ~a" refs/heads/master "" >stdin && + test_must_fail git update-ref -z --stdin err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin -z fails delete with bad ref name' ' + printf "%s\0" "delete ~a" refs/heads/master >stdin && + test_must_fail git update-ref -z --stdin err && + grep "fatal: invalid ref format: ~a" err +' + +test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 8df0445a84..37c2d633f0 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -346,36 +346,6 @@ test_expect_success 'B: fail on invalid blob sha1' ' ' rm -f .git/objects/pack_* .git/objects/index_* -cat >input < $GIT_COMMITTER_DATE -data <input < $GIT_COMMITTER_DATE -data <input < $GIT_COMMITTER_DATE From d0f810f0bc0d6b51722b400f70c2590713f168e8 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Wed, 3 Sep 2014 11:45:43 -0700 Subject: [PATCH 20/25] refs.c: allow listing and deleting badly named refs We currently do not handle badly named refs well: $ cp .git/refs/heads/master .git/refs/heads/master.....@\*@\\. $ git branch fatal: Reference has invalid format: 'refs/heads/master.....@*@\.' $ git branch -D master.....@\*@\\. error: branch 'master.....@*@\.' not found. Users cannot recover from a badly named ref without manually finding and deleting the loose ref file or appropriate line in packed-refs. Making that easier will make it easier to tweak the ref naming rules in the future, for example to forbid shell metacharacters like '`' and '"', without putting people in a state that is hard to get out of. So allow "branch --list" to show these refs and allow "branch -d/-D" and "update-ref -d" to delete them. Other commands (for example to rename refs) will continue to not handle these refs but can be changed in later patches. Details: In resolving functions, refuse to resolve refs that don't pass the git-check-ref-format(1) check unless the new RESOLVE_REF_ALLOW_BAD_NAME flag is passed. Even with RESOLVE_REF_ALLOW_BAD_NAME, refuse to resolve refs that escape the refs/ directory and do not match the pattern [A-Z_]* (think "HEAD" and "MERGE_HEAD"). In locking functions, refuse to act on badly named refs unless they are being deleted and either are in the refs/ directory or match [A-Z_]*. Just like other invalid refs, flag resolved, badly named refs with the REF_ISBROKEN flag, treat them as resolving to null_sha1, and skip them in all iteration functions except for for_each_rawref. Flag badly named refs (but not symrefs pointing to badly named refs) with a REF_BAD_NAME flag to make it easier for future callers to notice and handle them specially. For example, in a later patch for-each-ref will use this flag to detect refs whose names can confuse callers parsing for-each-ref output. In the transaction API, refuse to create or update badly named refs, but allow deleting them (unless they try to escape refs/ and don't match [A-Z_]*). Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/branch.c | 9 +-- cache.h | 17 ++++- refs.c | 148 ++++++++++++++++++++++++++++++++-------- refs.h | 12 +++- t/t1430-bad-ref-name.sh | 125 ++++++++++++++++++++++++++++++++- 5 files changed, 273 insertions(+), 38 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 0c7aac0878..7e113d6c7d 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -238,7 +238,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name = mkpathdup(fmt, bname.buf); target = resolve_ref_unsafe(name, RESOLVE_REF_READING - | RESOLVE_REF_NO_RECURSE, + | RESOLVE_REF_NO_RECURSE + | RESOLVE_REF_ALLOW_BAD_NAME, sha1, &flags); if (!target) { error(remote_branch @@ -248,7 +249,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, continue; } - if (!(flags & REF_ISSYMREF) && + if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) && check_branch_commit(bname.buf, name, sha1, head_rev, kinds, force)) { ret = 1; @@ -268,8 +269,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, ? _("Deleted remote branch %s (was %s).\n") : _("Deleted branch %s (was %s).\n"), bname.buf, - (flags & REF_ISSYMREF) - ? target + (flags & REF_ISBROKEN) ? "broken" + : (flags & REF_ISSYMREF) ? target : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } delete_branch_config(bname.buf); diff --git a/cache.h b/cache.h index b735f3f1e3..0501f7dca8 100644 --- a/cache.h +++ b/cache.h @@ -981,16 +981,29 @@ extern int read_ref(const char *refname, unsigned char *sha1); * If flags is non-NULL, set the value that it points to the * combination of REF_ISPACKED (if the reference was found among the * packed references), REF_ISSYMREF (if the initial reference was a - * symbolic reference) and REF_ISBROKEN (if the ref is malformed). + * symbolic reference), REF_BAD_NAME (if the reference name is ill + * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN + * (if the ref is malformed or has a bad name). See refs.h for more detail + * on each flag. * * If ref is not a properly-formatted, normalized reference, return * NULL. If more than MAXDEPTH recursive symbolic lookups are needed, * give up and return NULL. * - * errno is set to something meaningful on error. + * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their + * name is invalid according to git-check-ref-format(1). If the name + * is bad then the value stored in sha1 will be null_sha1 and the two + * flags REF_ISBROKEN and REF_BAD_NAME will be set. + * + * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/ + * directory and do not consist of all caps and underscores cannot be + * resolved. The function returns NULL for such ref names. + * Caps and underscores refers to the special refs, such as HEAD, + * FETCH_HEAD and friends, that all live outside of the refs/ directory. */ #define RESOLVE_REF_READING 0x01 #define RESOLVE_REF_NO_RECURSE 0x02 +#define RESOLVE_REF_ALLOW_BAD_NAME 0x04 extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); diff --git a/refs.c b/refs.c index fe1352a344..e7000f2cb2 100644 --- a/refs.c +++ b/refs.c @@ -187,8 +187,8 @@ struct ref_dir { /* * Bit values for ref_entry::flag. REF_ISSYMREF=0x01, - * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see - * refs.h. + * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are + * public values; see refs.h. */ /* @@ -196,16 +196,16 @@ struct ref_dir { * the correct peeled value for the reference, which might be * null_sha1 if the reference is not a tag or if it is broken. */ -#define REF_KNOWS_PEELED 0x08 +#define REF_KNOWS_PEELED 0x10 /* ref_entry represents a directory of references */ -#define REF_DIR 0x10 +#define REF_DIR 0x20 /* * Entry has not yet been read from disk (used only for REF_DIR * entries representing loose references) */ -#define REF_INCOMPLETE 0x20 +#define REF_INCOMPLETE 0x40 /* * A ref_entry represents either a reference or a "subdirectory" of @@ -274,6 +274,39 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry) return dir; } +/* + * Check if a refname is safe. + * For refs that start with "refs/" we consider it safe as long they do + * not try to resolve to outside of refs/. + * + * For all other refs we only consider them safe iff they only contain + * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like + * "config"). + */ +static int refname_is_safe(const char *refname) +{ + if (starts_with(refname, "refs/")) { + char *buf; + int result; + + buf = xmalloc(strlen(refname) + 1); + /* + * Does the refname try to escape refs/? + * For example: refs/foo/../bar is safe but refs/foo/../../bar + * is not. + */ + result = !normalize_path_copy(buf, refname + strlen("refs/")); + free(buf); + return result; + } + while (*refname) { + if (!isupper(*refname) && *refname != '_') + return 0; + refname++; + } + return 1; +} + static struct ref_entry *create_ref_entry(const char *refname, const unsigned char *sha1, int flag, int check_name) @@ -284,6 +317,8 @@ static struct ref_entry *create_ref_entry(const char *refname, if (check_name && check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) die("Reference has invalid format: '%s'", refname); + if (!check_name && !refname_is_safe(refname)) + die("Reference has invalid name: '%s'", refname); len = strlen(refname) + 1; ref = xmalloc(sizeof(struct ref_entry) + len); hashcpy(ref->u.value.sha1, sha1); @@ -1111,7 +1146,13 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir) refname = parse_ref_line(refline, sha1); if (refname) { - last = create_ref_entry(refname, sha1, REF_ISPACKED, 1); + int flag = REF_ISPACKED; + + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + hashclr(sha1); + flag |= REF_BAD_NAME | REF_ISBROKEN; + } + last = create_ref_entry(refname, sha1, flag, 0); if (peeled == PEELED_FULLY || (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/"))) last->flag |= REF_KNOWS_PEELED; @@ -1249,8 +1290,13 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) hashclr(sha1); flag |= REF_ISBROKEN; } + if (check_refname_format(refname.buf, + REFNAME_ALLOW_ONELEVEL)) { + hashclr(sha1); + flag |= REF_BAD_NAME | REF_ISBROKEN; + } add_entry_to_dir(dir, - create_ref_entry(refname.buf, sha1, flag, 1)); + create_ref_entry(refname.buf, sha1, flag, 0)); } strbuf_setlen(&refname, dirnamelen); } @@ -1369,10 +1415,10 @@ static struct ref_entry *get_packed_ref(const char *refname) * A loose ref file doesn't exist; check for a packed ref. The * options are forwarded from resolve_safe_unsafe(). */ -static const char *handle_missing_loose_ref(const char *refname, - int resolve_flags, - unsigned char *sha1, - int *flags) +static int resolve_missing_loose_ref(const char *refname, + int resolve_flags, + unsigned char *sha1, + int *flags) { struct ref_entry *entry; @@ -1385,14 +1431,15 @@ static const char *handle_missing_loose_ref(const char *refname, hashcpy(sha1, entry->u.value.sha1); if (flags) *flags |= REF_ISPACKED; - return refname; + return 0; } /* The reference is not a packed reference, either. */ if (resolve_flags & RESOLVE_REF_READING) { - return NULL; + errno = ENOENT; + return -1; } else { hashclr(sha1); - return refname; + return 0; } } @@ -1403,13 +1450,29 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned ssize_t len; char buffer[256]; static char refname_buffer[256]; + int bad_name = 0; if (flags) *flags = 0; if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - errno = EINVAL; - return NULL; + if (flags) + *flags |= REF_BAD_NAME; + + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(refname)) { + errno = EINVAL; + return NULL; + } + /* + * dwim_ref() uses REF_ISBROKEN to distinguish between + * missing refs and refs that were present but invalid, + * to complain about the latter to stderr. + * + * We don't know whether the ref exists, so don't set + * REF_ISBROKEN yet. + */ + bad_name = 1; } for (;;) { char path[PATH_MAX]; @@ -1435,11 +1498,17 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned */ stat_ref: if (lstat(path, &st) < 0) { - if (errno == ENOENT) - return handle_missing_loose_ref(refname, - resolve_flags, sha1, flags); - else + if (errno != ENOENT) return NULL; + if (resolve_missing_loose_ref(refname, resolve_flags, + sha1, flags)) + return NULL; + if (bad_name) { + hashclr(sha1); + if (flags) + *flags |= REF_ISBROKEN; + } + return refname; } /* Follow "normalized" - ie "refs/.." symlinks by hand */ @@ -1512,6 +1581,11 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned errno = EINVAL; return NULL; } + if (bad_name) { + hashclr(sha1); + if (flags) + *flags |= REF_ISBROKEN; + } return refname; } if (flags) @@ -1527,8 +1601,13 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { if (flags) *flags |= REF_ISBROKEN; - errno = EINVAL; - return NULL; + + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(buf)) { + errno = EINVAL; + return NULL; + } + bad_name = 1; } } } @@ -2160,18 +2239,16 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, int missing = 0; int attempts_remaining = 3; - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - errno = EINVAL; - return NULL; - } - lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; if (mustexist) resolve_flags |= RESOLVE_REF_READING; - if (flags & REF_NODEREF && flags & REF_DELETING) - resolve_flags |= RESOLVE_REF_NO_RECURSE; + if (flags & REF_DELETING) { + resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME; + if (flags & REF_NODEREF) + resolve_flags |= RESOLVE_REF_NO_RECURSE; + } refname = resolve_ref_unsafe(refname, resolve_flags, lock->old_sha1, &type); @@ -3519,6 +3596,13 @@ int ref_transaction_update(struct ref_transaction *transaction, if (have_old && !old_sha1) die("BUG: have_old is true but old_sha1 is NULL"); + if (!is_null_sha1(new_sha1) && + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + strbuf_addf(err, "refusing to update ref with bad name %s", + refname); + return -1; + } + update = add_update(transaction, refname); hashcpy(update->new_sha1, new_sha1); update->flags = flags; @@ -3544,6 +3628,12 @@ int ref_transaction_create(struct ref_transaction *transaction, if (!new_sha1 || is_null_sha1(new_sha1)) die("BUG: create ref with null new_sha1"); + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + strbuf_addf(err, "refusing to create ref with bad name %s", + refname); + return -1; + } + update = add_update(transaction, refname); hashcpy(update->new_sha1, new_sha1); diff --git a/refs.h b/refs.h index c61b8f442f..2bc3556874 100644 --- a/refs.h +++ b/refs.h @@ -56,11 +56,19 @@ struct ref_transaction; /* * Reference cannot be resolved to an object name: dangling symbolic - * reference (directly or indirectly), corrupt reference file, or - * symbolic reference refers to ill-formatted reference name. + * reference (directly or indirectly), corrupt reference file, + * reference exists but name is bad, or symbolic reference refers to + * ill-formatted reference name. */ #define REF_ISBROKEN 0x04 +/* + * Reference name is not well formed. + * + * See git-check-ref-format(1) for the definition of well formed ref names. + */ +#define REF_BAD_NAME 0x08 + /* * The signature for the callback function for the for_each_*() * functions below. The memory pointed to by the refname and sha1 diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index c7b19b0548..468e85621a 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -4,7 +4,8 @@ test_description='Test handling of ref names that check-ref-format rejects' . ./test-lib.sh test_expect_success setup ' - test_commit one + test_commit one && + test_commit two ' test_expect_success 'fast-import: fail on invalid branch name ".badbranchname"' ' @@ -37,6 +38,107 @@ test_expect_success 'fast-import: fail on invalid branch name "bad[branch]name"' test_must_fail git fast-import output && + grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -d can delete badly named ref' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -d broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -D can delete badly named ref' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -D broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -D cannot delete non-ref in .git dir' ' + echo precious >.git/my-private-file && + echo precious >expect && + test_must_fail git branch -D ../../my-private-file && + test_cmp expect .git/my-private-file +' + +test_expect_success 'branch -D cannot delete absolute path' ' + git branch -f extra && + test_must_fail git branch -D "$(pwd)/.git/refs/heads/extra" && + test_cmp_rev HEAD extra +' + +test_expect_success 'git branch cannot create a badly named ref' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + test_must_fail git branch broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -m cannot rename to a bad ref name' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + test_might_fail git branch -D goodref && + git branch goodref && + test_must_fail git branch -m goodref broken...ref && + test_cmp_rev master goodref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_failure 'branch -m can rename from a bad ref name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -m broken...ref renamed && + test_cmp_rev master renamed && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'push cannot create a badly named ref' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_failure 'push --mirror can delete badly named ref' ' + top=$(pwd) && + git init src && + git init dest && + + ( + cd src && + test_commit one + ) && + ( + cd dest && + test_commit two && + git checkout --detach && + cp .git/refs/heads/master .git/refs/heads/broken...ref + ) && + git -C src push --mirror "file://$top/dest" && + git -C dest branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'rev-parse skips symref pointing to broken name' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch shadow one && + cp .git/refs/heads/master .git/refs/heads/broken...ref && + git symbolic-ref refs/tags/shadow refs/heads/broken...ref && + + git rev-parse --verify one >expect && + git rev-parse --verify shadow >actual 2>err && + test_cmp expect actual && + test_i18ngrep "ignoring.*refs/tags/shadow" err +' + test_expect_success 'update-ref --no-deref -d can delete reference to broken name' ' git symbolic-ref refs/heads/badname refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/badname" && @@ -45,6 +147,27 @@ test_expect_success 'update-ref --no-deref -d can delete reference to broken nam test_path_is_missing .git/refs/heads/badname ' +test_expect_success 'update-ref -d can delete broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git update-ref -d refs/heads/broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'update-ref -d cannot delete non-ref in .git dir' ' + echo precious >.git/my-private-file && + echo precious >expect && + test_must_fail git update-ref -d my-private-file && + test_cmp expect .git/my-private-file +' + +test_expect_success 'update-ref -d cannot delete absolute path' ' + git branch -f extra && + test_must_fail git update-ref -d "$(pwd)/.git/refs/heads/extra" && + test_cmp_rev HEAD extra +' + test_expect_success 'update-ref --stdin fails create with bad ref name' ' echo "create ~a refs/heads/master" >stdin && test_must_fail git update-ref --stdin err && From 971c41c71728d057115b105f427202f98565f933 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Fri, 5 Sep 2014 14:35:17 -0700 Subject: [PATCH 21/25] for-each-ref: skip and warn about broken ref names Print a warning message for any bad ref names we find in the repo and skip them so callers don't have to deal with parsing them. It might be useful in the future to have a flag where we would not skip these refs for those callers that want to and are prepared (for example by using a --format argument with %0 as a delimiter after the ref name). Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/for-each-ref.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 492265d9f9..3ee22b96a5 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -839,6 +839,11 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f struct refinfo *ref; int cnt; + if (flag & REF_BAD_NAME) { + warning("ignoring ref with broken name %s", refname); + return 0; + } + if (*cb->grab_pattern) { const char **pattern; int namelen = strlen(refname); From 2ebb49ca8ac34afb0ffe00e4bceac73e8c825ef1 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 11 Sep 2014 08:42:57 -0700 Subject: [PATCH 22/25] remote rm/prune: print a message when writing packed-refs fails Until v2.1.0-rc0~22^2~11 (refs.c: add an err argument to repack_without_refs, 2014-06-20), repack_without_refs forgot to provide an error message when commit_packed_refs fails. Even today, it only provides a message for callers that pass a non-NULL err parameter. Internal callers in refs.c pass non-NULL err but "git remote" does not. That means that "git remote rm" and "git remote prune" can fail without printing a message about why. Fix them by passing in a non-NULL err parameter and printing the returned message. This is the last caller to a ref handling function passing err == NULL. A later patch can drop support for err == NULL, avoiding such problems in the future. Signed-off-by: Ronnie Sahlberg Signed-off-by: Jonathan Nieder Reviewed-by: Michael Haggerty Signed-off-by: Junio C Hamano --- builtin/remote.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/builtin/remote.c b/builtin/remote.c index 42a533aec3..7f28f92a37 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -749,13 +749,16 @@ static int mv(int argc, const char **argv) static int remove_branches(struct string_list *branches) { + struct strbuf err = STRBUF_INIT; const char **branch_names; int i, result = 0; branch_names = xmalloc(branches->nr * sizeof(*branch_names)); for (i = 0; i < branches->nr; i++) branch_names[i] = branches->items[i].string; - result |= repack_without_refs(branch_names, branches->nr, NULL); + if (repack_without_refs(branch_names, branches->nr, &err)) + result |= error("%s", err.buf); + strbuf_release(&err); free(branch_names); for (i = 0; i < branches->nr; i++) { @@ -1332,9 +1335,13 @@ static int prune_remote(const char *remote, int dry_run) delete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs)); for (i = 0; i < states.stale.nr; i++) delete_refs[i] = states.stale.items[i].util; - if (!dry_run) - result |= repack_without_refs(delete_refs, - states.stale.nr, NULL); + if (!dry_run) { + struct strbuf err = STRBUF_INIT; + if (repack_without_refs(delete_refs, states.stale.nr, + &err)) + result |= error("%s", err.buf); + strbuf_release(&err); + } free(delete_refs); } From 5a603b046351894a3c892d5bd6948fcee1bee5ab Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Thu, 28 Aug 2014 16:42:37 -0700 Subject: [PATCH 23/25] refs.c: do not permit err == NULL Some functions that take a strbuf argument to append an error treat !err as an indication that the message should be suppressed (e.g., ref_update_reject_duplicates). Others write the message to stderr on !err (e.g., repack_without_refs). Others crash (e.g., ref_transaction_update). Some of these behaviors are for historical reasons and others were accidents. Luckily no callers pass err == NULL any more. Simplify by consistently requiring the strbuf argument. Signed-off-by: Jonathan Nieder Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- refs.c | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/refs.c b/refs.c index e7000f2cb2..097fb4b60e 100644 --- a/refs.c +++ b/refs.c @@ -2646,6 +2646,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) struct string_list_item *ref_to_delete; int i, ret, removed = 0; + assert(err); + /* Look for a packed ref */ for (i = 0; i < n; i++) if (get_packed_ref(refnames[i])) @@ -2656,13 +2658,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) return 0; /* no refname exists in packed refs */ if (lock_packed_refs(0)) { - if (err) { - unable_to_lock_message(git_path("packed-refs"), errno, - err); - return -1; - } - unable_to_lock_error(git_path("packed-refs"), errno); - return error("cannot delete '%s' from packed refs", refnames[i]); + unable_to_lock_message(git_path("packed-refs"), errno, err); + return -1; } packed = get_packed_refs(&ref_cache); @@ -2688,7 +2685,7 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) /* Write what remains */ ret = commit_packed_refs(); - if (ret && err) + if (ret) strbuf_addf(err, "unable to overwrite old ref-pack file: %s", strerror(errno)); return ret; @@ -2696,6 +2693,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) { + assert(err); + if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { /* * loose. The loose file name is the same as the @@ -3551,6 +3550,8 @@ struct ref_transaction { struct ref_transaction *ref_transaction_begin(struct strbuf *err) { + assert(err); + return xcalloc(1, sizeof(struct ref_transaction)); } @@ -3590,6 +3591,8 @@ int ref_transaction_update(struct ref_transaction *transaction, { struct ref_update *update; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: update called for transaction that is not open"); @@ -3622,6 +3625,8 @@ int ref_transaction_create(struct ref_transaction *transaction, { struct ref_update *update; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: create called for transaction that is not open"); @@ -3653,6 +3658,8 @@ int ref_transaction_delete(struct ref_transaction *transaction, { struct ref_update *update; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: delete called for transaction that is not open"); @@ -3715,13 +3722,14 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n, struct strbuf *err) { int i; + + assert(err); + for (i = 1; i < n; i++) if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) { - const char *str = - "Multiple updates for ref '%s' not allowed."; - if (err) - strbuf_addf(err, str, updates[i]->refname); - + strbuf_addf(err, + "Multiple updates for ref '%s' not allowed.", + updates[i]->refname); return 1; } return 0; @@ -3735,6 +3743,8 @@ int ref_transaction_commit(struct ref_transaction *transaction, int n = transaction->nr; struct ref_update **updates = transaction->updates; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: commit called for transaction that is not open"); @@ -3771,9 +3781,8 @@ int ref_transaction_commit(struct ref_transaction *transaction, ret = (errno == ENOTDIR) ? TRANSACTION_NAME_CONFLICT : TRANSACTION_GENERIC_ERROR; - if (err) - strbuf_addf(err, "Cannot lock the ref '%s'.", - update->refname); + strbuf_addf(err, "Cannot lock the ref '%s'.", + update->refname); goto cleanup; } } @@ -3786,9 +3795,8 @@ int ref_transaction_commit(struct ref_transaction *transaction, if (write_ref_sha1(update->lock, update->new_sha1, update->msg)) { update->lock = NULL; /* freed by write_ref_sha1 */ - if (err) - strbuf_addf(err, "Cannot update the ref '%s'.", - update->refname); + strbuf_addf(err, "Cannot update the ref '%s'.", + update->refname); ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } From fb43bd1cd187bcd29312df3b0394a457d524906d Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Thu, 28 Aug 2014 16:41:34 -0700 Subject: [PATCH 24/25] lockfile: remove unable_to_lock_error The former caller uses unable_to_lock_message now. Signed-off-by: Jonathan Nieder Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- lockfile.c | 10 ---------- lockfile.h | 1 - 2 files changed, 11 deletions(-) diff --git a/lockfile.c b/lockfile.c index d098adebf1..4f16ee78ce 100644 --- a/lockfile.c +++ b/lockfile.c @@ -162,16 +162,6 @@ void unable_to_lock_message(const char *path, int err, struct strbuf *buf) absolute_path(path), strerror(err)); } -int unable_to_lock_error(const char *path, int err) -{ - struct strbuf buf = STRBUF_INIT; - - unable_to_lock_message(path, err, &buf); - error("%s", buf.buf); - strbuf_release(&buf); - return -1; -} - NORETURN void unable_to_lock_die(const char *path, int err) { struct strbuf buf = STRBUF_INIT; diff --git a/lockfile.h b/lockfile.h index dc066d1783..cd2ec95d30 100644 --- a/lockfile.h +++ b/lockfile.h @@ -71,7 +71,6 @@ struct lock_file { #define LOCK_DIE_ON_ERROR 1 #define LOCK_NO_DEREF 2 -extern int unable_to_lock_error(const char *path, int err); extern void unable_to_lock_message(const char *path, int err, struct strbuf *buf); extern NORETURN void unable_to_lock_die(const char *path, int err); From 65732845e89eee69422b725bb495f4282065284f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Thu, 28 Aug 2014 17:01:35 -0700 Subject: [PATCH 25/25] ref_transaction_commit: bail out on failure to remove a ref When removal of a loose or packed ref fails, bail out instead of trying to finish the transaction. This way, a single error message can be printed (instead of multiple messages being concatenated by mistake) and the operator can try to solve the underlying problem before there is a chance to muck things up even more. In particular, when git fails to remove a ref, git goes on to try to delete the reflog. Exiting early lets us keep the reflog. When git succeeds in deleting a ref A and fails to remove a ref B, it goes on to try to delete both reflogs. It would be better to just remove the reflog for A, but that would be a more invasive change. Failing early means we keep both reflogs, which puts the operator in a good position to understand the problem and recover. A long term goal is to avoid these problems altogether and roll back the transaction on failure. That kind of transactionality will have to wait for a later series (the plan for which is to make all destructive work happen in a single update of the packed-refs file). Signed-off-by: Jonathan Nieder Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- refs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index 097fb4b60e..0368ed461f 100644 --- a/refs.c +++ b/refs.c @@ -3809,16 +3809,20 @@ int ref_transaction_commit(struct ref_transaction *transaction, struct ref_update *update = updates[i]; if (update->lock) { - if (delete_ref_loose(update->lock, update->type, err)) + if (delete_ref_loose(update->lock, update->type, err)) { ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } if (!(update->flags & REF_ISPRUNING)) delnames[delnum++] = update->lock->ref_name; } } - if (repack_without_refs(delnames, delnum, err)) + if (repack_without_refs(delnames, delnum, err)) { ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } for (i = 0; i < delnum; i++) unlink_or_warn(git_path("logs/%s", delnames[i])); clear_loose_ref_cache(&ref_cache);