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

1  2 
builtin/branch.c
builtin/log.c
builtin/merge.c
sha1_name.c
wt-status.c
diff --combined builtin/branch.c
index 9cbab189f5cb1776324a2494d5e35ffacf461b69,e4d184d69a883d2c6a683f837f9c91aa9286c0a9..c70085c35f79f9e87aeadb9abc8aa62e3dc1f1b9
@@@ -123,14 -123,12 +123,12 @@@ static int branch_merged(int kind, cons
  
        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);
        }
@@@ -242,7 -240,7 +240,7 @@@ static int delete_branches(int argc, co
                                            sha1, &flags);
                if (!target) {
                        error(remote_branch
 -                            ? _("remote branch '%s' not found.")
 +                            ? _("remote-tracking branch '%s' not found.")
                              : _("branch '%s' not found."), bname.buf);
                        ret = 1;
                        continue;
  
                if (delete_ref(name, sha1, REF_NODEREF)) {
                        error(remote_branch
 -                            ? _("Error deleting remote branch '%s'")
 +                            ? _("Error deleting remote-tracking branch '%s'")
                              : _("Error deleting branch '%s'"),
                              bname.buf);
                        ret = 1;
                }
                if (!quiet) {
                        printf(remote_branch
 -                             ? _("Deleted remote branch %s (was %s).\n")
 +                             ? _("Deleted remote-tracking branch %s (was %s).\n")
                               : _("Deleted branch %s (was %s).\n"),
                               bname.buf,
                               (flags & REF_ISBROKEN) ? "broken"
@@@ -427,25 -425,19 +425,19 @@@ static void fill_tracking_info(struct s
        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),
@@@ -771,6 -763,7 +763,6 @@@ static const char edit_description[] = 
  
  static int edit_branch_description(const char *branch_name)
  {
 -      FILE *fp;
        int status;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf name = STRBUF_INIT;
                    "  %s\n"
                    "Lines starting with '%c' will be stripped.\n",
                    branch_name, comment_line_char);
 -      fp = fopen(git_path(edit_description), "w");
 -      if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
 +      if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
                strbuf_release(&buf);
                return error(_("could not write branch description template: %s"),
                             strerror(errno));
diff --combined builtin/log.c
index 4c4e6be28c85756d73eaefa3c8be6264c795fdcc,6faeb82c8ef5fd8d95d24be1168d97fd32c3400e..e67671e37ab51d49424bb9a8945901dc9f9776ed
@@@ -38,7 -38,7 +38,7 @@@ static const char *fmt_patch_subject_pr
  static const char *fmt_pretty;
  
  static const char * const builtin_log_usage[] = {
 -      N_("git log [<options>] [<revision range>] [[--] <path>...]"),
 +      N_("git log [<options>] [<revision-range>] [[--] <path>...]"),
        N_("git show [<options>] <object>..."),
        NULL
  };
@@@ -1632,16 -1632,13 +1632,13 @@@ int cmd_cherry(int argc, const char **a
                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 --combined builtin/merge.c
index f89f60e11a523d45a81c3bd3a92fa9b4c5021673,1840317118ab444a56cc0c66a0905a1c1b3650b4..85c54dcd5a21f2b8413e5f73585f5f0a6c4c5715
@@@ -492,7 -492,8 +492,7 @@@ static void merge_name(const char *remo
        }
        if (len) {
                struct strbuf truname = STRBUF_INIT;
 -              strbuf_addstr(&truname, "refs/heads/");
 -              strbuf_addstr(&truname, remote);
 +              strbuf_addf(&truname, "refs/heads/%s", remote);
                strbuf_setlen(&truname, truname.len - len);
                if (ref_exists(truname.buf)) {
                        strbuf_addf(msg,
                        strbuf_release(&truname);
                        goto cleanup;
                }
 -      }
 -
 -      if (!strcmp(remote, "FETCH_HEAD") &&
 -                      !access(git_path("FETCH_HEAD"), R_OK)) {
 -              const char *filename;
 -              FILE *fp;
 -              struct strbuf line = STRBUF_INIT;
 -              char *ptr;
 -
 -              filename = git_path("FETCH_HEAD");
 -              fp = fopen(filename, "r");
 -              if (!fp)
 -                      die_errno(_("could not open '%s' for reading"),
 -                                filename);
 -              strbuf_getline(&line, fp, '\n');
 -              fclose(fp);
 -              ptr = strstr(line.buf, "\tnot-for-merge\t");
 -              if (ptr)
 -                      strbuf_remove(&line, ptr-line.buf+1, 13);
 -              strbuf_addbuf(msg, &line);
 -              strbuf_release(&line);
 -              goto cleanup;
 +              strbuf_release(&truname);
        }
  
        if (remote_head->util) {
@@@ -933,7 -955,7 +933,7 @@@ static int setup_with_upstream(const ch
  
        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."));
@@@ -1015,24 -1037,28 +1015,24 @@@ static int default_edit_option(void
                st_stdin.st_mode == st_stdout.st_mode);
  }
  
 -static struct commit_list *collect_parents(struct commit *head_commit,
 -                                         int *head_subsumed,
 -                                         int argc, const char **argv)
 +static struct commit_list *reduce_parents(struct commit *head_commit,
 +                                        int *head_subsumed,
 +                                        struct commit_list *remoteheads)
  {
 -      int i;
 -      struct commit_list *remoteheads = NULL, *parents, *next;
 -      struct commit_list **remotes = &remoteheads;
 +      struct commit_list *parents, *next, **remotes = &remoteheads;
  
 -      if (head_commit)
 -              remotes = &commit_list_insert(head_commit, remotes)->next;
 -      for (i = 0; i < argc; i++) {
 -              struct commit *commit = get_merge_parent(argv[i]);
 -              if (!commit)
 -                      help_unknown_ref(argv[i], "merge",
 -                                       "not something we can merge");
 -              remotes = &commit_list_insert(commit, remotes)->next;
 -      }
 -      *remotes = NULL;
 +      /*
 +       * Is the current HEAD reachable from another commit being
 +       * merged?  If so we do not want to record it as a parent of
 +       * the resulting merge, unless --no-ff is given.  We will flip
 +       * this variable to 0 when we find HEAD among the independent
 +       * tips being merged.
 +       */
 +      *head_subsumed = 1;
  
 +      /* Find what parents to record by checking independent ones. */
        parents = reduce_heads(remoteheads);
  
 -      *head_subsumed = 1; /* we will flip this to 0 when we find it */
        for (remoteheads = NULL, remotes = &remoteheads;
             parents;
             parents = next) {
                        *head_subsumed = 0;
                else
                        remotes = &commit_list_insert(commit, remotes)->next;
 +              free(parents);
        }
        return remoteheads;
  }
  
 +static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg)
 +{
 +      struct fmt_merge_msg_opts opts;
 +
 +      memset(&opts, 0, sizeof(opts));
 +      opts.add_title = !have_message;
 +      opts.shortlog_len = shortlog_len;
 +      opts.credit_people = (0 < option_edit);
 +
 +      fmt_merge_msg(merge_names, merge_msg, &opts);
 +      if (merge_msg->len)
 +              strbuf_setlen(merge_msg, merge_msg->len - 1);
 +}
 +
 +static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names)
 +{
 +      const char *filename;
 +      int fd, pos, npos;
 +      struct strbuf fetch_head_file = STRBUF_INIT;
 +
 +      if (!merge_names)
 +              merge_names = &fetch_head_file;
 +
 +      filename = git_path("FETCH_HEAD");
 +      fd = open(filename, O_RDONLY);
 +      if (fd < 0)
 +              die_errno(_("could not open '%s' for reading"), filename);
 +
 +      if (strbuf_read(merge_names, fd, 0) < 0)
 +              die_errno(_("could not read '%s'"), filename);
 +      if (close(fd) < 0)
 +              die_errno(_("could not close '%s'"), filename);
 +
 +      for (pos = 0; pos < merge_names->len; pos = npos) {
 +              unsigned char sha1[20];
 +              char *ptr;
 +              struct commit *commit;
 +
 +              ptr = strchr(merge_names->buf + pos, '\n');
 +              if (ptr)
 +                      npos = ptr - merge_names->buf + 1;
 +              else
 +                      npos = merge_names->len;
 +
 +              if (npos - pos < 40 + 2 ||
 +                  get_sha1_hex(merge_names->buf + pos, sha1))
 +                      commit = NULL; /* bad */
 +              else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
 +                      continue; /* not-for-merge */
 +              else {
 +                      char saved = merge_names->buf[pos + 40];
 +                      merge_names->buf[pos + 40] = '\0';
 +                      commit = get_merge_parent(merge_names->buf + pos);
 +                      merge_names->buf[pos + 40] = saved;
 +              }
 +              if (!commit) {
 +                      if (ptr)
 +                              *ptr = '\0';
 +                      die("not something we can merge in %s: %s",
 +                          filename, merge_names->buf + pos);
 +              }
 +              remotes = &commit_list_insert(commit, remotes)->next;
 +      }
 +
 +      if (merge_names == &fetch_head_file)
 +              strbuf_release(&fetch_head_file);
 +}
 +
 +static struct commit_list *collect_parents(struct commit *head_commit,
 +                                         int *head_subsumed,
 +                                         int argc, const char **argv,
 +                                         struct strbuf *merge_msg)
 +{
 +      int i;
 +      struct commit_list *remoteheads = NULL;
 +      struct commit_list **remotes = &remoteheads;
 +      struct strbuf merge_names = STRBUF_INIT, *autogen = NULL;
 +
 +      if (merge_msg && (!have_message || shortlog_len))
 +              autogen = &merge_names;
 +
 +      if (head_commit)
 +              remotes = &commit_list_insert(head_commit, remotes)->next;
 +
 +      if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) {
 +              handle_fetch_head(remotes, autogen);
 +              remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
 +      } else {
 +              for (i = 0; i < argc; i++) {
 +                      struct commit *commit = get_merge_parent(argv[i]);
 +                      if (!commit)
 +                              help_unknown_ref(argv[i], "merge",
 +                                               "not something we can merge");
 +                      remotes = &commit_list_insert(commit, remotes)->next;
 +              }
 +              remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
 +              if (autogen) {
 +                      struct commit_list *p;
 +                      for (p = remoteheads; p; p = p->next)
 +                              merge_name(merge_remote_util(p->item)->name, autogen);
 +              }
 +      }
 +
 +      if (autogen) {
 +              prepare_merge_message(autogen, merge_msg);
 +              strbuf_release(autogen);
 +      }
 +
 +      return remoteheads;
 +}
 +
  int cmd_merge(int argc, const char **argv, const char *prefix)
  {
        unsigned char result_tree[20];
                option_commit = 0;
        }
  
 -      if (!abort_current_merge) {
 -              if (!argc) {
 -                      if (default_to_upstream)
 -                              argc = setup_with_upstream(&argv);
 -                      else
 -                              die(_("No commit specified and merge.defaultToUpstream not set."));
 -              } else if (argc == 1 && !strcmp(argv[0], "-"))
 -                      argv[0] = "@{-1}";
 +      if (!argc) {
 +              if (default_to_upstream)
 +                      argc = setup_with_upstream(&argv);
 +              else
 +                      die(_("No commit specified and merge.defaultToUpstream not set."));
 +      } else if (argc == 1 && !strcmp(argv[0], "-")) {
 +              argv[0] = "@{-1}";
        }
 +
        if (!argc)
                usage_with_options(builtin_merge_usage,
                        builtin_merge_options);
  
 -      /*
 -       * This could be traditional "merge <msg> HEAD <commit>..."  and
 -       * the way we can tell it is to see if the second token is HEAD,
 -       * but some people might have misused the interface and used a
 -       * commit-ish that is the same as HEAD there instead.
 -       * Traditional format never would have "-m" so it is an
 -       * additional safety measure to check for it.
 -       */
 -
 -      if (!have_message && head_commit &&
 -          is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
 -              strbuf_addstr(&merge_msg, argv[0]);
 -              head_arg = argv[1];
 -              argv += 2;
 -              argc -= 2;
 -              remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
 -      } else if (!head_commit) {
 +      if (!head_commit) {
                struct commit *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
                 * to forbid "git merge" into a branch yet to be born.
                 * We do the same for "git pull".
                 */
 -              if (argc != 1)
 -                      die(_("Can merge only exactly one commit into "
 -                              "empty head"));
                if (squash)
                        die(_("Squash commit into empty head not supported yet"));
                if (fast_forward == FF_NO)
                        die(_("Non-fast-forward commit does not make sense into "
                            "an empty head"));
 -              remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
 +              remoteheads = collect_parents(head_commit, &head_subsumed,
 +                                            argc, argv, NULL);
                remote_head = remoteheads->item;
                if (!remote_head)
                        die(_("%s - not something we can merge"), argv[0]);
 +              if (remoteheads->next)
 +                      die(_("Can merge only exactly one commit into empty head"));
                read_empty(remote_head->object.sha1, 0);
                update_ref("initial pull", "HEAD", remote_head->object.sha1,
                           NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
 -      } else {
 -              struct strbuf merge_names = STRBUF_INIT;
 +      }
  
 +      /*
 +       * This could be traditional "merge <msg> HEAD <commit>..."  and
 +       * the way we can tell it is to see if the second token is HEAD,
 +       * but some people might have misused the interface and used a
 +       * commit-ish that is the same as HEAD there instead.
 +       * Traditional format never would have "-m" so it is an
 +       * additional safety measure to check for it.
 +       */
 +      if (!have_message &&
 +          is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
 +              warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
 +              strbuf_addstr(&merge_msg, argv[0]);
 +              head_arg = argv[1];
 +              argv += 2;
 +              argc -= 2;
 +              remoteheads = collect_parents(head_commit, &head_subsumed,
 +                                            argc, argv, NULL);
 +      } else {
                /* We are invoked directly as the first-class UI. */
                head_arg = "HEAD";
  
                 * the standard merge summary message to be appended
                 * to the given message.
                 */
 -              remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
 -              for (p = remoteheads; p; p = p->next)
 -                      merge_name(merge_remote_util(p->item)->name, &merge_names);
 -
 -              if (!have_message || shortlog_len) {
 -                      struct fmt_merge_msg_opts opts;
 -                      memset(&opts, 0, sizeof(opts));
 -                      opts.add_title = !have_message;
 -                      opts.shortlog_len = shortlog_len;
 -                      opts.credit_people = (0 < option_edit);
 -
 -                      fmt_merge_msg(&merge_names, &merge_msg, &opts);
 -                      if (merge_msg.len)
 -                              strbuf_setlen(&merge_msg, merge_msg.len - 1);
 -              }
 +              remoteheads = collect_parents(head_commit, &head_subsumed,
 +                                            argc, argv, &merge_msg);
        }
  
        if (!head_commit || !argc)
diff --combined sha1_name.c
index 46218ba02c6c1fd87cf51fa189843da3b7950dbe,10969435bbaba89f4169d4ae9278a0203c5bcff8..4f3c142c7aa5d5d1ff9f86a0d52d43d9013b4eac
@@@ -6,7 -6,6 +6,7 @@@
  #include "tree-walk.h"
  #include "refs.h"
  #include "remote.h"
 +#include "dir.h"
  
  static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
  
@@@ -416,12 -415,12 +416,12 @@@ static int ambiguous_path(const char *p
        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))
        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 -488,8 +489,8 @@@ static int get_sha1_basic(const char *s
                                        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 -1068,36 +1069,36 @@@ static void set_shortened_ref(struct st
        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 -1148,13 +1149,13 @@@ int interpret_branch_name(const char *n
                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;
        }
@@@ -1238,13 -1246,14 +1247,13 @@@ static void diagnose_invalid_sha1_path(
                                       const char *object_name,
                                       int object_name_len)
  {
 -      struct stat st;
        unsigned char sha1[20];
        unsigned mode;
  
        if (!prefix)
                prefix = "";
  
 -      if (!lstat(filename, &st))
 +      if (file_exists(filename))
                die("Path '%s' exists on disk, but not in '%.*s'.",
                    filename, object_name_len, object_name);
        if (errno == ENOENT || errno == ENOTDIR) {
@@@ -1271,6 -1280,7 +1280,6 @@@ static void diagnose_invalid_index_path
                                        const char *prefix,
                                        const char *filename)
  {
 -      struct stat st;
        const struct cache_entry *ce;
        int pos;
        unsigned namelen = strlen(filename);
                            ce_stage(ce), filename);
        }
  
 -      if (!lstat(filename, &st))
 +      if (file_exists(filename))
                die("Path '%s' exists on disk, but not in the index.", filename);
        if (errno == ENOENT || errno == ENOTDIR)
                die("Path '%s' does not exist (neither on disk nor in the index).",
@@@ -1433,19 -1443,11 +1442,19 @@@ static int get_sha1_with_context_1(cons
                        new_filename = resolve_relative_path(filename);
                        if (new_filename)
                                filename = new_filename;
 -                      ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
 -                      if (ret && only_to_die) {
 -                              diagnose_invalid_sha1_path(prefix, filename,
 -                                                         tree_sha1,
 -                                                         name, len);
 +                      if (flags & GET_SHA1_FOLLOW_SYMLINKS) {
 +                              ret = get_tree_entry_follow_symlinks(tree_sha1,
 +                                      filename, sha1, &oc->symlink_path,
 +                                      &oc->mode);
 +                      } else {
 +                              ret = get_tree_entry(tree_sha1, filename,
 +                                                   sha1, &oc->mode);
 +                              if (ret && only_to_die) {
 +                                      diagnose_invalid_sha1_path(prefix,
 +                                                                 filename,
 +                                                                 tree_sha1,
 +                                                                 name, len);
 +                              }
                        }
                        hashcpy(oc->tree, tree_sha1);
                        strlcpy(oc->path, filename, sizeof(oc->path));
@@@ -1476,7 -1478,5 +1485,7 @@@ void maybe_die_on_misspelt_object_name(
  
  int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
  {
 +      if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
 +              die("BUG: incompatible flags for get_sha1_with_context");
        return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
  }
diff --combined wt-status.c
index 33452f169dfca7a3cd4f15b1c3cdc91a3cf40735,4313b9d8457037bcaef03399a852ef91198861ba..c56c78fb6f6947a61a1d54b15da53ad967cc7d38
@@@ -585,8 -585,6 +585,8 @@@ static void wt_status_collect_untracked
                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
        if (s->show_ignored_files)
                dir.flags |= DIR_SHOW_IGNORED_TOO;
 +      else
 +              dir.untracked = the_index.untracked;
        setup_standard_excludes(&dir);
  
        fill_directory(&dir, &s->pathspec);
@@@ -1534,25 -1532,18 +1534,19 @@@ static void wt_shortstatus_print_tracki
  
        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);
 +      free((char *)base);
  
        if (!upstream_is_gone && !num_ours && !num_theirs) {
                fputc(s->null_termination ? '\0' : '\n', s->fp);