[PATCH] Teach the --all option to 'git fetch'

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



'git remote' is meant for managing remotes and 'git fetch' is meant
for actually fetching data from remote repositories. Therefore, it is
not logical that you must use 'git remote update' to fetch from
several repositories at once. (Junio called 'git remote update'
a "half-baked UI experiment that failed" in topic 130891 in Gmane.)

Add the --all option to 'git fetch', to tell it to attempt to fetch
from all remotes. (The configuration variable skipDefaultUpdate for
the remote will NOT be consulted.)

Other options except -v and -q are silently ignored.

Signed-off-by: Björn Gustavsson <bgustavsson@xxxxxxxxx>
---
See 

  http://thread.gmane.org/gmane.comp.version-control.git/130819/focus=130891

for a previous discussion.

My implementation is deliberately minimal:

* There is no way to configure that certain remotes should be skipped by
  the --all option. I have never used skipDefaultUpdate with 'git remote
  update'. If there is a real need for that feature, it can easily be added.
  (But it should not use the existing skipDefaultUpdate configuration
  variable, as the name does not make sense for 'git fetch --all'.)

* All options except -q and -v are silently ignored. It might be useful
  to support some more options if there is a real need for them.
  (Perhaps --keep or --no-tags?)

 Documentation/git-fetch.txt |    5 +++
 builtin-fetch.c             |   80 +++++++++++++++++++++++++++++++++++++-----
 t/t5514-fetch-all.sh        |   76 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 151 insertions(+), 10 deletions(-)
 create mode 100755 t/t5514-fetch-all.sh

diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
index f2483d6..9172454 100644
--- a/Documentation/git-fetch.txt
+++ b/Documentation/git-fetch.txt
@@ -10,6 +10,8 @@ SYNOPSIS
 --------
 'git fetch' <options> <repository> <refspec>...
 
+'git fetch' --all <options>
+
 
 DESCRIPTION
 -----------
@@ -31,6 +33,9 @@ branches you are not interested in, you will not get them.
 
 OPTIONS
 -------
+--all::
+	Fetch all remotes.
+
 include::fetch-options.txt[]
 
 include::pull-fetch-param.txt[]
diff --git a/builtin-fetch.c b/builtin-fetch.c
index a35a6f8..c1c3c46 100644
--- a/builtin-fetch.c
+++ b/builtin-fetch.c
@@ -14,6 +14,7 @@
 
 static const char * const builtin_fetch_usage[] = {
 	"git fetch [options] [<repository> <refspec>...]",
+	"git fetch --all [options]",
 	NULL
 };
 
@@ -23,7 +24,7 @@ enum {
 	TAGS_SET = 2
 };
 
-static int append, force, keep, update_head_ok, verbosity;
+static int all, append, force, keep, update_head_ok, verbosity;
 static int tags = TAGS_DEFAULT;
 static const char *depth;
 static const char *upload_pack;
@@ -32,6 +33,8 @@ static struct transport *transport;
 
 static struct option builtin_fetch_options[] = {
 	OPT__VERBOSITY(&verbosity),
+	OPT_BOOLEAN(0, "all", &all,
+		    "fetch from all remotes"),
 	OPT_BOOLEAN('a', "append", &append,
 		    "append to .git/FETCH_HEAD instead of overwriting"),
 	OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
@@ -680,7 +683,53 @@ static void set_option(const char *name, const char *value)
 			name, transport->url);
 }
 
