]> git.ipfire.org Git - thirdparty/git.git/commitdiff
wt-status: tolerate dangling marks
authorJonathan Tan <jonathantanmy@google.com>
Tue, 1 Sep 2020 22:28:09 +0000 (15:28 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 2 Sep 2020 21:39:25 +0000 (14:39 -0700)
When a user checks out the upstream branch of HEAD, the upstream branch
not being a local branch, and then runs "git status", like this:

  git clone $URL client
  cd client
  git checkout @{u}
  git status

no status is printed, but instead an error message:

  fatal: HEAD does not point to a branch

(This error message when running "git branch" persists even after
checking out other things - it only stops after checking out a branch.)

This is because "git status" reads the reflog when determining the "HEAD
detached" message, and thus attempts to DWIM "@{u}", but that doesn't
work because HEAD no longer points to a branch.

Therefore, when calculating the status of a worktree, tolerate dangling
marks. This is done by adding an additional parameter to
dwim_ref() and repo_dwim_ref().

Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
19 files changed:
archive.c
branch.c
builtin/checkout.c
builtin/fast-export.c
builtin/log.c
builtin/merge.c
builtin/reset.c
builtin/rev-parse.c
builtin/show-branch.c
builtin/stash.c
bundle.c
cache.h
commit.c
refs.c
refs.h
remote.c
sha1-name.c
t/t7508-status.sh
wt-status.c

index fb39706120cdd6b668fbc10cefad608692587d09..0de6048bfccb8df89a1fce9abbeb3d0f65f004fc 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -397,10 +397,10 @@ static void parse_treeish_arg(const char **argv,
                const char *colon = strchrnul(name, ':');
                int refnamelen = colon - name;
 
-               if (!dwim_ref(name, refnamelen, &oid, &ref))
+               if (!dwim_ref(name, refnamelen, &oid, &ref, 0))
                        die(_("no such ref: %.*s"), refnamelen, name);
        } else {
-               dwim_ref(name, strlen(name), &oid, &ref);
+               dwim_ref(name, strlen(name), &oid, &ref, 0);
        }
 
        if (get_oid(name, &oid))
index 7095f7805869811c07aa25d9fbfd9c68ddc7d08d..9c9dae1eae321c6878607a3b624a187fcb176380 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -281,7 +281,7 @@ void create_branch(struct repository *r,
                die(_("Not a valid object name: '%s'."), start_name);
        }
 
-       switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref)) {
+       switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {
        case 0:
                /* Not branching from any existing branch */
                if (explicit_tracking)
index af849c644fec1852845b1dfdb6ce8daa903e0fb5..6350df83b51d6dba35bcae93d50bec9147e1c5e8 100644 (file)
@@ -650,7 +650,7 @@ static void setup_branch_path(struct branch_info *branch)
         * If this is a ref, resolve it; otherwise, look up the OID for our
         * expression.  Failure here is okay.
         */
-       if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+       if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname, 0))
                repo_get_oid_committish(the_repository, branch->name, &branch->oid);
 
        strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
@@ -1349,7 +1349,7 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
        struct object_id oid;
        char *to_free;
 
-       if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) {
+       if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
                const char *ref = to_free;
 
                if (skip_prefix(ref, "refs/tags/", &ref))
index 9f37895d4cf2ecb01c62e490be7a100ce5256398..1b8fca3ee000aa8f1fe49f407d538da09ac8a43b 100644 (file)
@@ -943,7 +943,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
                if (e->flags & UNINTERESTING)
                        continue;
 
-               if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1)
+               if (dwim_ref(e->name, strlen(e->name), &oid, &full_name, 0) != 1)
                        continue;
 
                if (refspecs.nr) {
index d104d5c6889ba2d2ffaaeafa08fbfcd759d8bc92..76578c46fd463ba475cb7b92e9b1cb670f7e84a0 100644 (file)
@@ -1062,7 +1062,7 @@ static char *find_branch_name(struct rev_info *rev)
                return NULL;
        ref = rev->cmdline.rev[positive].name;
        tip_oid = &rev->cmdline.rev[positive].item->oid;
-       if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) &&
+       if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref, 0) &&
            skip_prefix(full_ref, "refs/heads/", &v) &&
            oideq(tip_oid, &branch_oid))
                branch = xstrdup(v);
index 7da707bf55d94f0a151fb03954b3b107e408e551..835b2f497870d6a611421a067166e6bcb45bbe25 100644 (file)
@@ -501,7 +501,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
 
