Merge branch 'jk/at-push-sha1'
authorJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:17:36 +0000 (12:17 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:17:36 +0000 (12:17 -0700)
Introduce <branch>@{push} short-hand to denote the remote-tracking
branch that tracks the branch at the remote the <branch> would be
pushed to.

* jk/at-push-sha1:
for-each-ref: accept "%(push)" format
for-each-ref: use skip_prefix instead of starts_with
sha1_name: implement @{push} shorthand
sha1_name: refactor interpret_upstream_mark
sha1_name: refactor upstream_mark
remote.c: add branch_get_push
remote.c: return upstream name from stat_tracking_info
remote.c: untangle error logic in branch_get_upstream
remote.c: report specific errors from branch_get_upstream
remote.c: introduce branch_get_upstream helper
remote.c: hoist read_config into remote_get_1
remote.c: provide per-branch pushremote name
remote.c: hoist branch.*.remote lookup out of remote_get_1
remote.c: drop "remote" pointer from "struct branch"
remote.c: refactor setup of branch->merge list
remote.c: drop default_remote_name variable

14 files changed:
Documentation/git-for-each-ref.txt
Documentation/revisions.txt
Documentation/technical/api-remote.txt
builtin/branch.c
builtin/for-each-ref.c
builtin/log.c
builtin/merge.c
remote.c
remote.h
sha1_name.c
t/t1507-rev-parse-upstream.sh
t/t1514-rev-parse-push.sh [new file with mode: 0755]
t/t6300-for-each-ref.sh
wt-status.c
index 42408752d0848f0854308407ff7606b40f1e6eed..7f8d9a5b5f358baa50bdfe205f555ea904f9f629 100644 (file)
@@ -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.
index 07961185fe0cddf78822aee75b43f206cbcd4579..d85e3033646faec23b23065cbd78436aca508acf 100644 (file)
@@ -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.
index 5d245aa9d1b1654749ee51495e9eebda261c3681..2cfdd224a83710f32e344b76c131181527caf98c 100644 (file)
@@ -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.
index 9cbab189f5cb1776324a2494d5e35ffacf461b69..c70085c35f79f9e87aeadb9abc8aa62e3dc1f1b9 100644 (file)
@@ -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),
index 83f9cf91633a514e9301762fe774ee91be092848..05dd23d2a35b30eab9d7ef3c01e00c3e99cf5577 100644 (file)
@@ -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)
index 4c4e6be28c85756d73eaefa3c8be6264c795fdcc..e67671e37ab51d49424bb9a8945901dc9f9776ed 100644 (file)
@@ -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);
index f89f60e11a523d45a81c3bd3a92fa9b4c5021673..85c54dcd5a21f2b8413e5f73585f5f0a6c4c5715 100644 (file)
@@ -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."));
index 68901b0070d257dc31b59e9292cd7b6d7ef2e952..a467d4ff077bfe50195c78f2f0348d6dfd922c1f 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -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"),
index 02d66ceff5c962995de37f351a09fe91055d6364..312b7ca131c459faeae32ff5cf3021c6516eb2af 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -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);
index 46218ba02c6c1fd87cf51fa189843da3b7950dbe..4f3c142c7aa5d5d1ff9f86a0d52d43d9013b4eac 100644 (file)
@@ -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;
        }
index 1978947c4196fb666d55395bedc53ae149b6d13d..46ef1f22dca14423ecff8da45d608a8885f23dba 100755 (executable)
@@ -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 (executable)
index 0000000..7214f5b
--- /dev/null
@@ -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
index c66bf7981c5328356005963e15262c8d870ac61c..24fc2ba55da38232f07c58346c628b61822af44a 100755 (executable)
@@ -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
index 33452f169dfca7a3cd4f15b1c3cdc91a3cf40735..c56c78fb6f6947a61a1d54b15da53ad967cc7d38 100644 (file)
@@ -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);