git-am foreign patch support: StGIT support
[gitweb.git] / builtin-remote.c
index 379826eed580f9179fd2d4887e14720f1e639ccf..71abf68404f5b260ba96208717d89e50e778dd36 100644 (file)
@@ -15,16 +15,18 @@ static const char * const builtin_remote_usage[] = {
        "git remote set-head <name> [-a | -d | <branch>]",
        "git remote show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
-       "git remote [-v | --verbose] update [group]",
+       "git remote [-v | --verbose] update [-p | --prune] [group]",
        NULL
 };
 
 #define GET_REF_STATES (1<<0)
 #define GET_HEAD_NAMES (1<<1)
+#define GET_PUSH_REF_STATES (1<<2)
 
 static int verbose;
 
 static int show_all(void);
+static int prune_remote(const char *remote, int dry_run);
 
 static inline int postfixcmp(const char *string, const char *postfix)
 {
@@ -220,7 +222,7 @@ static void read_branches(void)
 
 struct ref_states {
        struct remote *remote;
-       struct string_list new, stale, tracked, heads;
+       struct string_list new, stale, tracked, heads, push;
        int queried;
 };
 
@@ -275,6 +277,103 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
        return 0;
 }
 
+struct push_info {
+       char *dest;
+       int forced;
+       enum {
+               PUSH_STATUS_CREATE = 0,
+               PUSH_STATUS_DELETE,
+               PUSH_STATUS_UPTODATE,
+               PUSH_STATUS_FASTFORWARD,
+               PUSH_STATUS_OUTOFDATE,
+               PUSH_STATUS_NOTQUERIED,
+       } status;
+};
+
+static int get_push_ref_states(const struct ref *remote_refs,
+       struct ref_states *states)
+{
+       struct remote *remote = states->remote;
+       struct ref *ref, *local_refs, *push_map, **push_tail;
+       if (remote->mirror)
+               return 0;
+
+       local_refs = get_local_heads();
+       ref = push_map = copy_ref_list(remote_refs);
+       while (ref->next)
+               ref = ref->next;
+       push_tail = &ref->next;
+
+       match_refs(local_refs, push_map, &push_tail, remote->push_refspec_nr,
+                  remote->push_refspec, MATCH_REFS_NONE);
+
+       states->push.strdup_strings = 1;
+       for (ref = push_map; ref; ref = ref->next) {
+               struct string_list_item *item;
+               struct push_info *info;
+
+               if (!ref->peer_ref)
+                       continue;
+               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+
+               item = string_list_append(abbrev_branch(ref->peer_ref->name),
+                                         &states->push);
+               item->util = xcalloc(sizeof(struct push_info), 1);
+               info = item->util;
+               info->forced = ref->force;
+               info->dest = xstrdup(abbrev_branch(ref->name));
+
+               if (is_null_sha1(ref->new_sha1)) {
+                       info->status = PUSH_STATUS_DELETE;
+               } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
+                       info->status = PUSH_STATUS_UPTODATE;
+               else if (is_null_sha1(ref->old_sha1))
+                       info->status = PUSH_STATUS_CREATE;
+               else if (has_sha1_file(ref->old_sha1) &&
+                        ref_newer(ref->new_sha1, ref->old_sha1))
+                       info->status = PUSH_STATUS_FASTFORWARD;
+               else
+                       info->status = PUSH_STATUS_OUTOFDATE;
+       }
+       free_refs(local_refs);
+       free_refs(push_map);
+       return 0;
+}
+
+static int get_push_ref_states_noquery(struct ref_states *states)
+{
+       int i;
+       struct remote *remote = states->remote;
+       struct string_list_item *item;
+       struct push_info *info;
+
+       if (remote->mirror)
+               return 0;
+
+       states->push.strdup_strings = 1;
+       if (!remote->push_refspec_nr) {
+               item = string_list_append("(matching)", &states->push);
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->status = PUSH_STATUS_NOTQUERIED;
+               info->dest = xstrdup(item->string);
+       }
+       for (i = 0; i < remote->push_refspec_nr; i++) {
+               struct refspec *spec = remote->push + i;
+               if (spec->matching)
+                       item = string_list_append("(matching)", &states->push);
+               else if (strlen(spec->src))
+                       item = string_list_append(spec->src, &states->push);
+               else
+                       item = string_list_append("(delete)", &states->push);
+
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->forced = spec->force;
+               info->status = PUSH_STATUS_NOTQUERIED;
+               info->dest = xstrdup(spec->dst ? spec->dst : item->string);
+       }
+       return 0;
+}
+
 static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
 {
        struct ref *ref, *matches;
@@ -283,7 +382,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 
        refspec.force = 0;
        refspec.pattern = 1;
-       refspec.src = refspec.dst = "refs/heads/";
+       refspec.src = refspec.dst = "refs/heads/*";
        states->heads.strdup_strings = 1;
        get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
        matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
@@ -426,8 +525,8 @@ static int migrate_file(struct remote *remote)
                path = git_path("remotes/%s", remote->name);
        else if (remote->origin == REMOTE_BRANCHES)
                path = git_path("branches/%s", remote->name);
-       if (path && unlink(path))
-               warning("failed to remove '%s'", path);
+       if (path)
+               unlink_or_warn(path);
        return 0;
 }
 
