cat-file --batch / --batch-check: do not exit if hashes are missing
[gitweb.git] / builtin-remote.c
index 25b02275dae539d58f89e51cfaacd363662e4368..c49f00f58b3cda6ac76a93d5f0bb36e8bc5bafb1 100644 (file)
@@ -19,6 +19,8 @@ static const char * const builtin_remote_usage[] = {
 
 static int verbose;
 
+static int show_all(void);
+
 static inline int postfixcmp(const char *string, const char *postfix)
 {
        int len1 = strlen(string), len2 = strlen(postfix);
@@ -46,6 +48,7 @@ static int opt_parse_track(const struct option *opt, const char *arg, int not)
 static int fetch_remote(const char *name)
 {
        const char *argv[] = { "fetch", name, NULL };
+       printf("Updating %s\n", name);
        if (run_command_v_opt(argv, RUN_GIT_CMD))
                return error("Could not fetch %s", name);
        return 0;
@@ -87,19 +90,24 @@ static int add(int argc, const char **argv)
        strbuf_init(&buf, 0);
        strbuf_init(&buf2, 0);
 
+       strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
+       if (!valid_fetch_refspec(buf2.buf))
+               die("'%s' is not a valid remote name", name);
+
        strbuf_addf(&buf, "remote.%s.url", name);
        if (git_config_set(buf.buf, url))
                return 1;
 
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "remote.%s.fetch", name);
+
        if (track.nr == 0)
                path_list_append("*", &track);
        for (i = 0; i < track.nr; i++) {
                struct path_list_item *item = track.items + i;
 
-               strbuf_reset(&buf);
-               strbuf_addf(&buf, "remote.%s.fetch", name);
-
                strbuf_reset(&buf2);
+               strbuf_addch(&buf2, '+');
                if (mirror)
                        strbuf_addf(&buf2, "refs/%s:refs/%s",
                                        item->path, item->path);
@@ -110,6 +118,13 @@ static int add(int argc, const char **argv)
                        return 1;
        }
 
+       if (mirror) {
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "remote.%s.mirror", name);
+               if (git_config_set(buf.buf, "yes"))
+                       return 1;
+       }
+
        if (fetch && fetch_remote(name))
                return 1;
 
@@ -138,7 +153,7 @@ struct branch_info {
 
 static struct path_list branch_list;
 
-static int config_read_branches(const char *key, const char *value)
+static int config_read_branches(const char *key, const char *value, void *cb)
 {
        if (!prefixcmp(key, "branch.")) {
                char *name;
@@ -185,13 +200,12 @@ static void read_branches(void)
 {
        if (branch_list.nr)
                return;
-       git_config(config_read_branches);
+       git_config(config_read_branches, NULL);
        sort_path_list(&branch_list);
 }
 
 struct ref_states {
        struct remote *remote;
-       struct strbuf remote_prefix;
        struct path_list new, stale, tracked;
 };
 
@@ -206,7 +220,10 @@ static int handle_one_branch(const char *refname,
        if (!remote_find_tracking(states->remote, &refspec)) {
                struct path_list_item *item;
                const char *name = skip_prefix(refspec.src, "refs/heads/");
-               if (unsorted_path_list_has_path(&states->tracked, name) ||
+               /* symbolic refs pointing nowhere were handled already */
+               if ((flags & REF_ISSYMREF) ||
+                               unsorted_path_list_has_path(&states->tracked,
+                                       name) ||
                                unsorted_path_list_has_path(&states->new,
                                        name))
                        return 0;
@@ -244,31 +261,72 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
        }
        free_refs(fetch_map);
 
-       strbuf_addf(&states->remote_prefix,
-               "refs/remotes/%s/", states->remote->name);
        for_each_ref(handle_one_branch, states);
        sort_path_list(&states->stale);
 
        return 0;
 }
 
+struct known_remote {
+       struct known_remote *next;
+       struct remote *remote;
+};
+
+struct known_remotes {
+       struct remote *to_delete;
+       struct known_remote *list;
+};
+
+static int add_known_remote(struct remote *remote, void *cb_data)
+{
+       struct known_remotes *all = cb_data;
+       struct known_remote *r;
+
+       if (!strcmp(all->to_delete->name, remote->name))
+               return 0;
+
+       r = xmalloc(sizeof(*r));
+       r->remote = remote;
+       r->next = all->list;
+       all->list = r;
+       return 0;
+}
+
 struct branches_for_remote {
-       const char *prefix;
+       struct remote *remote;
        struct path_list *branches;
+       struct known_remotes *keep;
 };
 
 static int add_branch_for_removal(const char *refname,
        const unsigned char *sha1, int flags, void *cb_data)
 {
        struct branches_for_remote *branches = cb_data;
+       struct refspec refspec;
+       struct path_list_item *item;
+       struct known_remote *kr;
 
-       if (!prefixcmp(refname, branches->prefix)) {
-               struct path_list_item *item;
-               item = path_list_append(refname, branches->branches);
-               item->util = xmalloc(20);
-               hashcpy(item->util, sha1);
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (remote_find_tracking(branches->remote, &refspec))
+               return 0;
+
+       /* don't delete a branch if another remote also uses it */
+       for (kr = branches->keep->list; kr; kr = kr->next) {
+               memset(&refspec, 0, sizeof(refspec));
+               refspec.dst = (char *)refname;
+               if (!remote_find_tracking(kr->remote, &refspec))
+                       return 0;
        }
 
+       /* make sure that symrefs are deleted */
+       if (flags & REF_ISSYMREF)
+               return unlink(git_path(refname));
+
+       item = path_list_append(refname, branches->branches);
+       item->util = xmalloc(20);
+       hashcpy(item->util, sha1);
+
        return 0;
 }
 
@@ -293,8 +351,9 @@ static int rm(int argc, const char **argv)
        };
        struct remote *remote;
        struct strbuf buf;
+       struct known_remotes known_remotes = { NULL, NULL };
        struct path_list branches = { NULL, 0, 0, 1 };
-       struct branches_for_remote cb_data = { NULL, &branches };
+       struct branches_for_remote cb_data = { NULL, &branches, &known_remotes };
        int i;
 
        if (argc != 2)
@@ -304,6 +363,9 @@ static int rm(int argc, const char **argv)
        if (!remote)
                die("No such remote: %s", argv[1]);
 
+       known_remotes.to_delete = remote;
+       for_each_remote(add_known_remote, &known_remotes);
+
        strbuf_init(&buf, 0);
        strbuf_addf(&buf, "remote.%s", remote->name);
        if (git_config_rename_section(buf.buf, NULL) < 1)
@@ -332,9 +394,7 @@ static int rm(int argc, const char **argv)
         * the branches one by one, since for_each_ref() relies on cached
         * refs, which are invalidated when deleting a branch.
         */
-       strbuf_reset(&buf);
-       strbuf_addf(&buf, "refs/remotes/%s/", remote->name);
-       cb_data.prefix = buf.buf;
+       cb_data.remote = remote;
        i = for_each_ref(add_branch_for_removal, &cb_data);
        strbuf_release(&buf);
 
@@ -371,8 +431,11 @@ static int show_or_prune(int argc, const char **argv, int prune)
 
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
 
-       if (argc < 1)
+       if (argc < 1) {
+               if (!prune)
+                       return show_all();
                usage_with_options(builtin_remote_usage, options);
+       }
 
        memset(&states, 0, sizeof(states));
        for (; argc; argc--, argv++) {
@@ -387,6 +450,7 @@ static int show_or_prune(int argc, const char **argv, int prune)
                transport = transport_get(NULL, states.remote->url_nr > 0 ?
                        states.remote->url[0] : NULL);
                ref = transport_get_remote_refs(transport);
+               transport_disconnect(transport);
 
                read_branches();
                got_states = get_ref_states(ref, &states);
@@ -395,17 +459,10 @@ static int show_or_prune(int argc, const char **argv, int prune)
                                        states.remote->name);
 
                if (prune) {
-                       struct strbuf buf;
-
-                       strbuf_init(&buf, 0);
                        for (i = 0; i < states.stale.nr; i++) {
-                               strbuf_reset(&buf);
-                               strbuf_addf(&buf, "refs/remotes/%s/%s", *argv,
-                                               states.stale.items[i].path);
-                               result |= delete_ref(buf.buf, NULL);
+                               const char *refname = states.stale.items[i].util;
+                               result |= delete_ref(refname, NULL);
                        }
-
-                       strbuf_release(&buf);
                        goto cleanup_states;
                }
 
@@ -452,6 +509,7 @@ static int show_or_prune(int argc, const char **argv, int prune)
                                        spec->dst ? ":" : "",
                                        skip_prefix(spec->dst, "refs/heads/"));
                        }
+                       printf("\n");
                }
 cleanup_states:
                /* NEEDSWORK: free remote */
@@ -463,24 +521,65 @@ static int show_or_prune(int argc, const char **argv, int prune)
        return result;
 }
 
-static int update_one(struct remote *remote, void *priv)
+static int get_one_remote_for_update(struct remote *remote, void *priv)
 {
+       struct path_list *list = priv;
        if (!remote->skip_default_update)
-               return fetch_remote(remote->name);
+               path_list_append(xstrdup(remote->name), list);
+       return 0;
+}
+
+struct remote_group {
+       const char *name;
+       struct path_list *list;
+} remote_group;
+
+static int get_remote_group(const char *key, const char *value, void *cb)
+{
+       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)
+                               path_list_append(xstrndup(value, space),
+                                               remote_group.list);
+                       value += space + (value[space] != '\0');
+                       space = strcspn(value, " \t\n");
+               }
+       }
+
        return 0;
 }
 
 static int update(int argc, const char **argv)
 {
-       int i;
+       int i, result = 0;
+       struct path_list list = { NULL, 0, 0, 0 };
+       static const char *default_argv[] = { NULL, "default", NULL };
 
-       if (argc < 2)
-               return for_each_remote(update_one, NULL);
+       if (argc < 2) {
+               argc = 2;
+               argv = default_argv;
+       }
 
-       for (i = 1; i < argc; i++)
-               if (fetch_remote(argv[i]))
-                       return 1;
-       return 0;
+       remote_group.list = &list;
+       for (i = 1; i < argc; i++) {
+               remote_group.name = argv[i];
+               result = git_config(get_remote_group, NULL);
+       }
+
+       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].path);
+
+       /* all names were strdup()ed or strndup()ed */
+       list.strdup_paths = 1;
+       path_list_clear(&list, 0);
+
+       return result;
 }
 
 static int get_one_entry(struct remote *remote, void *priv)