diff options
-rw-r--r-- | Documentation/git-for-each-ref.txt | 6 | ||||
-rw-r--r-- | Documentation/revisions.txt | 25 | ||||
-rw-r--r-- | Documentation/technical/api-remote.txt | 4 | ||||
-rw-r--r-- | builtin/branch.c | 24 | ||||
-rw-r--r-- | builtin/for-each-ref.c | 32 | ||||
-rw-r--r-- | builtin/log.c | 7 | ||||
-rw-r--r-- | builtin/merge.c | 2 | ||||
-rw-r--r-- | remote.c | 262 | ||||
-rw-r--r-- | remote.h | 28 | ||||
-rw-r--r-- | sha1_name.c | 81 | ||||
-rwxr-xr-x | t/t1507-rev-parse-upstream.sh | 8 | ||||
-rwxr-xr-x | t/t1514-rev-parse-push.sh | 63 | ||||
-rwxr-xr-x | t/t6300-for-each-ref.sh | 13 | ||||
-rw-r--r-- | wt-status.c | 18 |
14 files changed, 424 insertions, 149 deletions
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 42408752d0..7f8d9a5b5f 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -97,6 +97,12 @@ upstream:: or "=" (in sync). Has no effect if the ref does not have tracking information associated with it. +push:: + The name of a local ref which represents the `@{push}` location + for the displayed ref. Respects `:short`, `:track`, and + `:trackshort` options as `upstream` does. Produces an empty + string if no `@{push}` ref is configured. + HEAD:: '*' if HEAD matches current ref (the checked out branch), ' ' otherwise. diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index 07961185fe..d85e303364 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -98,6 +98,31 @@ some output processing may assume ref names in UTF-8. `branch.<name>.merge`). A missing branchname defaults to the current one. +'<branchname>@\{push\}', e.g. 'master@\{push\}', '@\{push\}':: + The suffix '@\{push}' reports the branch "where we would push to" if + `git push` were run while `branchname` was checked out (or the current + 'HEAD' if no branchname is specified). Since our push destination is + in a remote repository, of course, we report the local tracking branch + that corresponds to that branch (i.e., something in 'refs/remotes/'). ++ +Here's an example to make it more clear: ++ +------------------------------ +$ git config push.default current +$ git config remote.pushdefault myfork +$ git checkout -b mybranch origin/master + +$ git rev-parse --symbolic-full-name @{upstream} +refs/remotes/origin/master + +$ git rev-parse --symbolic-full-name @{push} +refs/remotes/myfork/mybranch +------------------------------ ++ +Note in the example that we set up a triangular workflow, where we pull +from one location and push to another. In a non-triangular workflow, +'@\{push}' is the same as '@\{upstream}', and there is no need for it. + '<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0':: A suffix '{caret}' to a revision parameter means the first parent of that commit object. '{caret}<n>' means the <n>th parent (i.e. diff --git a/Documentation/technical/api-remote.txt b/Documentation/technical/api-remote.txt index 5d245aa9d1..2cfdd224a8 100644 --- a/Documentation/technical/api-remote.txt +++ b/Documentation/technical/api-remote.txt @@ -97,10 +97,6 @@ It contains: The name of the remote listed in the configuration. -`remote`:: - - The struct remote for that remote. - `merge_name`:: An array of the "merge" lines in the configuration. diff --git a/builtin/branch.c b/builtin/branch.c index 9cbab189f5..c70085c35f 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -123,14 +123,12 @@ static int branch_merged(int kind, const char *name, if (kind == REF_LOCAL_BRANCH) { struct branch *branch = branch_get(name); + const char *upstream = branch_get_upstream(branch, NULL); unsigned char sha1[20]; - if (branch && - branch->merge && - branch->merge[0] && - branch->merge[0]->dst && + if (upstream && (reference_name = reference_name_to_free = - resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING, + resolve_refdup(upstream, RESOLVE_REF_READING, sha1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } @@ -427,25 +425,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int ours, theirs; char *ref = NULL; struct branch *branch = branch_get(branch_name); + const char *upstream; struct strbuf fancy = STRBUF_INIT; int upstream_is_gone = 0; int added_decoration = 1; - switch (stat_tracking_info(branch, &ours, &theirs)) { - case 0: - /* no base */ - return; - case -1: - /* with "gone" base */ + if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) { + if (!upstream) + return; upstream_is_gone = 1; - break; - default: - /* with base */ - break; } if (show_upstream_ref) { - ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + ref = shorten_unambiguous_ref(upstream, 0); if (want_color(branch_use_color)) strbuf_addf(&fancy, "%s%s%s", branch_get_color(BRANCH_COLOR_UPSTREAM), diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 83f9cf9163..05dd23d2a3 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -74,6 +74,7 @@ static struct { { "contents:body" }, { "contents:signature" }, { "upstream" }, + { "push" }, { "symref" }, { "flag" }, { "HEAD" }, @@ -659,15 +660,26 @@ static void populate_value(struct refinfo *ref) else if (starts_with(name, "symref")) refname = ref->symref ? ref->symref : ""; else if (starts_with(name, "upstream")) { + const char *branch_name; /* only local branches may have an upstream */ - if (!starts_with(ref->refname, "refs/heads/")) + if (!skip_prefix(ref->refname, "refs/heads/", + &branch_name)) continue; - branch = branch_get(ref->refname + 11); + branch = branch_get(branch_name); - if (!branch || !branch->merge || !branch->merge[0] || - !branch->merge[0]->dst) + refname = branch_get_upstream(branch, NULL); + if (!refname) + continue; + } else if (starts_with(name, "push")) { + const char *branch_name; + if (!skip_prefix(ref->refname, "refs/heads/", + &branch_name)) + continue; + branch = branch_get(branch_name); + + refname = branch_get_push(branch, NULL); + if (!refname) continue; - refname = branch->merge[0]->dst; } else if (starts_with(name, "color:")) { char color[COLOR_MAXLEN] = ""; @@ -713,11 +725,12 @@ static void populate_value(struct refinfo *ref) refname = shorten_unambiguous_ref(refname, warn_ambiguous_refs); else if (!strcmp(formatp, "track") && - starts_with(name, "upstream")) { + (starts_with(name, "upstream") || + starts_with(name, "push"))) { char buf[40]; if (stat_tracking_info(branch, &num_ours, - &num_theirs) != 1) + &num_theirs, NULL)) continue; if (!num_ours && !num_theirs) @@ -735,11 +748,12 @@ static void populate_value(struct refinfo *ref) } continue; } else if (!strcmp(formatp, "trackshort") && - starts_with(name, "upstream")) { + (starts_with(name, "upstream") || + starts_with(name, "push"))) { assert(branch); if (stat_tracking_info(branch, &num_ours, - &num_theirs) != 1) + &num_theirs, NULL)) continue; if (!num_ours && !num_theirs) diff --git a/builtin/log.c b/builtin/log.c index 4c4e6be28c..e67671e37a 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1632,16 +1632,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) break; default: current_branch = branch_get(NULL); - if (!current_branch || !current_branch->merge - || !current_branch->merge[0] - || !current_branch->merge[0]->dst) { + upstream = branch_get_upstream(current_branch, NULL); + if (!upstream) { fprintf(stderr, _("Could not find a tracked" " remote branch, please" " specify <upstream> manually.\n")); usage_with_options(cherry_usage, options); } - - upstream = current_branch->merge[0]->dst; } init_revisions(&revs, prefix); diff --git a/builtin/merge.c b/builtin/merge.c index f89f60e11a..85c54dcd5a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -933,7 +933,7 @@ static int setup_with_upstream(const char ***argv) if (!branch) die(_("No current branch.")); - if (!branch->remote) + if (!branch->remote_name) die(_("No remote for the current branch.")); if (!branch->merge_nr) die(_("No default upstream defined for the current branch.")); @@ -49,10 +49,7 @@ static int branches_alloc; static int branches_nr; static struct branch *current_branch; -static const char *default_remote_name; -static const char *branch_pushremote_name; static const char *pushremote_name; -static int explicit_default_remote_name; static struct rewrites rewrites; static struct rewrites rewrites_push; @@ -367,16 +364,9 @@ static int handle_config(const char *key, const char *value, void *cb) return 0; branch = make_branch(name, subkey - name); if (!strcmp(subkey, ".remote")) { - if (git_config_string(&branch->remote_name, key, value)) - return -1; - if (branch == current_branch) { - default_remote_name = branch->remote_name; - explicit_default_remote_name = 1; - } + return git_config_string(&branch->remote_name, key, value); } else if (!strcmp(subkey, ".pushremote")) { - if (branch == current_branch) - if (git_config_string(&branch_pushremote_name, key, value)) - return -1; + return git_config_string(&branch->pushremote_name, key, value); } else if (!strcmp(subkey, ".merge")) { if (!value) return config_error_nonbool(key); @@ -501,12 +491,15 @@ static void alias_all_urls(void) static void read_config(void) { + static int loaded; unsigned char sha1[20]; const char *head_ref; int flag; - if (default_remote_name) /* did this already */ + + if (loaded) return; - default_remote_name = "origin"; + loaded = 1; + current_branch = NULL; head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag); if (head_ref && (flag & REF_ISSYMREF) && @@ -514,10 +507,6 @@ static void read_config(void) current_branch = make_branch(head_ref, 0); } git_config(handle_config, NULL); - if (branch_pushremote_name) { - free((char *)pushremote_name); - pushremote_name = branch_pushremote_name; - } alias_all_urls(); } @@ -696,22 +685,45 @@ static int valid_remote_nick(const char *name) return !strchr(name, '/'); /* no slash */ } -static struct remote *remote_get_1(const char *name, const char *pushremote_name) +const char *remote_for_branch(struct branch *branch, int *explicit) +{ + if (branch && branch->remote_name) { + if (explicit) + *explicit = 1; + return branch->remote_name; + } + if (explicit) + *explicit = 0; + return "origin"; +} + +const char *pushremote_for_branch(struct branch *branch, int *explicit) +{ + if (branch && branch->pushremote_name) { + if (explicit) + *explicit = 1; + return branch->pushremote_name; + } + if (pushremote_name) { + if (explicit) + *explicit = 1; + return pushremote_name; + } + return remote_for_branch(branch, explicit); +} + +static struct remote *remote_get_1(const char *name, + const char *(*get_default)(struct branch *, int *)) { struct remote *ret; int name_given = 0; + read_config(); + if (name) name_given = 1; - else { - if (pushremote_name) { - name = pushremote_name; - name_given = 1; - } else { - name = default_remote_name; - name_given = explicit_default_remote_name; - } - } + else + name = get_default(current_branch, &name_given); ret = make_remote(name, 0); if (valid_remote_nick(name)) { @@ -731,14 +743,12 @@ static struct remote *remote_get_1(const char *name, const char *pushremote_name struct remote *remote_get(const char *name) { - read_config(); - return remote_get_1(name, NULL); + return remote_get_1(name, remote_for_branch); } struct remote *pushremote_get(const char *name) { - read_config(); - return remote_get_1(name, pushremote_name); + return remote_get_1(name, pushremote_for_branch); } int remote_is_configured(const char *name) @@ -1633,15 +1643,31 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, static void set_merge(struct branch *ret) { + struct remote *remote; char *ref; unsigned char sha1[20]; int i; + if (!ret) + return; /* no branch */ + if (ret->merge) + return; /* already run */ + if (!ret->remote_name || !ret->merge_nr) { + /* + * no merge config; let's make sure we don't confuse callers + * with a non-zero merge_nr but a NULL merge + */ + ret->merge_nr = 0; + return; + } + + remote = remote_get(ret->remote_name); + ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge)); for (i = 0; i < ret->merge_nr; i++) { ret->merge[i] = xcalloc(1, sizeof(**ret->merge)); ret->merge[i]->src = xstrdup(ret->merge_name[i]); - if (!remote_find_tracking(ret->remote, ret->merge[i]) || + if (!remote_find_tracking(remote, ret->merge[i]) || strcmp(ret->remote_name, ".")) continue; if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]), @@ -1661,11 +1687,7 @@ struct branch *branch_get(const char *name) ret = current_branch; else ret = make_branch(name, 0); - if (ret && ret->remote_name) { - ret->remote = remote_get(ret->remote_name); - if (ret->merge_nr) - set_merge(ret); - } + set_merge(ret); return ret; } @@ -1683,6 +1705,130 @@ int branch_merge_matches(struct branch *branch, return refname_match(branch->merge[i]->src, refname); } +__attribute((format (printf,2,3))) +static const char *error_buf(struct strbuf *err, const char *fmt, ...) +{ + if (err) { + va_list ap; + va_start(ap, fmt); + strbuf_vaddf(err, fmt, ap); + va_end(ap); + } + return NULL; +} + +const char *branch_get_upstream(struct branch *branch, struct strbuf *err) +{ + if (!branch) + return error_buf(err, _("HEAD does not point to a branch")); + + if (!branch->merge || !branch->merge[0]) { + /* + * no merge config; is it because the user didn't define any, + * or because it is not a real branch, and get_branch + * auto-vivified it? + */ + if (!ref_exists(branch->refname)) + return error_buf(err, _("no such branch: '%s'"), + branch->name); + return error_buf(err, + _("no upstream configured for branch '%s'"), + branch->name); + } + + if (!branch->merge[0]->dst) + return error_buf(err, + _("upstream branch '%s' not stored as a remote-tracking branch"), + branch->merge[0]->src); + + return branch->merge[0]->dst; +} + +static const char *tracking_for_push_dest(struct remote *remote, + const char *refname, + struct strbuf *err) +{ + char *ret; + + ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname); + if (!ret) + return error_buf(err, + _("push destination '%s' on remote '%s' has no local tracking branch"), + refname, remote->name); + return ret; +} + +static const char *branch_get_push_1(struct branch *branch, struct strbuf *err) +{ + struct remote *remote; + + if (!branch) + return error_buf(err, _("HEAD does not point to a branch")); + + remote = remote_get(pushremote_for_branch(branch, NULL)); + if (!remote) + return error_buf(err, + _("branch '%s' has no remote for pushing"), + branch->name); + + if (remote->push_refspec_nr) { + char *dst; + const char *ret; + + dst = apply_refspecs(remote->push, remote->push_refspec_nr, + branch->refname); + if (!dst) + return error_buf(err, + _("push refspecs for '%s' do not include '%s'"), + remote->name, branch->name); + + ret = tracking_for_push_dest(remote, dst, err); + free(dst); + return ret; + } + + if (remote->mirror) + return tracking_for_push_dest(remote, branch->refname, err); + + switch (push_default) { + case PUSH_DEFAULT_NOTHING: + return error_buf(err, _("push has no destination (push.default is 'nothing')")); + + case PUSH_DEFAULT_MATCHING: + case PUSH_DEFAULT_CURRENT: + return tracking_for_push_dest(remote, branch->refname, err); + + case PUSH_DEFAULT_UPSTREAM: + return branch_get_upstream(branch, err); + + case PUSH_DEFAULT_UNSPECIFIED: + case PUSH_DEFAULT_SIMPLE: + { + const char *up, *cur; + + up = branch_get_upstream(branch, err); + if (!up) + return NULL; + cur = tracking_for_push_dest(remote, branch->refname, err); + if (!cur) + return NULL; + if (strcmp(cur, up)) + return error_buf(err, + _("cannot resolve 'simple' push to a single destination")); + return cur; + } + } + + die("BUG: unhandled push situation"); +} + +const char *branch_get_push(struct branch *branch, struct strbuf *err) +{ + if (!branch->push_tracking_ref) + branch->push_tracking_ref = branch_get_push_1(branch, err); + return branch->push_tracking_ref; +} + static int ignore_symref_update(const char *refname) { unsigned char sha1[20]; @@ -1877,12 +2023,15 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1) /* * Compare a branch with its upstream, and save their differences (number - * of commits) in *num_ours and *num_theirs. + * of commits) in *num_ours and *num_theirs. The name of the upstream branch + * (or NULL if no upstream is defined) is returned via *upstream_name, if it + * is not itself NULL. * - * Return 0 if branch has no upstream (no base), -1 if upstream is missing - * (with "gone" base), otherwise 1 (with base). + * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no + * upstream defined, or ref does not exist), 0 otherwise. */ -int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, + const char **upstream_name) { unsigned char sha1[20]; struct commit *ours, *theirs; @@ -1892,12 +2041,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) int rev_argc; /* Cannot stat unless we are marked to build on top of somebody else. */ - if (!branch || - !branch->merge || !branch->merge[0] || !branch->merge[0]->dst) - return 0; + base = branch_get_upstream(branch, NULL); + if (upstream_name) + *upstream_name = base; + if (!base) + return -1; /* Cannot stat if what we used to build on no longer exists */ - base = branch->merge[0]->dst; if (read_ref(base, sha1)) return -1; theirs = lookup_commit_reference(sha1); @@ -1913,7 +2063,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) /* are we the same? */ if (theirs == ours) { *num_theirs = *num_ours = 0; - return 1; + return 0; } /* Run "rev-list --left-right ours...theirs" internally... */ @@ -1949,7 +2099,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) /* clear object flags smudged by the above traversal */ clear_commit_marks(ours, ALL_REV_FLAGS); clear_commit_marks(theirs, ALL_REV_FLAGS); - return 1; + return 0; } /* @@ -1958,23 +2108,17 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) int format_tracking_info(struct branch *branch, struct strbuf *sb) { int ours, theirs; + const char *full_base; char *base; int upstream_is_gone = 0; - switch (stat_tracking_info(branch, &ours, &theirs)) { - case 0: - /* no base */ - return 0; - case -1: - /* with "gone" base */ + if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) { + if (!full_base) + return 0; upstream_is_gone = 1; - break; - default: - /* with base */ - break; } - base = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + base = shorten_unambiguous_ref(full_base, 0); if (upstream_is_gone) { strbuf_addf(sb, _("Your branch is based on '%s', but the upstream is gone.\n"), @@ -203,19 +203,42 @@ struct branch { const char *refname; const char *remote_name; - struct remote *remote; + const char *pushremote_name; const char **merge_name; struct refspec **merge; int merge_nr; int merge_alloc; + + const char *push_tracking_ref; }; struct branch *branch_get(const char *name); +const char *remote_for_branch(struct branch *branch, int *explicit); +const char *pushremote_for_branch(struct branch *branch, int *explicit); int branch_has_merge_config(struct branch *branch); int branch_merge_matches(struct branch *, int n, const char *); +/** + * Return the fully-qualified refname of the tracking branch for `branch`. + * I.e., what "branch@{upstream}" would give you. Returns NULL if no + * upstream is defined. + * + * If `err` is not NULL and no upstream is defined, a more specific error + * message is recorded there (if the function does not return NULL, then + * `err` is not touched). + */ +const char *branch_get_upstream(struct branch *branch, struct strbuf *err); + +/** + * Return the tracking branch that corresponds to the ref we would push to + * given a bare `git push` while `branch` is checked out. + * + * The return value and `err` conventions match those of `branch_get_upstream`. + */ +const char *branch_get_push(struct branch *branch, struct strbuf *err); + /* Flags to match_refs. */ enum match_refs_flags { MATCH_REFS_NONE = 0, @@ -226,7 +249,8 @@ enum match_refs_flags { }; /* Reporting of tracking info */ -int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs); +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, + const char **upstream_name); int format_tracking_info(struct branch *branch, struct strbuf *sb); struct ref *get_local_heads(void); diff --git a/sha1_name.c b/sha1_name.c index 46218ba02c..4f3c142c7a 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -416,12 +416,12 @@ static int ambiguous_path(const char *path, int len) return slash; } -static inline int upstream_mark(const char *string, int len) +static inline int at_mark(const char *string, int len, + const char **suffix, int nr) { - const char *suffix[] = { "@{upstream}", "@{u}" }; int i; - for (i = 0; i < ARRAY_SIZE(suffix); i++) { + for (i = 0; i < nr; i++) { int suffix_len = strlen(suffix[i]); if (suffix_len <= len && !memcmp(string, suffix[i], suffix_len)) @@ -430,6 +430,18 @@ static inline int upstream_mark(const char *string, int len) return 0; } +static inline int upstream_mark(const char *string, int len) +{ + const char *suffix[] = { "@{upstream}", "@{u}" }; + return at_mark(string, len, suffix, ARRAY_SIZE(suffix)); +} + +static inline int push_mark(const char *string, int len) +{ + const char *suffix[] = { "@{push}" }; + return at_mark(string, len, suffix, ARRAY_SIZE(suffix)); +} + static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags); static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf); @@ -477,7 +489,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1, nth_prior = 1; continue; } - if (!upstream_mark(str + at, len - at)) { + if (!upstream_mark(str + at, len - at) && + !push_mark(str + at, len - at)) { reflog_len = (len-1) - (at+2); len = at; } @@ -1056,46 +1069,36 @@ static void set_shortened_ref(struct strbuf *buf, const char *ref) free(s); } -static const char *get_upstream_branch(const char *branch_buf, int len) -{ - char *branch = xstrndup(branch_buf, len); - struct branch *upstream = branch_get(*branch ? branch : NULL); - - /* - * Upstream can be NULL only if branch refers to HEAD and HEAD - * points to something different than a branch. - */ - if (!upstream) - die(_("HEAD does not point to a branch")); - if (!upstream->merge || !upstream->merge[0]->dst) { - if (!ref_exists(upstream->refname)) - die(_("No such branch: '%s'"), branch); - if (!upstream->merge) { - die(_("No upstream configured for branch '%s'"), - upstream->name); - } - die( - _("Upstream branch '%s' not stored as a remote-tracking branch"), - upstream->merge[0]->src); - } - free(branch); - - return upstream->merge[0]->dst; -} - -static int interpret_upstream_mark(const char *name, int namelen, - int at, struct strbuf *buf) +static int interpret_branch_mark(const char *name, int namelen, + int at, struct strbuf *buf, + int (*get_mark)(const char *, int), + const char *(*get_data)(struct branch *, + struct strbuf *)) { int len; + struct branch *branch; + struct strbuf err = STRBUF_INIT; + const char *value; - len = upstream_mark(name + at, namelen - at); + len = get_mark(name + at, namelen - at); if (!len) return -1; if (memchr(name, ':', at)) return -1; - set_shortened_ref(buf, get_upstream_branch(name, at)); + if (at) { + char *name_str = xmemdupz(name, at); + branch = branch_get(name_str); + free(name_str); + } else + branch = branch_get(NULL); + + value = get_data(branch, &err); + if (!value) + die("%s", err.buf); + + set_shortened_ref(buf, value); return len + at; } @@ -1146,7 +1149,13 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf) if (len > 0) return reinterpret(name, namelen, len, buf); - len = interpret_upstream_mark(name, namelen, at - name, buf); + len = interpret_branch_mark(name, namelen, at - name, buf, + upstream_mark, branch_get_upstream); + if (len > 0) + return len; + + len = interpret_branch_mark(name, namelen, at - name, buf, + push_mark, branch_get_push); if (len > 0) return len; } diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh index 1978947c41..46ef1f22dc 100755 --- a/t/t1507-rev-parse-upstream.sh +++ b/t/t1507-rev-parse-upstream.sh @@ -150,7 +150,7 @@ test_expect_success 'branch@{u} works when tracking a local branch' ' test_expect_success 'branch@{u} error message when no upstream' ' cat >expect <<-EOF && - fatal: No upstream configured for branch ${sq}non-tracking${sq} + fatal: no upstream configured for branch ${sq}non-tracking${sq} EOF error_message non-tracking@{u} 2>actual && test_i18ncmp expect actual @@ -158,7 +158,7 @@ test_expect_success 'branch@{u} error message when no upstream' ' test_expect_success '@{u} error message when no upstream' ' cat >expect <<-EOF && - fatal: No upstream configured for branch ${sq}master${sq} + fatal: no upstream configured for branch ${sq}master${sq} EOF test_must_fail git rev-parse --verify @{u} 2>actual && test_i18ncmp expect actual @@ -166,7 +166,7 @@ test_expect_success '@{u} error message when no upstream' ' test_expect_success 'branch@{u} error message with misspelt branch' ' cat >expect <<-EOF && - fatal: No such branch: ${sq}no-such-branch${sq} + fatal: no such branch: ${sq}no-such-branch${sq} EOF error_message no-such-branch@{u} 2>actual && test_i18ncmp expect actual @@ -183,7 +183,7 @@ test_expect_success '@{u} error message when not on a branch' ' test_expect_success 'branch@{u} error message if upstream branch not fetched' ' cat >expect <<-EOF && - fatal: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch + fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch EOF error_message bad-upstream@{u} 2>actual && test_i18ncmp expect actual diff --git a/t/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh new file mode 100755 index 0000000000..7214f5b33f --- /dev/null +++ b/t/t1514-rev-parse-push.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +test_description='test <branch>@{push} syntax' +. ./test-lib.sh + +resolve () { + echo "$2" >expect && + git rev-parse --symbolic-full-name "$1" >actual && + test_cmp expect actual +} + +test_expect_success 'setup' ' + git init --bare parent.git && + git init --bare other.git && + git remote add origin parent.git && + git remote add other other.git && + test_commit base && + git push origin HEAD && + git branch --set-upstream-to=origin/master master && + git branch --track topic origin/master && + git push origin topic && + git push other topic +' + +test_expect_success '@{push} with default=nothing' ' + test_config push.default nothing && + test_must_fail git rev-parse master@{push} +' + +test_expect_success '@{push} with default=simple' ' + test_config push.default simple && + resolve master@{push} refs/remotes/origin/master +' + +test_expect_success 'triangular @{push} fails with default=simple' ' + test_config push.default simple && + test_must_fail git rev-parse topic@{push} +' + +test_expect_success '@{push} with default=current' ' + test_config push.default current && + resolve topic@{push} refs/remotes/origin/topic +' + +test_expect_success '@{push} with default=matching' ' + test_config push.default matching && + resolve topic@{push} refs/remotes/origin/topic +' + +test_expect_success '@{push} with pushremote defined' ' + test_config push.default current && + test_config branch.topic.pushremote other && + resolve topic@{push} refs/remotes/other/topic +' + +test_expect_success '@{push} with push refspecs' ' + test_config push.default nothing && + test_config remote.origin.push refs/heads/*:refs/heads/magic/* && + git push && + resolve topic@{push} refs/remotes/origin/magic/topic +' + +test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index c66bf7981c..24fc2ba55d 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -28,7 +28,10 @@ test_expect_success setup ' git update-ref refs/remotes/origin/master master && git remote add origin nowhere && git config branch.master.remote origin && - git config branch.master.merge refs/heads/master + git config branch.master.merge refs/heads/master && + git remote add myfork elsewhere && + git config remote.pushdefault myfork && + git config push.default current ' test_atom() { @@ -47,6 +50,7 @@ test_atom() { test_atom head refname refs/heads/master test_atom head upstream refs/remotes/origin/master +test_atom head push refs/remotes/myfork/master test_atom head objecttype commit test_atom head objectsize 171 test_atom head objectname $(git rev-parse refs/heads/master) @@ -83,6 +87,7 @@ test_atom head HEAD '*' test_atom tag refname refs/tags/testtag test_atom tag upstream '' +test_atom tag push '' test_atom tag objecttype tag test_atom tag objectsize 154 test_atom tag objectname $(git rev-parse refs/tags/testtag) @@ -347,6 +352,12 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' ' test_cmp expected actual ' +test_expect_success '%(push) supports tracking specifiers, too' ' + echo "[ahead 1]" >expected && + git for-each-ref --format="%(push:track)" refs/heads >actual && + test_cmp expected actual +' + cat >expected <<EOF $(git rev-parse --short HEAD) EOF diff --git a/wt-status.c b/wt-status.c index 33452f169d..c56c78fb6f 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1534,21 +1534,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) color_fprintf(s->fp, branch_color_local, "%s", branch_name); - switch (stat_tracking_info(branch, &num_ours, &num_theirs)) { - case 0: - /* no base */ - fputc(s->null_termination ? '\0' : '\n', s->fp); - return; - case -1: - /* with "gone" base */ + if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) { + if (!base) { + fputc(s->null_termination ? '\0' : '\n', s->fp); + return; + } + upstream_is_gone = 1; - break; - default: - /* with base */ - break; } - base = branch->merge[0]->dst; base = shorten_unambiguous_ref(base, 0); color_fprintf(s->fp, header_color, "..."); color_fprintf(s->fp, branch_color_remote, "%s", base); |