branch: report invalid tracking branch as gone
[gitweb.git] / remote.c
index e71f66de25b6031bbe1ee94ab3770aacd15b46e0..87c8dd35be50a825cbb7ae6b5232fee9b43586d4 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -276,10 +276,9 @@ static void read_remotes_file(struct remote *remote)
 
 static void read_branches_file(struct remote *remote)
 {
-       const char *slash = strchr(remote->name, '/');
        char *frag;
        struct strbuf branch = STRBUF_INIT;
-       int n = slash ? slash - remote->name : 1000;
+       int n = 1000;
        FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
        char *s, *p;
        int len;
@@ -299,21 +298,11 @@ static void read_branches_file(struct remote *remote)
        while (isspace(p[-1]))
                *--p = 0;
        len = p - s;
-       if (slash)
-               len += strlen(slash);
        p = xmalloc(len + 1);
        strcpy(p, s);
-       if (slash)
-               strcat(p, slash);
 
        /*
-        * With "slash", e.g. "git fetch jgarzik/netdev-2.6" when
-        * reading from $GIT_DIR/branches/jgarzik fetches "HEAD" from
-        * the partial URL obtained from the branches file plus
-        * "/netdev-2.6" and does not store it in any tracking ref.
-        * #branch specifier in the file is ignored.
-        *
-        * Otherwise, the branches file would have URL and optionally
+        * The branches file would have URL and optionally
         * #branch specified.  The "master" (or specified) branch is
         * fetched and stored in the local branch of the same name.
         */
@@ -323,12 +312,8 @@ static void read_branches_file(struct remote *remote)
                strbuf_addf(&branch, "refs/heads/%s", frag);
        } else
                strbuf_addstr(&branch, "refs/heads/master");