-int cmd_fetch(int argc, const char **argv, const char *prefix)
+static int get_one_remote_for_fetch(struct remote *remote, void *priv)
+{
+	struct string_list *list = priv;
+	string_list_append(remote->name, list);
+	return 0;
+}
+
+static int fetch_all(int argc)
+{
+	int i, result = 0;
+	struct string_list list = { NULL, 0, 0, 0 };
+	const char *argv[] = { "fetch", NULL, NULL, NULL, NULL };
+
+	if (argc == 1)
+		die("fetch --all does not take a repository argument");
+	else if (argc > 1)
+		die("fetch --all does not make sense with refspecs");
+
+	argc = 1;
+	if (verbosity >= 2)
+		argv[argc++] = "-v";
+	if (verbosity >= 1)
+		argv[argc++] = "-v";
+	else if (verbosity < 0)
+		argv[argc++] = "-q";
+
+	result = for_each_remote(get_one_remote_for_fetch, &list);
+
+	for (i = 0; i < list.nr; i++) {
+		const char *name = list.items[i].string;
+		argv[argc] = name;
+		if (verbosity >= 0)
+			printf("Fetching %s\n", name);
+		if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+			error("Could not fetch %s", name);
+			result = 1;
+		}
+	}
+
+	/* all names were strdup()ed or strndup()ed */
+	list.strdup_strings = 1;
+	string_list_clear(&list, 0);
+
+	return result;
+}
+
+static int fetch_one(int argc, const char **argv)
 {
 	struct remote *remote;
 	int i;
@@ -688,14 +737,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 	int ref_nr = 0;
 	int exit_code;
 
-	/* Record the command line for the reflog */
-	strbuf_addstr(&default_rla, "fetch");
-	for (i = 1; i < argc; i++)
-		strbuf_addf(&default_rla, " %s", argv[i]);
-
-	argc = parse_options(argc, argv, prefix,
-			     builtin_fetch_options, builtin_fetch_usage, 0);
-
 	if (argc == 0)
 		remote = remote_get(NULL);
 	else
@@ -746,3 +787,22 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 	transport = NULL;
 	return exit_code;
 }
+
+int cmd_fetch(int argc, const char **argv, const char *prefix)
+{
+	int i;
+
+	/* Record the command line for the reflog */
+	strbuf_addstr(&default_rla, "fetch");
+	for (i = 1; i < argc; i++)
+		strbuf_addf(&default_rla, " %s", argv[i]);
+
+	argc = parse_options(argc, argv, prefix,
+			     builtin_fetch_options, builtin_fetch_usage, 0);
+
+	if (all) {
+		return fetch_all(argc);
+	} else {
+		return fetch_one(argc, argv);
+	}
+}
diff --git a/t/t5514-fetch-all.sh b/t/t5514-fetch-all.sh
new file mode 100755
index 0000000..25244bf
--- /dev/null
+++ b/t/t5514-fetch-all.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='fetch --all works correctly'
+
+. ./test-lib.sh
+
+setup_repository () {
+	mkdir "$1" && (
+	cd "$1" &&
+	git init &&
+	>file &&
+	git add file &&
+	test_tick &&
+	git commit -m "Initial" &&
+	git checkout -b side &&
+	>elif &&
+	git add elif &&
+	test_tick &&
+	git commit -m "Second" &&
+	git checkout master
+	)
+}
+
+test_expect_success setup '
+	setup_repository one &&
+	setup_repository two &&
+	(
+		cd two && git branch another
+	) &&
+	git clone --mirror two three
+	git clone one test
+'
+
+cat > test/expect << EOF
+  one/master
+  one/side
+  origin/HEAD -> origin/master
+  origin/master
+  origin/side
+  three/another
+  three/master
+  three/side
+  two/another
+  two/master
+  two/side
+EOF
+
+test_expect_success 'git fetch --all' '
+	(cd test &&
+	 git remote add one ../one &&
+	 git remote add two ../two &&
+	 git remote add three ../three &&
+	 git fetch --all &&
+	 git branch -r > output &&
+	 test_cmp expect output)
+'
+
+test_expect_success 'git fetch --all should continue if a remote has errors' '
+	(git clone one test2 &&
+	 cd test2 &&
+	 git remote add bad ../non-existing &&
+	 git remote add one ../one &&
+	 git remote add two ../two &&
+	 git remote add three ../three &&
+	 test_must_fail git fetch --all &&
+	 git branch -r > output &&
+	 test_cmp ../test/expect output)
+'
+
+test_expect_success 'git fetch --all does not allow non-option arguments' '
+	(cd test &&
+	 test_must_fail git fetch --all origin &&
+	 test_must_fail git fetch --all origin master)
+'
+
+test_done
-- 
1.6.5.1.69.g36942

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]