-       if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) {
+       if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref, 0) > 0) {
                if (starts_with(found_ref, "refs/heads/")) {
                        strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
                                    oid_to_hex(&branch_head), remote);
index 8ae69d6f2b9e5e1760dd40d32631b0ecf48a31e6..c635b062c3a7b1cf2ad53b34f1471f5eeee83cce 100644 (file)
@@ -423,7 +423,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                        char *ref = NULL;
                        int err;
 
-                       dwim_ref(rev, strlen(rev), &dummy, &ref);
+                       dwim_ref(rev, strlen(rev), &dummy, &ref, 0);
                        if (ref && !starts_with(ref, "refs/"))
                                ref = NULL;
 
index 669dd2fd6f087628c3d7dc9ba165771825418799..ed200c8af1285ed4ac45a50aaa6275a739585836 100644 (file)
@@ -136,7 +136,7 @@ static void show_rev(int type, const struct object_id *oid, const char *name)
                        struct object_id discard;
                        char *full;
 
-                       switch (dwim_ref(name, strlen(name), &discard, &full)) {
+                       switch (dwim_ref(name, strlen(name), &discard, &full, 0)) {
                        case 0:
                                /*
                                 * Not found -- not a ref.  We could
index 7e52ee91264a63713da5178add167f8d62c36877..a472862c303e87715c0b21b0c812ff7efad08c09 100644 (file)
@@ -741,7 +741,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        die(Q_("only %d entry can be shown at one time.",
                               "only %d entries can be shown at one time.",
                               MAX_REVS), MAX_REVS);
-               if (!dwim_ref(*av, strlen(*av), &oid, &ref))
+               if (!dwim_ref(*av, strlen(*av), &oid, &ref, 0))
                        die(_("no such ref %s"), *av);
 
                /* Has the base been specified? */
index 0c52a3b849c4c6f811995f3be89284052c2dd313..9312cd19b92df0f0610fb9bc436ad11a5a512a3d 100644 (file)
@@ -185,7 +185,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)
        end_of_rev = strchrnul(revision, '@');
        strbuf_add(&symbolic, revision, end_of_rev - revision);
 
-       ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
+       ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref, 0);
        strbuf_release(&symbolic);
        switch (ret) {
        case 0: /* Not found, but valid ref */
index 2a0d744d3fa51b2bbbb307a04f7e32a45b1b690d..b5f60fa7b2bbc0575b982aac6157d2e5b1ff7443 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -377,7 +377,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)
 
                if (e->item->flags & UNINTERESTING)
                        continue;
-               if (dwim_ref(e->name, strlen(e->name), &oid, &ref) != 1)
+               if (dwim_ref(e->name, strlen(e->name), &oid, &ref, 0) != 1)
                        goto skip_write_ref;
                if (read_ref_full(e->name, RESOLVE_REF_READING, &oid, &flag))
                        flag = 0;
diff --git a/cache.h b/cache.h
index 01a3da5192650a71fb27a7e5d78d15921fe08678..f0090a00210b4a34529c82f300b5465f23bd4046 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1567,6 +1567,13 @@ struct interpret_branch_name_options {
         * allowed, even ones to refs outside of those namespaces.
         */
        unsigned allowed;
+
+       /*
+        * If ^{upstream} or ^{push} (or equivalent) is requested, and the
+        * branch in question does not have such a reference, return -1 instead
+        * of die()-ing.
+        */
+       unsigned nonfatal_dangling_mark : 1;
 };
 int repo_interpret_branch_name(struct repository *r,
                               const char *str, int len,
index 7128895c3ad86c261b2260d7faba0f79f263b1ca..9b5a34cfd763b48514551d45f285fd1bbd259240 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -921,7 +921,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
        struct commit *ret = NULL;
        char *full_refname;
 
-       switch (dwim_ref(refname, strlen(refname), &oid, &full_refname)) {
+       switch (dwim_ref(refname, strlen(refname), &oid, &full_refname, 0)) {
        case 0:
                die("No such ref: '%s'", refname);
        case 1:
diff --git a/refs.c b/refs.c
index e107919b94305a9260b253d4bc70578f3e881b9b..ae27afc5e92b7d2b6c9a699f82e53ef496f4dbe0 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -598,10 +598,13 @@ const char *git_default_branch_name(void)
  * to name a branch.
  */
 static char *substitute_branch_name(struct repository *r,
-                                   const char **string, int *len)
+                                   const char **string, int *len,
+                                   int nonfatal_dangling_mark)
 {
        struct strbuf buf = STRBUF_INIT;
-       struct interpret_branch_name_options options = { 0 } ;
+       struct interpret_branch_name_options options = {
+               .nonfatal_dangling_mark = nonfatal_dangling_mark
+       };
        int ret = repo_interpret_branch_name(r, *string, *len, &buf, &options);
 
        if (ret == *len) {
@@ -615,9 +618,10 @@ static char *substitute_branch_name(struct repository *r,
 }
 
 int repo_dwim_ref(struct repository *r, const char *str, int len,
-                 struct object_id *oid, char **ref)
+                 struct object_id *oid, char **ref, int nonfatal_dangling_mark)
 {
-       char *last_branch = substitute_branch_name(r, &str, &len);
+       char *last_branch = substitute_branch_name(r, &str, &len,
+                                                  nonfatal_dangling_mark);
        int   refs_found  = expand_ref(r, str, len, oid, ref);
        free(last_branch);
        return refs_found;
@@ -661,7 +665,7 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
                  struct object_id *oid, char **log)
 {
        struct ref_store *refs = get_main_ref_store(r);
-       char *last_branch = substitute_branch_name(r, &str, &len);
+       char *last_branch = substitute_branch_name(r, &str, &len, 0);
        const char **p;
        int logs_found = 0;
        struct strbuf path = STRBUF_INIT;
diff --git a/refs.h b/refs.h
index 04a68540c33be812b04cc71b04b8ecb2c120bf63..411a68a0e54e2f5f08bbf1c39f365bdac167ce80 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -151,12 +151,14 @@ struct argv_array;
 void expand_ref_prefix(struct argv_array *prefixes, const char *prefix);
 
 int expand_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
-int repo_dwim_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
+int repo_dwim_ref(struct repository *r, const char *str, int len,
+                 struct object_id *oid, char **ref, int nonfatal_dangling_mark);
 int repo_dwim_log(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
 static inline int dwim_ref(const char *str, int len, struct object_id *oid,
-                          char **ref)
+                          char **ref, int nonfatal_dangling_mark)
 {
-       return repo_dwim_ref(the_repository, str, len, oid, ref);
+       return repo_dwim_ref(the_repository, str, len, oid, ref,
+                            nonfatal_dangling_mark);
 }
 int dwim_log(const char *str, int len, struct object_id *oid, char **ref);
 
index bc46413e6a75788da66b5c0c6bec9aa5a52c5403..b7460bd083b763c032722767f9e92a62697fe8f2 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1558,7 +1558,7 @@ static void set_merge(struct branch *ret)
                    strcmp(ret->remote_name, "."))
                        continue;
                if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
-                            &oid, &ref) == 1)
+                            &oid, &ref, 0) == 1)
                        ret->merge[i]->dst = ref;
                else
                        ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