-       if (!slash) {
-               strbuf_addf(&branch, ":refs/heads/%s", remote->name);
-       } else {
-               strbuf_reset(&branch);
-               strbuf_addstr(&branch, "HEAD:");
-       }
+
+       strbuf_addf(&branch, ":refs/heads/%s", remote->name);
        add_url_alias(remote, p);
        add_fetch_refspec(remote, strbuf_detach(&branch, NULL));
        /*
@@ -1317,6 +1302,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
        free(sent_tips.tip);
 }
 
+static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
+{
+       for ( ; ref; ref = ref->next)
+               string_list_append_nodup(ref_index, ref->name)->util = ref;
+
+       sort_string_list(ref_index);
+}
+
 /*
  * Given the set of refs the local repository has, the set of refs the
  * remote repository has, and the refspec used for push, determine
@@ -1335,6 +1328,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
        int errs;
        static const char *default_refspec[] = { ":", NULL };
        struct ref *ref, **dst_tail = tail_ref(dst);
+       struct string_list dst_ref_index = STRING_LIST_INIT_NODUP;
 
        if (!nr_refspec) {
                nr_refspec = 1;
@@ -1345,6 +1339,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
 
        /* pick the remainder */
        for (ref = src; ref; ref = ref->next) {
+               struct string_list_item *dst_item;
                struct ref *dst_peer;
                const struct refspec *pat = NULL;
                char *dst_name;
@@ -1353,7 +1348,11 @@ int match_push_refs(struct ref *src, struct ref **dst,
                if (!dst_name)
                        continue;
 
-               dst_peer = find_ref_by_name(*dst, dst_name);
+               if (!dst_ref_index.nr)
+                       prepare_ref_index(&dst_ref_index, *dst);
+
+               dst_item = string_list_lookup(&dst_ref_index, dst_name);
+               dst_peer = dst_item ? dst_item->util : NULL;
                if (dst_peer) {
                        if (dst_peer->peer_ref)
                                /* We're already sending something to this ref. */
@@ -1370,6 +1369,8 @@ int match_push_refs(struct ref *src, struct ref **dst,
                        /* Create a new one and link it */
                        dst_peer = make_linked_ref(dst_name, &dst_tail);
                        hashcpy(dst_peer->new_sha1, ref->new_sha1);
+                       string_list_insert(&dst_ref_index,
+                               dst_peer->name)->util = dst_peer;
                }
                dst_peer->peer_ref = copy_ref(ref);
                dst_peer->force = pat->force;
@@ -1377,10 +1378,13 @@ int match_push_refs(struct ref *src, struct ref **dst,
                free(dst_name);
        }
 
+       string_list_clear(&dst_ref_index, 0);
+
        if (flags & MATCH_REFS_FOLLOW_TAGS)
                add_missing_tags(src, dst, &dst_tail);
 
        if (send_prune) {
+               struct string_list src_ref_index = STRING_LIST_INIT_NODUP;
                /* check for missing refs on the remote */
                for (ref = *dst; ref; ref = ref->next) {
                        char *src_name;
@@ -1391,11 +1395,15 @@ int match_push_refs(struct ref *src, struct ref **dst,
 
                        src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL);
                        if (src_name) {
-                               if (!find_ref_by_name(src, src_name))
+                               if (!src_ref_index.nr)
+                                       prepare_ref_index(&src_ref_index, src);
+                               if (!string_list_has_string(&src_ref_index,
+                                           src_name))
                                        ref->peer_ref = alloc_delete_ref();
                                free(src_name);
                        }
                }
+               string_list_clear(&src_ref_index, 0);
        }
        if (errs)
                return -1;
@@ -1687,7 +1695,11 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
 }
 
 /*
- * Return true if there is anything to report, otherwise false.
+ * Compare a branch with its upstream, and save their differences (number
+ * of commits) in *num_ours and *num_theirs.
+ *
+ * Return 0 if branch has no upstream (no base), -1 if upstream is missing
+ * (with "gone" base), otherwise 1 (with base).
  */
 int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
 {
@@ -1698,34 +1710,30 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
        const char *rev_argv[10], *base;
        int rev_argc;
 
-       /*
-        * Nothing to report unless we are marked to build on top of
-        * somebody else.
-        */
+       /* 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;
 
-       /*
-        * If what we used to build on no longer exists, there is
-        * nothing to report.
-        */
+       /* Cannot stat if what we used to build on no longer exists */
        base = branch->merge[0]->dst;
        if (read_ref(base, sha1))
-               return 0;
+               return -1;
        theirs = lookup_commit_reference(sha1);
        if (!theirs)
-               return 0;
+               return -1;
 
        if (read_ref(branch->refname, sha1))
-               return 0;
+               return -1;
        ours = lookup_commit_reference(sha1);
        if (!ours)
-               return 0;
+               return -1;
 
        /* are we the same? */
-       if (theirs == ours)
-               return 0;
+       if (theirs == ours) {
+               *num_theirs = *num_ours = 0;
+               return 1;
+       }
 
        /* Run "rev-list --left-right ours...theirs" internally... */
        rev_argc = 0;
@@ -1767,31 +1775,52 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
  */
 int format_tracking_info(struct branch *branch, struct strbuf *sb)
 {
-       int num_ours, num_theirs;
+       int ours, theirs;
        const char *base;
+       int upstream_is_gone = 0;
 
-       if (!stat_tracking_info(branch, &num_ours, &num_theirs))
+       switch (stat_tracking_info(branch, &ours, &theirs)) {
+       case 0:
+               /* no base */
                return 0;
+       case -1:
+               /* with "gone" base */
+               upstream_is_gone = 1;
+               break;
+       default:
+               /* Nothing to report if neither side has changes. */
+               if (!ours && !theirs)
+                       return 0;
+               /* with base */
+               break;
+       }
 
        base = branch->merge[0]->dst;
        base = shorten_unambiguous_ref(base, 0);
-       if (!num_theirs) {
+       if (upstream_is_gone) {
+               strbuf_addf(sb,
+                       _("Your branch is based on '%s', but the upstream is gone.\n"),
+                       base);
+               if (advice_status_hints)
+                       strbuf_addf(sb,
+                               _("  (use \"git branch --unset-upstream\" to fixup)\n"));
+       } else if (!theirs) {
                strbuf_addf(sb,
                        Q_("Your branch is ahead of '%s' by %d commit.\n",
                           "Your branch is ahead of '%s' by %d commits.\n",
-                          num_ours),
-                       base, num_ours);
+                          ours),
+                       base, ours);
                if (advice_status_hints)
                        strbuf_addf(sb,
                                _("  (use \"git push\" to publish your local commits)\n"));
-       } else if (!num_ours) {
+       } else if (!ours) {
                strbuf_addf(sb,
                        Q_("Your branch is behind '%s' by %d commit, "
                               "and can be fast-forwarded.\n",
                           "Your branch is behind '%s' by %d commits, "
                               "and can be fast-forwarded.\n",
-                          num_theirs),
-                       base, num_theirs);
+                          theirs),
+                       base, theirs);
                if (advice_status_hints)
                        strbuf_addf(sb,
                                _("  (use \"git pull\" to update your local branch)\n"));
@@ -1803,8 +1832,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
                           "Your branch and '%s' have diverged,\n"
                               "and have %d and %d different commits each, "
                               "respectively.\n",
-                          num_theirs),
-                       base, num_ours, num_theirs);
+                          theirs),
+                       base, ours, theirs);
                if (advice_status_hints)
                        strbuf_addf(sb,
                                _("  (use \"git pull\" to merge the remote branch into yours)\n"));