@@ -510,9 +609,8 @@ static int mv(int argc, const char **argv)
                struct string_list_item *item = remote_branches.items + i;
                int flag = 0;
                unsigned char sha1[20];
-               const char *symref;
 
-               symref = resolve_ref(item->string, sha1, 1, &flag);
+               resolve_ref(item->string, sha1, 1, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
                if (delete_ref(item->string, NULL, REF_NODEREF))
@@ -644,12 +742,20 @@ static int rm(int argc, const char **argv)
        return result;
 }
 
+void clear_push_info(void *util, const char *string)
+{
+       struct push_info *info = util;
+       free(info->dest);
+       free(info);
+}
+
 static void free_remote_ref_states(struct ref_states *states)
 {
        string_list_clear(&states->new, 0);
        string_list_clear(&states->stale, 0);
        string_list_clear(&states->tracked, 0);
        string_list_clear(&states->heads, 0);
+       string_list_clear_func(&states->push, clear_push_info);
 }
 
 static int append_ref_to_tracked_list(const char *refname,
@@ -693,9 +799,12 @@ static int get_remote_ref_states(const char *name,
                        get_ref_states(remote_refs, states);
                if (query & GET_HEAD_NAMES)
                        get_head_names(remote_refs, states);
+               if (query & GET_PUSH_REF_STATES)
+                       get_push_ref_states(remote_refs, states);
        } else {
                for_each_ref(append_ref_to_tracked_list, states);
                sort_string_list(&states->tracked);
+               get_push_ref_states_noquery(states);
        }
 
        return 0;
@@ -704,7 +813,7 @@ static int get_remote_ref_states(const char *name,
 struct show_info {
        struct string_list *list;
        struct ref_states *states;
-       int width;
+       int width, width2;
        int any_rebase;
 };
 
@@ -799,6 +908,72 @@ int show_local_info_item(struct string_list_item *item, void *cb_data)
        return 0;
 }
 
+int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = push_item->util;
+       struct string_list_item *item;
+       int n;
+       if ((n = strlen(push_item->string)) > show_info->width)
+               show_info->width = n;
+       if ((n = strlen(push_info->dest)) > show_info->width2)
+               show_info->width2 = n;
+       item = string_list_append(push_item->string, show_info->list);
+       item->util = push_item->util;
+       return 0;
+}
+
+/*
+ * Sorting comparison for a string list that has push_info
+ * structs in its util field
+ */
+static int cmp_string_with_push(const void *va, const void *vb)
+{
+       const struct string_list_item *a = va;
+       const struct string_list_item *b = vb;
+       const struct push_info *a_push = a->util;
+       const struct push_info *b_push = b->util;
+       int cmp = strcmp(a->string, b->string);
+       return cmp ? cmp : strcmp(a_push->dest, b_push->dest);
+}
+
+int show_push_info_item(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = item->util;
+       char *src = item->string, *status = NULL;
+
+       switch (push_info->status) {
+       case PUSH_STATUS_CREATE:
+               status = "create";
+               break;
+       case PUSH_STATUS_DELETE:
+               status = "delete";
+               src = "(none)";
+               break;
+       case PUSH_STATUS_UPTODATE:
+               status = "up to date";
+               break;
+       case PUSH_STATUS_FASTFORWARD:
+               status = "fast forwardable";
+               break;
+       case PUSH_STATUS_OUTOFDATE:
+               status = "local out of date";
+               break;
+       case PUSH_STATUS_NOTQUERIED:
+               break;
+       }
+       if (status)
+               printf("    %-*s %s to %-*s (%s)\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       show_info->width2, push_info->dest, status);
+       else
+               printf("    %-*s %s to %s\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       push_info->dest);
+       return 0;
+}
+
 static int show(int argc, const char **argv)
 {
        int no_query = 0, result = 0, query_flag = 0;
@@ -817,7 +992,7 @@ static int show(int argc, const char **argv)
                return show_all();
 
        if (!no_query)
-               query_flag = (GET_REF_STATES | GET_HEAD_NAMES);
+               query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
 
        memset(&states, 0, sizeof(states));
        memset(&info, 0, sizeof(info));
@@ -867,19 +1042,19 @@ static int show(int argc, const char **argv)
                string_list_clear(info.list, 0);
 
                /* git push info */
-               if (states.remote->push_refspec_nr) {
-                       printf("  Local branch%s pushed with 'git push'\n",
-                               states.remote->push_refspec_nr > 1 ?
-                                       "es" : "");
-                       for (i = 0; i < states.remote->push_refspec_nr; i++) {
-                               struct refspec *spec = states.remote->push + i;
-                               printf("    %s%s%s%s\n",
-                                      spec->force ? "+" : "",
-                                      abbrev_branch(spec->src),
-                                      spec->dst ? ":" : "",
-                                      spec->dst ? abbrev_branch(spec->dst) : "");
-                       }
-               }
+               if (states.remote->mirror)
+                       printf("  Local refs will be mirrored by 'git push'\n");
+
+               info.width = info.width2 = 0;
+               for_each_string_list(add_push_to_show_info, &states.push, &info);
+               qsort(info.list->items, info.list->nr,
+                       sizeof(*info.list->items), cmp_string_with_push);
+               if (info.list->nr)
+                       printf("  Local ref%s configured for 'git push'%s:\n",
+                               info.list->nr > 1 ? "s" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(show_push_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
 
                free_remote_ref_states(&states);
        }
@@ -954,46 +1129,49 @@ static int prune(int argc, const char **argv)
                OPT__DRY_RUN(&dry_run),
                OPT_END()
        };
-       struct ref_states states;
-       const char *dangling_msg;
 
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
 
        if (argc < 1)
                usage_with_options(builtin_remote_usage, options);
 
-       dangling_msg = (dry_run
-                       ? " %s will become dangling!\n"
-                       : " %s has become dangling!\n");
+       for (; argc; argc--, argv++)
+               result |= prune_remote(*argv, dry_run);
 
-       memset(&states, 0, sizeof(states));
-       for (; argc; argc--, argv++) {
-               int i;
-
-               get_remote_ref_states(*argv, &states, GET_REF_STATES);
+       return result;
+}
 
-               if (states.stale.nr) {
-                       printf("Pruning %s\n", *argv);
-                       printf("URL: %s\n",
-                              states.remote->url_nr
-                              ? states.remote->url[0]
-                              : "(no URL)");
-               }
+static int prune_remote(const char *remote, int dry_run)
+{
+       int result = 0, i;
+       struct ref_states states;
+       const char *dangling_msg = dry_run
+               ? " %s will become dangling!\n"
+               : " %s has become dangling!\n";
 
-               for (i = 0; i < states.stale.nr; i++) {
-                       const char *refname = states.stale.items[i].util;
+       memset(&states, 0, sizeof(states));
+       get_remote_ref_states(remote, &states, GET_REF_STATES);
+
+       if (states.stale.nr) {
+               printf("Pruning %s\n", remote);
+               printf("URL: %s\n",
+                      states.remote->url_nr
+                      ? states.remote->url[0]
+                      : "(no URL)");
+       }
 
-                       if (!dry_run)
-                               result |= delete_ref(refname, NULL, 0);
+       for (i = 0; i < states.stale.nr; i++) {
+               const char *refname = states.stale.items[i].util;
 
-                       printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
-                              abbrev_ref(refname, "refs/remotes/"));
-                       warn_dangling_symref(dangling_msg, refname);
-               }
+               if (!dry_run)
+                       result |= delete_ref(refname, NULL, 0);
 
-               free_remote_ref_states(&states);
+               printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
+                      abbrev_ref(refname, "refs/remotes/"));
+               warn_dangling_symref(dangling_msg, refname);
        }
 
+       free_remote_ref_states(&states);
        return result;
 }
 
@@ -1010,16 +1188,18 @@ struct remote_group {
        struct string_list *list;
 } remote_group;
 
-static int get_remote_group(const char *key, const char *value, void *cb)
+static int get_remote_group(const char *key, const char *value, void *num_hits)
 {
        if (!prefixcmp(key, "remotes.") &&
                        !strcmp(key + 8, remote_group.name)) {
                /* split list by white space */
                int space = strcspn(value, " \t\n");
                while (*value) {
-                       if (space > 1)
+                       if (space > 1) {
                                string_list_append(xstrndup(value, space),
                                                remote_group.list);
+                               ++*((int *)num_hits);
+                       }
                        value += space + (value[space] != '\0');
                        space = strcspn(value, " \t\n");
                }
@@ -1030,10 +1210,18 @@ static int get_remote_group(const char *key, const char *value, void *cb)
 
 static int update(int argc, const char **argv)
 {
-       int i, result = 0;
+       int i, result = 0, prune = 0;
        struct string_list list = { NULL, 0, 0, 0 };
        static const char *default_argv[] = { NULL, "default", NULL };
+       struct option options[] = {
+               OPT_GROUP("update specific options"),
+               OPT_BOOLEAN('p', "prune", &prune,
+                           "prune remotes after fetching"),
+               OPT_END()
+       };
 
+       argc = parse_options(argc, argv, options, builtin_remote_usage,
+                            PARSE_OPT_KEEP_ARGV0);
        if (argc < 2) {
                argc = 2;
                argv = default_argv;
@@ -1041,15 +1229,28 @@ static int update(int argc, const char **argv)
 
        remote_group.list = &list;
        for (i = 1; i < argc; i++) {
+               int groups_found = 0;
                remote_group.name = argv[i];
-               result = git_config(get_remote_group, NULL);
+               result = git_config(get_remote_group, &groups_found);
+               if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) {
+                       struct remote *remote;
+                       if (!remote_is_configured(argv[i]))
+                               die("No such remote or remote group: %s",
+                                   argv[i]);
+                       remote = remote_get(argv[i]);
+                       string_list_append(remote->name, remote_group.list);
+               }
        }
 
        if (!result && !list.nr  && argc == 2 && !strcmp(argv[1], "default"))
                result = for_each_remote(get_one_remote_for_update, &list);
 
-       for (i = 0; i < list.nr; i++)
-               result |= fetch_remote(list.items[i].string);
+       for (i = 0; i < list.nr; i++) {
+               int err = fetch_remote(list.items[i].string);
+               result |= err;
+               if (!err && prune)
+                       result |= prune_remote(list.items[i].string, 0);
+       }
 
        /* all names were strdup()ed or strndup()ed */
        list.strdup_strings = 1;