index a7a9de66c4577540799fad3230c65c9e17fbe583..0b23b86ceb4433ece828e780be180579a77f3fe8 100644 (file)
@@ -809,7 +809,7 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
 
        if (len == r->hash_algo->hexsz && !get_oid_hex(str, oid)) {
                if (warn_ambiguous_refs && warn_on_object_refname_ambiguity) {
-                       refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref);
+                       refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0);
                        if (refs_found > 0) {
                                warning(warn_msg, len, str);
                                if (advice_object_name_warning)
@@ -860,11 +860,11 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
 
        if (!len && reflog_len)
                /* allow "@{...}" to mean the current branch reflog */
-               refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref);
+               refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref, 0);
        else if (reflog_len)
                refs_found = repo_dwim_log(r, str, len, oid, &real_ref);
        else
-               refs_found = repo_dwim_ref(r, str, len, oid, &real_ref);
+               refs_found = repo_dwim_ref(r, str, len, oid, &real_ref, 0);
 
        if (!refs_found)
                return -1;
@@ -1496,8 +1496,14 @@ static int interpret_branch_mark(struct repository *r,
                branch = branch_get(NULL);
 
        value = get_data(branch, &err);
-       if (!value)
-               die("%s", err.buf);
+       if (!value) {
+               if (options->nonfatal_dangling_mark) {
+                       strbuf_release(&err);
+                       return -1;
+               } else {
+                       die("%s", err.buf);
+               }
+       }
 
        if (!branch_interpret_allowed(value, options->allowed))
                return -1;
index 8e969f3e3680d856073460b5b15e524ad9170d7f..2e566a9c32678f8e326708bb5d6287045ae8e552 100755 (executable)
@@ -846,6 +846,18 @@ test_expect_success 'status refreshes the index' '
        test_cmp expect output
 '
 
+test_expect_success 'status shows detached HEAD properly after checking out non-local upstream branch' '
+       test_when_finished rm -rf upstream downstream actual &&
+
+       test_create_repo upstream &&
+       test_commit -C upstream foo &&
+
+       git clone upstream downstream &&
+       git -C downstream checkout @{u} &&
+       git -C downstream status >actual &&
+       test_i18ngrep "HEAD detached at [0-9a-f]\\+" actual
+'
+
 test_expect_success 'setup status submodule summary' '
        test_create_repo sm && (
                cd sm &&
index c560cbe860a42daa136e16195535ef10e2ab0a9f..6cb519ec07708ef04e68a3a56936e511d49b3cca 100644 (file)
@@ -1574,7 +1574,7 @@ static void wt_status_get_detached_from(struct repository *r,
                return;
        }
 
-       if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref) == 1 &&
+       if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref, 1) == 1 &&
            /* sha1 is a commit? match without further lookup */
            (oideq(&cb.noid, &oid) ||
             /* perhaps sha1 is a tag, try to dereference to a commit */