diff --git a/archive.c b/archive.c index fb39706120..0de6048bfc 100644 --- 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)) diff --git a/branch.c b/branch.c index 7095f78058..9c9dae1eae 100644 --- 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) diff --git a/builtin/checkout.c b/builtin/checkout.c index 6ec82097a2..0951f8fee5 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -651,7 +651,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); @@ -1345,7 +1345,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)) diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9f37895d4c..1b8fca3ee0 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -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) { diff --git a/builtin/log.c b/builtin/log.c index b58f8da09e..4ec7f57cf4 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1061,7 +1061,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); diff --git a/builtin/merge.c b/builtin/merge.c index b9a89ba858..032a8f5434 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -500,7 +500,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); diff --git a/builtin/reset.c b/builtin/reset.c index 8ae69d6f2b..c635b062c3 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -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; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 669dd2fd6f..ed200c8af1 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -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 diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 7eae5f3801..d6d2dabeca 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -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? */ diff --git a/builtin/stash.c b/builtin/stash.c index 4bdfaf8397..3f811f3050 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -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 */ diff --git a/bundle.c b/bundle.c index 995a940dfd..cb0e5931ac 100644 --- a/bundle.c +++ b/bundle.c @@ -403,7 +403,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 4cad61ffa4..cee8aa5dc3 100644 --- a/cache.h +++ b/cache.h @@ -1557,21 +1557,32 @@ int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end); * * If the input was ok but there are not N branch switches in the * reflog, it returns 0. - * - * If "allowed" is non-zero, it is a treated as a bitfield of allowable - * expansions: local branches ("refs/heads/"), remote branches - * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is - * allowed, even ones to refs outside of those namespaces. */ #define INTERPRET_BRANCH_LOCAL (1<<0) #define INTERPRET_BRANCH_REMOTE (1<<1) #define INTERPRET_BRANCH_HEAD (1<<2) +struct interpret_branch_name_options { + /* + * If "allowed" is non-zero, it is a treated as a bitfield of allowable + * expansions: local branches ("refs/heads/"), remote branches + * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is + * 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, struct strbuf *buf, - unsigned allowed); -#define interpret_branch_name(str, len, buf, allowed) \ - repo_interpret_branch_name(the_repository, str, len, buf, allowed) + const struct interpret_branch_name_options *options); +#define interpret_branch_name(str, len, buf, options) \ + repo_interpret_branch_name(the_repository, str, len, buf, options) int validate_headref(const char *ref); diff --git a/commit.c b/commit.c index bc741e78ad..f53429c0ac 100644 --- 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 156fdcd459..8374dfefa6 100644 --- a/refs.c +++ b/refs.c @@ -598,10 +598,14 @@ 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; - int ret = repo_interpret_branch_name(r, *string, *len, &buf, 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) { size_t size; @@ -614,19 +618,15 @@ 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; } -int dwim_ref(const char *str, int len, struct object_id *oid, char **ref) -{ - return repo_dwim_ref(the_repository, str, len, oid, ref); -} - int expand_ref(struct repository *repo, const char *str, int len, struct object_id *oid, char **ref) { @@ -665,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 04bd25019f..6695518156 100644 --- a/refs.h +++ b/refs.h @@ -1,6 +1,8 @@ #ifndef REFS_H #define REFS_H +#include "cache.h" + struct object_id; struct ref_store; struct repository; @@ -151,9 +153,15 @@ struct strvec; void expand_ref_prefix(struct strvec *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); -int dwim_ref(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, int nonfatal_dangling_mark) +{ + 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); /* diff --git a/remote.c b/remote.c index c5ed74f91c..420150837b 100644 --- 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]); diff --git a/revision.c b/revision.c index 0d67b842fd..1239023f93 100644 --- a/revision.c +++ b/revision.c @@ -315,13 +315,14 @@ static void add_pending_object_with_path(struct rev_info *revs, const char *name, unsigned mode, const char *path) { + struct interpret_branch_name_options options = { 0 }; if (!obj) return; if (revs->no_walk && (obj->flags & UNINTERESTING)) revs->no_walk = 0; if (revs->reflog_info && obj->type == OBJ_COMMIT) { struct strbuf buf = STRBUF_INIT; - int len = interpret_branch_name(name, 0, &buf, 0); + int len = interpret_branch_name(name, 0, &buf, &options); if (0 < len && name[len] && buf.len) strbuf_addstr(&buf, name + len); diff --git a/sha1-name.c b/sha1-name.c index 0b8cb5247a..0b23b86ceb 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -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; @@ -1427,9 +1427,12 @@ static int reinterpret(struct repository *r, struct strbuf tmp = STRBUF_INIT; int used = buf->len; int ret; + struct interpret_branch_name_options options = { + .allowed = allowed + }; strbuf_add(buf, name + len, namelen - len); - ret = repo_interpret_branch_name(r, buf->buf, buf->len, &tmp, allowed); + ret = repo_interpret_branch_name(r, buf->buf, buf->len, &tmp, &options); /* that data was not interpreted, remove our cruft */ if (ret < 0) { strbuf_setlen(buf, used); @@ -1471,7 +1474,7 @@ static int interpret_branch_mark(struct repository *r, int (*get_mark)(const char *, int), const char *(*get_data)(struct branch *, struct strbuf *), - unsigned allowed) + const struct interpret_branch_name_options *options) { int len; struct branch *branch; @@ -1493,10 +1496,16 @@ 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, allowed)) + if (!branch_interpret_allowed(value, options->allowed)) return -1; set_shortened_ref(r, buf, value); @@ -1506,7 +1515,7 @@ static int interpret_branch_mark(struct repository *r, int repo_interpret_branch_name(struct repository *r, const char *name, int namelen, struct strbuf *buf, - unsigned allowed) + const struct interpret_branch_name_options *options) { char *at; const char *start; @@ -1515,7 +1524,7 @@ int repo_interpret_branch_name(struct repository *r, if (!namelen) namelen = strlen(name); - if (!allowed || (allowed & INTERPRET_BRANCH_LOCAL)) { + if (!options->allowed || (options->allowed & INTERPRET_BRANCH_LOCAL)) { len = interpret_nth_prior_checkout(r, name, namelen, buf); if (!len) { return len; /* syntax Ok, not enough switches */ @@ -1523,7 +1532,8 @@ int repo_interpret_branch_name(struct repository *r, if (len == namelen) return len; /* consumed all */ else - return reinterpret(r, name, namelen, len, buf, allowed); + return reinterpret(r, name, namelen, len, buf, + options->allowed); } } @@ -1531,22 +1541,22 @@ int repo_interpret_branch_name(struct repository *r, (at = memchr(start, '@', namelen - (start - name))); start = at + 1) { - if (!allowed || (allowed & INTERPRET_BRANCH_HEAD)) { + if (!options->allowed || (options->allowed & INTERPRET_BRANCH_HEAD)) { len = interpret_empty_at(name, namelen, at - name, buf); if (len > 0) return reinterpret(r, name, namelen, len, buf, - allowed); + options->allowed); } len = interpret_branch_mark(r, name, namelen, at - name, buf, upstream_mark, branch_get_upstream, - allowed); + options); if (len > 0) return len; len = interpret_branch_mark(r, name, namelen, at - name, buf, push_mark, branch_get_push, - allowed); + options); if (len > 0) return len; } @@ -1557,7 +1567,10 @@ int repo_interpret_branch_name(struct repository *r, void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed) { int len = strlen(name); - int used = interpret_branch_name(name, len, sb, allowed); + struct interpret_branch_name_options options = { + .allowed = allowed + }; + int used = interpret_branch_name(name, len, sb, &options); if (used < 0) used = 0; diff --git a/t/t7508-status.sh b/t/t7508-status.sh index e81759319f..45e1f6ff68 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -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 && diff --git a/wt-status.c b/wt-status.c index bb0f9120de..d9872d543b 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1569,7 +1569,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 */