Add git remote set-branches
authorJonathan Nieder <jrnieder@gmail.com>
Wed, 19 May 2010 18:38:50 +0000 (13:38 -0500)
committerJunio C Hamano <gitster@pobox.com>
Thu, 20 May 2010 03:45:41 +0000 (20:45 -0700)
Add ‘git remote set-branches’ for changing the list of tracked refs
for a remote repository with one "porcelain-level" command.  This
complements the longstanding ‘git remote add --track’ option.

The interface is based on the ‘git remote set-url’ subcommand.

   git remote set-branches base --add C
   git remote set-branches base A B D
   git remote set-branches base --delete D; # not implemented

Suggested-by: martin f. krafft <madduck@debian.org>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-remote.txt
builtin/remote.c
t/t5505-remote.sh

index 3fc599c0c7d5495ada81b20e4d37fa4936708270..de4386bf7eca2d236a320d83f514cd5010461a1c 100644 (file)
@@ -14,6 +14,7 @@ SYNOPSIS
 'git remote rename' <old> <new>
 'git remote rm' <name>
 'git remote set-head' <name> (-a | -d | <branch>)
+'git remote set-branches' <name> [--add] <branch>...
 'git remote set-url' [--push] <name> <newurl> [<oldurl>]
 'git remote set-url --add' [--push] <name> <newurl>
 'git remote set-url --delete' [--push] <name> <url>
@@ -104,6 +105,18 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
 `refs/remotes/origin/master` already exists; if not it must be fetched first.
 +
 
+'set-branches'::
+
+Changes the list of branches tracked by the named remote.
+This can be used to track a subset of the available remote branches
+after the initial setup for a remote.
++
+The named branches will be interpreted as if specified with the
+`-t` option on the 'git remote add' command line.
++
+With `--add`, instead of replacing the list of currently tracked
+branches, adds to that list.
+
 'set-url'::
 
 Changes URL remote points to. Sets first URL remote points to matching
index 277765b864202b2b6f20069e0cb3f95eaef4fcaa..c4d17b52bc7725c1e1c77e1ce3e2379729a924c4 100644 (file)
@@ -16,6 +16,7 @@ static const char * const builtin_remote_usage[] = {
        "git remote [-v | --verbose] show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+       "git remote set-branches <name> [--add] <branch>...",
        "git remote set-url <name> <newurl> [<oldurl>]",
        "git remote set-url --add <name> <newurl>",
        "git remote set-url --delete <name> <url>",
@@ -42,6 +43,12 @@ static const char * const builtin_remote_sethead_usage[] = {
        NULL
 };
 
+static const char * const builtin_remote_setbranches_usage[] = {
+       "git remote set-branches <name> <branch>...",
+       "git remote set-branches --add <name> <branch>...",
+       NULL
+};
+
 static const char * const builtin_remote_show_usage[] = {
        "git remote show [<options>] <name>",
        NULL
@@ -104,6 +111,20 @@ static int fetch_remote(const char *name)
        return 0;
 }
 
+static int add_branch(const char *key, const char *branchname,
+               const char *remotename, int mirror, struct strbuf *tmp)
+{
+       strbuf_reset(tmp);
+       strbuf_addch(tmp, '+');
+       if (mirror)
+               strbuf_addf(tmp, "refs/%s:refs/%s",
+                               branchname, branchname);
+       else
+               strbuf_addf(tmp, "refs/heads/%s:refs/remotes/%s/%s",
+                               branchname, remotename, branchname);
+       return git_config_set_multivar(key, tmp->buf, "^$", 0);
+}
+
 static int add(int argc, const char **argv)
 {
        int fetch = 0, mirror = 0;
@@ -151,17 +172,8 @@ static int add(int argc, const char **argv)
        if (track.nr == 0)
                string_list_append("*", &track);
        for (i = 0; i < track.nr; i++) {
-               struct string_list_item *item = track.items + i;
-
-               strbuf_reset(&buf2);
-               strbuf_addch(&buf2, '+');
-               if (mirror)
-                       strbuf_addf(&buf2, "refs/%s:refs/%s",
-                                       item->string, item->string);
-               else
-                       strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
-                                       item->string, name, item->string);
-               if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
+               if (add_branch(buf.buf, track.items[i].string,
+                               name, mirror, &buf2))
                        return 1;
        }
 
@@ -1265,6 +1277,72 @@ static int update(int argc, const char **argv)
        return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
 }
 
+static int remove_all_fetch_refspecs(const char *remote, const char *key)
+{
+       return git_config_set_multivar(key, NULL, NULL, 1);
+}
+
+static int add_branches(struct remote *remote, const char **branches,
+                       const char *key)
+{
+       const char *remotename = remote->name;
+       int mirror = remote->mirror;
+       struct strbuf refspec = STRBUF_INIT;
+
+       for (; *branches; branches++)
+               if (add_branch(key, *branches, remotename, mirror, &refspec)) {
+                       strbuf_release(&refspec);
+                       return 1;
+               }
+
+       strbuf_release(&refspec);
+       return 0;
+}
+
+static int set_remote_branches(const char *remotename, const char **branches,
+                               int add_mode)
+{
+       struct strbuf key = STRBUF_INIT;
+       struct remote *remote;
+
+       strbuf_addf(&key, "remote.%s.fetch", remotename);
+
+       if (!remote_is_configured(remotename))
+               die("No such remote '%s'", remotename);
+       remote = remote_get(remotename);
+
+       if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
+               strbuf_release(&key);
+               return 1;
+       }
+       if (add_branches(remote, branches, key.buf)) {
+               strbuf_release(&key);
+               return 1;
+       }
+
+       strbuf_release(&key);
+       return 0;
+}
+
+static int set_branches(int argc, const char **argv)
+{
+       int add_mode = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('\0', "add", &add_mode, "add branch"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, NULL, options,
+                            builtin_remote_setbranches_usage, 0);
+       if (argc == 0) {
+               error("no remote specified");
+               usage_with_options(builtin_remote_seturl_usage, options);
+       }
+       argv[argc] = NULL;
+
+       return set_remote_branches(argv[0], argv + 1, add_mode);
+}
+
 static int set_url(int argc, const char **argv)
 {
        int i, push_mode = 0, add_mode = 0, delete_mode = 0;
@@ -1430,6 +1508,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
                result = rm(argc, argv);
        else if (!strcmp(argv[0], "set-head"))
                result = set_head(argc, argv);
+       else if (!strcmp(argv[0], "set-branches"))
+               result = set_branches(argc, argv);
        else if (!strcmp(argv[0], "set-url"))
                result = set_url(argc, argv);
        else if (!strcmp(argv[0], "show"))
index 230c0cd784b317856749609cf64a5067e35a6965..8aaf53ed818126291cc644e02d6dcd569edbbff6 100755 (executable)
@@ -534,6 +534,94 @@ test_expect_success 'show empty remote' '
        )
 '
 
+test_expect_success 'remote set-branches requires a remote' '
+       test_must_fail git remote set-branches &&
+       test_must_fail git remote set-branches --add
+'
+
+test_expect_success 'remote set-branches' '
+       echo "+refs/heads/*:refs/remotes/scratch/*" >expect.initial &&
+       sort <<-\EOF >expect.add &&
+       +refs/heads/*:refs/remotes/scratch/*
+       +refs/heads/other:refs/remotes/scratch/other
+       EOF
+       sort <<-\EOF >expect.replace &&
+       +refs/heads/maint:refs/remotes/scratch/maint
+       +refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       EOF
+       sort <<-\EOF >expect.add-two &&
+       +refs/heads/maint:refs/remotes/scratch/maint
+       +refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       +refs/heads/pu:refs/remotes/scratch/pu
+       +refs/heads/t/topic:refs/remotes/scratch/t/topic
+       EOF
+       sort <<-\EOF >expect.setup-ffonly &&
+       refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       EOF
+       sort <<-\EOF >expect.respect-ffonly &&
+       refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       +refs/heads/pu:refs/remotes/scratch/pu
+       EOF
+
+       git clone .git/ setbranches &&
+       (
+               cd setbranches &&
+               git remote rename origin scratch &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.initial &&
+
+               git remote set-branches scratch --add other &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.add &&
+
+               git remote set-branches scratch maint master next &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.replace &&
+
+               git remote set-branches --add scratch pu t/topic &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.add-two &&
+
+               git config --unset-all remote.scratch.fetch &&
+               git config remote.scratch.fetch \
+                       refs/heads/master:refs/remotes/scratch/master &&
+               git config --add remote.scratch.fetch \
+                       +refs/heads/next:refs/remotes/scratch/next &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.setup-ffonly &&
+
+               git remote set-branches --add scratch pu &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.respect-ffonly
+       ) &&
+       test_cmp expect.initial actual.initial &&
+       test_cmp expect.add actual.add &&
+       test_cmp expect.replace actual.replace &&
+       test_cmp expect.add-two actual.add-two &&
+       test_cmp expect.setup-ffonly actual.setup-ffonly &&
+       test_cmp expect.respect-ffonly actual.respect-ffonly
+'
+
+test_expect_success 'remote set-branches with --mirror' '
+       echo "+refs/*:refs/*" >expect.initial &&
+       echo "+refs/heads/master:refs/heads/master" >expect.replace &&
+       git clone --mirror .git/ setbranches-mirror &&
+       (
+               cd setbranches-mirror &&
+               git remote rename origin scratch &&
+               git config --get-all remote.scratch.fetch >../actual.initial &&
+
+               git remote set-branches scratch heads/master &&
+               git config --get-all remote.scratch.fetch >../actual.replace
+       ) &&
+       test_cmp expect.initial actual.initial &&
+       test_cmp expect.replace actual.replace
+'
+
 test_expect_success 'new remote' '
        git remote add someremote foo &&
        echo foo >expect &&