threads / patch / 11150

patch, 6 partsbuiltin-remote

Subject: [PATCH 0/6] builtin-remote

## tl;dr

14 messages between Dec 5, 2007 and Dec 5, 2007. Diffs are folded; open one to read it.

replies: 13people: 3as markdown or json

Johannes Schindelin· Dec 5, 2007, 19:00 UTC · lore
Hi,

this series introduces remote as a builtin, and fixes a long-standing bug (if you created a remote with --mirror, prune would not do the right thing).

I know it is pretty late in the game for 1.5.4, but then, I do not expect this to go into that version...

Ciao, Dscho

Johannes Schindelin· Dec 5, 2007, 19:00 UTC · re: Johannes Schindelin · lore

(unknown)

[PATCH 1/6] path-list: add functions to work with unsorted lists

Up to now, path-lists were sorted at all times. But sometimes it is much more convenient to build the list and sort it at the end, or sort it not at all.

Add path_list_append() and sort_path_list() to allow that.

Also, add the unsorted_path_list_has_path() function, to do a linear search.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
	I should have done this much earlier...
 path-list.c |   30 ++++++++++++++++++++++++++++++
 path-list.h |    8 +++++++-
 2 files changed, 37 insertions(+), 1 deletions(-)
Show changes to 2 files +37 −1

path-list.c, path-list.h

diff --git a/path-list.c b/path-list.c
index 3d83b7b..92e5cf2 100644
--- a/path-list.c
+++ b/path-list.c
@@ -102,3 +102,33 @@ void print_path_list(const char *text, const struct path_list *p)
 	for (i = 0; i < p->nr; i++)
 		printf("%s:%p\n", p->items[i].path, p->items[i].util);
 }
+
+struct path_list_item *path_list_append(const char *path, struct path_list *list)
+{
+	ALLOC_GROW(list->items, list->nr + 1, list->alloc);
+	list->items[list->nr].path =
+		list->strdup_paths ? xstrdup(path) : (char *)path;
+	return list->items + list->nr++;
+}
+
+static int cmp_items(const void *a, const void *b)
+{
+	const struct path_list_item *one = a;
+	const struct path_list_item *two = b;
+	return strcmp(one->path, two->path);
+}
+
+void sort_path_list(struct path_list *list)
+{
+	qsort(list->items, list->nr, sizeof(*list->items), cmp_items);
+}
+
+int unsorted_path_list_has_path(struct path_list *list, const char *path)
+{
+	int i;
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return 1;
+	return 0;
+}
+
diff --git a/path-list.h b/path-list.h
index 5931e2c..ca2cbba 100644
--- a/path-list.h
+++ b/path-list.h
@@ -13,10 +13,16 @@ struct path_list
 };
 
 void print_path_list(const char *text, const struct path_list *p);
+void path_list_clear(struct path_list *list, int free_util);
 
+/* Use these functions only on sorted lists: */
 int path_list_has_path(const struct path_list *list, const char *path);
-void path_list_clear(struct path_list *list, int free_util);
 struct path_list_item *path_list_insert(const char *path, struct path_list *list);
 struct path_list_item *path_list_lookup(const char *path, struct path_list *list);
 
+/* Use these functions only on unsorted lists: */
+struct path_list_item *path_list_append(const char *path, struct path_list *list);
+void sort_path_list(struct path_list *list);
+int unsorted_path_list_has_path(struct path_list *list, const char *path);
+
 #endif /* PATH_LIST_H */
-- 
1.5.3.7.2157.g9598e
Johannes Schindelin· Dec 5, 2007, 19:05 UTC · re: Johannes Schindelin · lore

[PATCH 1/6] path-list: add functions to work with unsorted lists

Up to now, path-lists were sorted at all times. But sometimes it is much more convenient to build the list and sort it at the end, or sort it not at all.

Add path_list_append() and sort_path_list() to allow that.

Also, add the unsorted_path_list_has_path() function, to do a linear search.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
	I should have done that long ago.
 path-list.c |   30 ++++++++++++++++++++++++++++++
 path-list.h |    8 +++++++-
 2 files changed, 37 insertions(+), 1 deletions(-)
Show changes to 2 files +37 −1

path-list.c, path-list.h

diff --git a/path-list.c b/path-list.c
index 3d83b7b..92e5cf2 100644
--- a/path-list.c
+++ b/path-list.c
@@ -102,3 +102,33 @@ void print_path_list(const char *text, const struct path_list *p)
 	for (i = 0; i < p->nr; i++)
 		printf("%s:%p\n", p->items[i].path, p->items[i].util);
 }
+
+struct path_list_item *path_list_append(const char *path, struct path_list *list)
+{
+	ALLOC_GROW(list->items, list->nr + 1, list->alloc);
+	list->items[list->nr].path =
+		list->strdup_paths ? xstrdup(path) : (char *)path;
+	return list->items + list->nr++;
+}
+
+static int cmp_items(const void *a, const void *b)
+{
+	const struct path_list_item *one = a;
+	const struct path_list_item *two = b;
+	return strcmp(one->path, two->path);
+}
+
+void sort_path_list(struct path_list *list)
+{
+	qsort(list->items, list->nr, sizeof(*list->items), cmp_items);
+}
+
+int unsorted_path_list_has_path(struct path_list *list, const char *path)
+{
+	int i;
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return 1;
+	return 0;
+}
+
diff --git a/path-list.h b/path-list.h
index 5931e2c..ca2cbba 100644
--- a/path-list.h
+++ b/path-list.h
@@ -13,10 +13,16 @@ struct path_list
 };
 
 void print_path_list(const char *text, const struct path_list *p);
+void path_list_clear(struct path_list *list, int free_util);
 
+/* Use these functions only on sorted lists: */
 int path_list_has_path(const struct path_list *list, const char *path);
-void path_list_clear(struct path_list *list, int free_util);
 struct path_list_item *path_list_insert(const char *path, struct path_list *list);
 struct path_list_item *path_list_lookup(const char *path, struct path_list *list);
 
+/* Use these functions only on unsorted lists: */
+struct path_list_item *path_list_append(const char *path, struct path_list *list);
+void sort_path_list(struct path_list *list);
+int unsorted_path_list_has_path(struct path_list *list, const char *path);
+
 #endif /* PATH_LIST_H */
-- 
1.5.3.7.2157.g9598e
Johannes Schindelin· Dec 5, 2007, 19:01 UTC · re: Johannes Schindelin · lore

[PATCH 2/6] parseopt: add flag to stop on first non option

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
	This allows "git remote --option command --command-option".
 parse-options.c |    2 ++
 parse-options.h |    1 +
 2 files changed, 3 insertions(+), 0 deletions(-)
Show changes to 2 files +3 −0

parse-options.c, parse-options.h

diff --git a/parse-options.c b/parse-options.c
index e12b428..6df1230 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -229,6 +229,8 @@ int parse_options(int argc, const char **argv, const struct option *options,
 		const char *arg = args.argv[0];
 
 		if (*arg != '-' || !arg[1]) {
+			if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
+				break;
 			argv[j++] = args.argv[0];
 			continue;
 		}
diff --git a/parse-options.h b/parse-options.h
index 102ac31..0d40cd2 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -18,6 +18,7 @@ enum parse_opt_type {
 
 enum parse_opt_flags {
 	PARSE_OPT_KEEP_DASHDASH = 1,
+	PARSE_OPT_STOP_AT_NON_OPTION = 2,
 };
 
 enum parse_opt_option_flags {
-- 
1.5.3.7.2157.g9598e
Johannes Schindelin· Dec 5, 2007, 19:02 UTC · re: Johannes Schindelin · lore

[PATCH 3/6] Test "git remote show" and "git remote prune"

While at it, also fix a few instances where a cd was done outside of a subshell.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5505-remote.sh |   34 ++++++++++++++++++++++++++++++++++
 1 files changed, 34 insertions(+), 0 deletions(-)
Show changes to t/t5505-remote.sh +34 −0
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 636aec2..c7d7242 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -97,4 +97,38 @@ test_expect_success 'remove remote' '
 )
 '
 
+cat > test/expect << EOF
+* remote origin
+  URL: $(pwd)/one/.git
+  Remote branch(es) merged with 'git pull' while on branch master
+    master
+  New remote branches (next fetch will store in remotes/origin)
+    master
+  Tracked remote branches
+    side master
+EOF
+
+test_expect_success 'show' '
+	(cd test &&
+	 git config --add remote.origin.fetch \
+		refs/heads/master:refs/heads/upstream &&
+	 git fetch &&
+	 git branch -d -r origin/master &&
+	 (cd ../one &&
+	  echo 1 > file &&
+	  git commit -m update file) &&
+	 git remote show origin > output &&
+	 git diff expect output)
+'
+
+test_expect_success 'prune' '
+	(cd one &&
+	 git branch -m side side2) &&
+	(cd test &&
+	 git fetch origin &&
+	 git remote prune origin &&
+	 git rev-parse refs/remotes/origin/side2 &&
+	 ! git rev-parse refs/remotes/origin/side)
+'
+
 test_done
-- 
1.5.3.7.2157.g9598e
René Scharfe· Dec 5, 2007, 21:59 UTC · re: Johannes Schindelin · lore

Re: [PATCH 3/6] Test "git remote show" and "git remote prune"

Johannes Schindelin schrieb:
Show 7 quoted lines
> While at it, also fix a few instances where a cd was done outside of a
> subshell.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  t/t5505-remote.sh |   34 ++++++++++++++++++++++++++++++++++
>  1 files changed, 34 insertions(+), 0 deletions(-)
It seems to me the patch only adds tests, but doesn't fix existing ones.
 And looking at t5505-remote.sh, every call of cd is already done inside
of a subshell, so there doesn't seem to be anything to fix either. :-?
Johannes Schindelin· Dec 5, 2007, 22:38 UTC · re: René Scharfe · lore

Re: [PATCH 3/6] Test "git remote show" and "git remote prune"

Hi,
On Wed, 5 Dec 2007, Ren? Scharfe wrote:
Show 12 quoted lines
> Johannes Schindelin schrieb:
> > While at it, also fix a few instances where a cd was done outside of a 
> > subshell.
> > 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  t/t5505-remote.sh |   34 ++++++++++++++++++++++++++++++++++
> >  1 files changed, 34 insertions(+), 0 deletions(-)
> 
> It seems to me the patch only adds tests, but doesn't fix existing ones. 
> And looking at t5505-remote.sh, every call of cd is already done inside 
> of a subshell, so there doesn't seem to be anything to fix either. :-?

Right. This comment is from a long time ago, back when I had my own (incomplete) t5505-remote.sh. It was funny to me that Junio chose exactly the same name... but his implementation was different ;-)

I agree that this comment is obsolete.

Thanks, Dscho

Johannes Schindelin· Dec 5, 2007, 19:02 UTC · re: Johannes Schindelin · lore

[PATCH 4/6] Make git-remote a builtin

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile                                           |    3 +-
 builtin-remote.c                                   |  517 ++++++++++++++++++++
 builtin.h                                          |    1 +
 .../examples/git-remote.perl                       |    0 
 git.c                                              |    1 +
 remote.c                                           |    3 +-
 remote.h                                           |    1 +
 t/t5505-remote.sh                                  |    4 +-
 8 files changed, 526 insertions(+), 4 deletions(-)
 create mode 100644 builtin-remote.c
 rename git-remote.perl => contrib/examples/git-remote.perl (100%)
Show changes to 8 files +526 −4

Makefile, builtin-remote.c, builtin.h, git-remote.perl, git.c, remote.c, remote.h, t/t5505-remote.sh

diff --git a/Makefile b/Makefile
index 233bb5f..e840ca3 100644
--- a/Makefile
+++ b/Makefile
@@ -228,7 +228,7 @@ SCRIPT_SH = \
 SCRIPT_PERL = \
 	git-add--interactive.perl \
 	git-archimport.perl git-cvsimport.perl git-relink.perl \
-	git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \
+	git-cvsserver.perl git-cvsexportcommit.perl \
 	git-send-email.perl git-svn.perl
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
@@ -362,6 +362,7 @@ BUILTIN_OBJS = \
 	builtin-push.o \
 	builtin-read-tree.o \
 	builtin-reflog.o \
+	builtin-remote.o \
 	builtin-send-pack.o \
 	builtin-config.o \
 	builtin-rerere.o \
diff --git a/builtin-remote.c b/builtin-remote.c
new file mode 100644
index 0000000..41ac4a1
--- /dev/null
+++ b/builtin-remote.c
@@ -0,0 +1,517 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "transport.h"
+#include "remote.h"
+#include "path-list.h"
+#include "strbuf.h"
+#include "run-command.h"
+#include "refs.h"
+
+static const char * const builtin_remote_usage[] = {
+	"git remote",
+        "git remote add <name> <url>",
+        "git remote rm <name>",
+        "git remote show <name>",
+        "git remote prune <name>",
+        "git remote update [group]",
+	NULL
+};
+
+static int verbose;
+
+static inline int postfixcmp(const char *string, const char *postfix)
+{
+	int len1 = strlen(string), len2 = strlen(postfix);
+	if (len1 < len2)
+		return 1;
+	return strcmp(string + len1 - len2, postfix);
+}
+
+static inline const char *skip_prefix(const char *name, const char *prefix)
+{
+	return !name ? "" :
+		prefixcmp(name, prefix) ?  name : name + strlen(prefix);
+}
+
+static int opt_parse_track(const struct option *opt, const char *arg, int not)
+{
+	struct path_list *list = opt->value;
+	if (not)
+		path_list_clear(list, 0);
+	else
+		path_list_append(arg, list);
+	return 0;
+}
+
+static int fetch_remote(const char *name)
+{
+	const char *argv[] = { "fetch", name, NULL };
+	if (run_command_v_opt(argv, RUN_GIT_CMD))
+		return error ("Could not fetch %s", name);
+	return 0;
+}
+
+static int add(int argc, const char **argv)
+{
+	int fetch = 0, mirror = 0;
+	struct path_list track = { NULL, 0, 0 };
+	const char *master = NULL;
+	struct remote *remote;
+	struct strbuf buf, buf2;
+	const char *name, *url;
+	int i;
+
+	struct option options[] = {
+		OPT_GROUP("add specific options"),
+		OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
+		OPT_CALLBACK('t', "track", &track, "branch",
+			"branch(es) to track", opt_parse_track),
+		OPT_STRING('m', "master", &master, "branch", "master branch"),
+		OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
+
+	if (argc < 2)
+		usage_with_options(builtin_remote_usage, options);
+
+	name = argv[0];
+	url = argv[1];
+
+	remote = remote_get(name);
+	if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
+			remote->fetch_refspec_nr))
+		die ("remote %s already exists.", name);
+
+	strbuf_init(&buf, 0);
+	strbuf_init(&buf2, 0);
+
+	strbuf_addf(&buf, "remote.%s.url", name);
+	if (git_config_set(buf.buf, url))
+		return 1;
+
+	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);
+		if (mirror)
+			strbuf_addf(&buf2, "refs/%s:refs/%s",
+					item->path, item->path);
+		else
+			strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
+					item->path, name, item->path);
+		if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
+			return 1;
+	}
+
+	if (fetch && fetch_remote(name))
+		return 1;
+
+	if (master) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "refs/remotes/%s/HEAD", name);
+
+		strbuf_reset(&buf2);
+		strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
+
+		if (create_symref(buf.buf, buf2.buf, "remote add"))
+			return error ("Could not setup master '%s'", master);
+	}
+
+	strbuf_release(&buf);
+	strbuf_release(&buf2);
+	path_list_clear(&track, 0);
+
+	return 0;
+}
+
+struct branch_info {
+	char *remote;
+	struct path_list merge;
+};
+
+static struct path_list branch_list;
+
+static int config_read_branches(const char *key, const char *value)
+{
+	if (!prefixcmp(key, "branch.")) {
+		char *name;
+		struct path_list_item *item;
+		struct branch_info *info;
+		enum { REMOTE, MERGE } type;
+
+		key += 7;
+		if (!postfixcmp(key, ".remote")) {
+			name = xstrndup(key, strlen(key) - 7);
+			type = REMOTE;
+		} else if (!postfixcmp(key, ".merge")) {
+			name = xstrndup(key, strlen(key) - 6);
+			type = MERGE;
+		} else
+			return 0;
+
+		item = path_list_insert(name, &branch_list);
+
+		if (!item->util)
+			item->util = xcalloc(sizeof(struct branch_info), 1);
+		info = item->util;
+		if (type == REMOTE) {
+			if (info->remote)
+				warning ("more than one branch.%s", key);
+			info->remote = xstrdup(value);
+		} else {
+			char *space = strchr(value, ' ');
+			value = skip_prefix(value, "refs/heads/");
+			while (space) {
+				char *merge;
+				merge = xstrndup(value, space - value);
+				path_list_append(merge, &info->merge);
+				value = skip_prefix(space + 1, "refs/heads/");
+				space = strchr(value, ' ');
+			}
+			path_list_append(xstrdup(value), &info->merge);
+		}
+	}
+	return 0;
+}
+
+static void read_branches()
+{
+	if (branch_list.nr)
+		return;
+	git_config(config_read_branches);
+	sort_path_list(&branch_list);
+}
+
+struct ref_states {
+	struct remote *remote;
+	struct strbuf remote_prefix;
+	struct path_list new, stale, tracked;
+};
+
+static int handle_one_branch(const char *refname,
+	const unsigned char *sha1, int flags, void *cb_data)
+{
+	struct ref_states *states = cb_data;
+	struct refspec refspec;
+
+	memset(&refspec, 0, sizeof(refspec));
+	refspec.dst = (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) ||
+				unsorted_path_list_has_path(&states->new,
+					name))
+			return 0;
+		item = path_list_append(name, &states->stale);
+		item->util = xstrdup(refname);
+	}
+	return 0;
+}
+
+static int get_ref_states(const struct ref *ref, struct ref_states *states)
+{
+	struct ref *fetch_map = NULL, **tail = &fetch_map;
+	int i;
+
+	for (i = 0; i < states->remote->fetch_refspec_nr; i++)
+		if (get_fetch_map(ref, states->remote->fetch +i, &tail, 1))
+			die ("Could not get fetch map for refspec %s",
+				states->remote->fetch_refspec[i]);
+
+	states->new.strdup_paths = states->tracked.strdup_paths = 1;
+	for (ref = fetch_map; ref; ref = ref->next) {
+		struct path_list *target = &states->tracked;
+		unsigned char sha1[20];
+		void *util = NULL;
+
+		if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
+			target = &states->new;
+		else {
+			target = &states->tracked;
+			if (hashcmp(sha1, ref->new_sha1))
+				util = &states;
+		}
+		path_list_append(skip_prefix(ref->name, "refs/heads/"),
+				target)->util = util;
+	}
+	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;
+}
+
+static int remove_tracking_branch(const char *refname,
+	const unsigned char *sha1, int flags, void *cb_data)
+{
+	const char *remote_prefix = cb_data;
+
+	if (!prefixcmp(refname, remote_prefix) &&
+			delete_ref(refname, sha1))
+		return error("Could not remove branch %s", refname);
+	return 0;
+}
+
+static int rm(int argc, const char **argv)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct remote *remote;
+	struct strbuf buf;
+	int i;
+
+	if (argc != 2)
+		usage_with_options(builtin_remote_usage, options);
+
+	remote = remote_get(argv[1]);
+	if (!remote)
+		die ("No such remote: %s", argv[1]);
+
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "remote.%s", remote->name);
+	if (git_config_rename_section(buf.buf, NULL) < 1)
+		return error("Could not remove config section '%s'", buf.buf);
+
+	read_branches();
+	for (i = 0; i < branch_list.nr; i++) {
+		struct path_list_item *item = branch_list.items +i;
+		struct branch_info *info = item->util;
+		if (info->remote && !strcmp(info->remote, remote->name)) {
+			const char *keys[] = { "remote", "merge", NULL }, **k;
+			for (k = keys; *k; k++) {
+				strbuf_reset(&buf);
+				strbuf_addf(&buf, "branch.%s.%s",
+						item->path, *k);
+				if (git_config_set(buf.buf, NULL)) {
+					strbuf_release(&buf);
+					return -1;
+				}
+			}
+		}
+	}
+
+	/*
+	 * NEEDSTHOUGHT: this could check something like
+	 * !remote_find_tracking(remote, &refspec) to know if the
+	 * branch was really tracked.  But then we'll have to make
+	 * sure that no other remote writes into that branch.
+	 * Probably not worth it.
+	 */
+	strbuf_reset(&buf);
+	strbuf_addf(&buf, "refs/remotes/%s/", remote->name);
+	i = for_each_ref(remove_tracking_branch, buf.buf);
+	strbuf_release(&buf);
+
+	return i;
+}
+
+static void show_list(const char *title, struct path_list *list)
+{
+	int i;
+
+	if (!list->nr)
+		return;
+
+	printf(title, list->nr > 1 ? "es" : "");
+	printf("\n    ");
+	for (i = 0; i < list->nr; i++)
+		printf("%s%s", i ? " " : "", list->items[i].path);
+	printf("\n");
+}
+
+static int show_or_prune(int argc, const char **argv, int prune)
+{
+	int dry_run = 0, result = 0;
+	struct option options[] = {
+		OPT_GROUP("show specific options"),
+		OPT__DRY_RUN(&dry_run),
+		OPT_END()
+	};
+	struct ref_states states;
+
+	argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
+
+	if (argc < 1)
+		usage_with_options(builtin_remote_usage, options);
+
+	memset(&states, 0, sizeof(states));
+	for (; argc; argc--, argv++) {
+		struct transport *transport;
+		const struct ref *ref;
+		struct strbuf buf;
+		int i, got_states;
+
+		states.remote = remote_get(*argv);
+		if (!states.remote)
+			return error("No such remote: %s", *argv);
+		transport = transport_get(NULL, states.remote->url[0]);
+		ref = transport_get_remote_refs(transport);
+
+		read_branches();
+		got_states = get_ref_states(ref, &states);
+		if (got_states)
+			result = error("Error getting local info for '%s'",
+					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);
+			}
+
+			strbuf_release(&buf);
+			goto cleanup_states;
+		}
+
+		printf("* remote %s\n  URL: %s\n", *argv,
+			states.remote->url[0] ?
+				states.remote->url[0] : "(no URL)");
+
+		for (i = 0; i < branch_list.nr; i++) {
+			struct path_list_item *branch = branch_list.items + i;
+			struct branch_info *info = branch->util;
+			int j;
+
+			if (!info->merge.nr || strcmp(*argv, info->remote))
+				continue;
+			printf("  Remote branch%s merged with 'git pull' "
+				"while on branch %s\n   ",
+				info->merge.nr > 1 ? "es" : "",
+				branch->path);
+			for (j = 0; j < info->merge.nr; j++)
+				printf(" %s", info->merge.items[j].path);
+			printf("\n");
+		}
+
+		if (got_states)
+			continue;
+		strbuf_init(&buf, 0);
+		strbuf_addf(&buf, "  New remote branch%%s (next fetch will "
+			"store in remotes/%s)", states.remote->name);
+		show_list(buf.buf, &states.new);
+		strbuf_release(&buf);
+		show_list("  Stale tracking branch%s (use 'git remote prune')",
+				&states.stale);
+		show_list("  Tracked remote branch%s",
+				&states.tracked);
+
+		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", spec->force ? "+" : "",
+					skip_prefix(spec->src, "refs/heads/"),
+					spec->dst ? ":" : "",
+					skip_prefix(spec->dst, "refs/heads/"));
+			}
+		}
+cleanup_states:
+		/* NEEDSWORK: free remote */
+		path_list_clear(&states.new, 0);
+		path_list_clear(&states.stale, 0);
+		path_list_clear(&states.tracked, 0);
+	}
+
+	return result;
+}
+
+static int update_one(struct remote *remote, void *priv)
+{
+	if (!remote->skip_default_update)
+		return fetch_remote(remote->name);
+	return 0;
+}
+
+static int update(int argc, const char **argv)
+{
+	int i;
+
+	if (argc < 2)
+		return for_each_remote(update_one, NULL);
+
+	for (i = 1; i < argc; i++)
+		if (fetch_remote(argv[i]))
+			return 1;
+	return 0;
+}
+
+static int get_one_entry(struct remote *remote, void *priv)
+{
+	struct path_list *list = priv;
+
+	path_list_append(remote->name, list)->util = (void *)remote->url[0];
+	if (remote->url[0] && remote->url[1])
+		warning ("Remote %s has more than one URL", remote->name);
+
+	return 0;
+}
+
+static int show_all()
+{
+	struct path_list list = { NULL, 0, 0 };
+	int result = for_each_remote(get_one_entry, &list);
+
+	if (!result) {
+		int i;
+
+		sort_path_list(&list);
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item = list.items + i;
+			printf("%s%s%s\n", item->path,
+				verbose ? "\t" : "",
+				verbose && item->util ?
+					(const char *)item->util : "");
+		}
+	}
+	return result;
+}
+
+int cmd_remote(int argc, const char **argv, const char *prefix)
+{
+	struct option options[] = {
+		OPT__VERBOSE(&verbose),
+		OPT_END()
+	};
+	int result;
+
+	argc = parse_options(argc, argv, options, builtin_remote_usage,
+		PARSE_OPT_STOP_AT_NON_OPTION);
+
+	if (argc < 1)
+		result = show_all();
+	else if (!strcmp(argv[0], "add"))
+		result = add(argc, argv);
+	else if (!strcmp(argv[0], "rm"))
+		result = rm(argc, argv);
+	else if (!strcmp(argv[0], "show"))
+		result = show_or_prune(argc, argv, 0);
+	else if (!strcmp(argv[0], "prune"))
+		result = show_or_prune(argc, argv, 1);
+	else if (!strcmp(argv[0], "update"))
+		result = update(argc, argv);
+	else {
+		error ("Unknown subcommand: %s", argv[0]);
+		usage_with_options(builtin_remote_usage, options);
+	}
+
+	return result ? 1 : 0;
+}
diff --git a/builtin.h b/builtin.h
index cb675c4..20b6e75 100644
--- a/builtin.h
+++ b/builtin.h
@@ -66,6 +66,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
+extern int cmd_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_config(int argc, const char **argv, const char *prefix);
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_reset(int argc, const char **argv, const char *prefix);
diff --git a/git-remote.perl b/contrib/examples/git-remote.perl
similarity index 100%
rename from git-remote.perl
rename to contrib/examples/git-remote.perl
diff --git a/git.c b/git.c
index c8b7e74..80e9082 100644
--- a/git.c
+++ b/git.c
@@ -349,6 +349,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "push", cmd_push, RUN_SETUP },
 		{ "read-tree", cmd_read_tree, RUN_SETUP },
 		{ "reflog", cmd_reflog, RUN_SETUP },
+		{ "remote", cmd_remote, RUN_SETUP },
 		{ "repo-config", cmd_config },
 		{ "rerere", cmd_rerere, RUN_SETUP },
 		{ "reset", cmd_reset, RUN_SETUP },
diff --git a/remote.c b/remote.c
index 3fb0f99..ae7abe4 100644
--- a/remote.c
+++ b/remote.c
@@ -280,7 +280,8 @@ static int handle_config(const char *key, const char *value)
 			remote->fetch_tags = -1;
 	} else if (!strcmp(subkey, ".proxy")) {
 		remote->http_proxy = xstrdup(value);
-	}
+	} else if (!strcmp(subkey, ".skipdefaultupdate"))
+		remote->skip_default_update = 1;
 	return 0;
 }
 
diff --git a/remote.h b/remote.h
index 86e036d..bb26d3c 100644
--- a/remote.h
+++ b/remote.h
@@ -22,6 +22,7 @@ struct remote {
 	 * 2 to always fetch tags
 	 */
 	int fetch_tags;
+	int skip_default_update;
 
 	const char *receivepack;
 	const char *uploadpack;
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index c7d7242..d343a96 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -100,9 +100,9 @@ test_expect_success 'remove remote' '
 cat > test/expect << EOF
 * remote origin
   URL: $(pwd)/one/.git
-  Remote branch(es) merged with 'git pull' while on branch master
+  Remote branch merged with 'git pull' while on branch master
     master
-  New remote branches (next fetch will store in remotes/origin)
+  New remote branch (next fetch will store in remotes/origin)
     master
   Tracked remote branches
     side master
-- 
1.5.3.7.2157.g9598e
Johannes Schindelin· Dec 5, 2007, 21:40 UTC · re: Johannes Schindelin · lore

[PATCH 7/6] builtin-remote: guard remote->url accesses by remote->url_nr check

struct remote's url member is not a NULL terminated list. Instead, we have to check url_nr before accessing it.

Noticed by Johannes Sixt.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
	For your viewing pleasure, this is not a resend, but an amend.  
	Feel free to squash with 4/6, or to bug me to do it.
 builtin-remote.c |   10 ++++++----
 1 files changed, 6 insertions(+), 4 deletions(-)
Show changes to builtin-remote.c +6 −4
diff --git a/builtin-remote.c b/builtin-remote.c
index 142eb97..ea323e2 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -356,7 +356,8 @@ static int show_or_prune(int argc, const char **argv, int prune)
 		states.remote = remote_get(*argv);
 		if (!states.remote)
 			return error("No such remote: %s", *argv);
-		transport = transport_get(NULL, states.remote->url[0]);
+		transport = transport_get(NULL, states.remote->url_nr > 0 ?
+			states.remote->url[0] : NULL);
 		ref = transport_get_remote_refs(transport);
 
 		read_branches();
@@ -391,7 +392,7 @@ static int show_or_prune(int argc, const char **argv, int prune)
 		}
 
 		printf("* remote %s\n  URL: %s\n", *argv,
-			states.remote->url[0] ?
+			states.remote->url_nr > 0 ?
 				states.remote->url[0] : "(no URL)");
 
 		for (i = 0; i < branch_list.nr; i++) {
@@ -468,8 +469,9 @@ static int get_one_entry(struct remote *remote, void *priv)
 {
 	struct path_list *list = priv;
 
-	path_list_append(remote->name, list)->util = (void *)remote->url[0];
-	if (remote->url[0] && remote->url[1])
+	path_list_append(remote->name, list)->util = remote->url_nr ?
+		(void *)remote->url[0] : NULL;
+	if (remote->url_nr > 1)
 		warning ("Remote %s has more than one URL", remote->name);
 
 	return 0;
-- 
1.5.3.7.2157.g9598e
Johannes Schindelin· Dec 5, 2007, 19:02 UTC · re: Johannes Schindelin · lore

[PATCH 5/6] builtin-remote: prune remotes correctly that were added with --mirror

This adds special handling for mirror remotes.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
	This fixes a real bug in git-remote, which I tried to fix in the
	perl script, failing.
 builtin-remote.c  |   16 +++++++++++++---
 t/t5505-remote.sh |   16 ++++++++++++++++
 2 files changed, 29 insertions(+), 3 deletions(-)
Show changes to 2 files +29 −3

builtin-remote.c, t/t5505-remote.sh

diff --git a/builtin-remote.c b/builtin-remote.c
index 41ac4a1..142eb97 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -367,12 +367,22 @@ static int show_or_prune(int argc, const char **argv, int prune)
 
 		if (prune) {
 			struct strbuf buf;
+			int prefix_len;
 
 			strbuf_init(&buf, 0);
+			if (states.remote->fetch_refspec_nr == 1 &&
+					states.remote->fetch->pattern &&
+					!strcmp(states.remote->fetch->src,
+						states.remote->fetch->dst))
+				/* handle --mirror remote */
+				strbuf_addstr(&buf, "refs/heads/");
+			else
+				strbuf_addf(&buf, "refs/remotes/%s/", *argv);
+			prefix_len = buf.len;
+
 			for (i = 0; i < states.stale.nr; i++) {
-				strbuf_reset(&buf);
-				strbuf_addf(&buf, "refs/remotes/%s/%s", *argv,
-						states.stale.items[i].path);
+				strbuf_setlen(&buf, prefix_len);
+				strbuf_addstr(&buf, states.stale.items[i].path);
 				result |= delete_ref(buf.buf, NULL);
 			}
 
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index d343a96..2376e0a 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -131,4 +131,20 @@ test_expect_success 'prune' '
 	 ! git rev-parse refs/remotes/origin/side)
 '
 
+test_expect_success 'add --mirror && prune' '
+	(mkdir mirror &&
+	 cd mirror &&
+	 git init &&
+	 git remote add --mirror -f origin ../one) &&
+	(cd one &&
+	 git branch -m side2 side) &&
+	(cd mirror &&
+	 git rev-parse --verify refs/heads/side2 &&
+	 ! git rev-parse --verify refs/heads/side &&
+	 git fetch origin &&
+	 git remote prune origin &&
+	 ! git rev-parse --verify refs/heads/side2 &&
+	 git rev-parse --verify refs/heads/side)
+'
+
 test_done
-- 
1.5.3.7.2157.g9598e
Johannes Schindelin· Dec 5, 2007, 19:03 UTC · re: Johannes Schindelin · lore

[PATCH 6/6] DWIM "git add remote ..."

It is wrong to divert to "git remote add" when you typed the (more English) "git add remote". But is it also pretty convenient.

So implement it, but do not document it ;-)
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin-add.c     |    7 +++++++
 t/t5505-remote.sh |    6 ++++++
 2 files changed, 13 insertions(+), 0 deletions(-)
Show changes to 2 files +13 −0

builtin-add.c, t/t5505-remote.sh

diff --git a/builtin-add.c b/builtin-add.c
index 5c29cc2..b5b4c2f 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -196,6 +196,13 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 	const char **pathspec;
 	struct dir_struct dir;
 
+	if (argc > 1 && !strcmp(argv[1], "remote") &&
+			access("remote", F_OK)) {
+		argv[1] = argv[0];
+		argv[0] = "remote";
+		return cmd_remote(argc, argv, prefix);
+	}
+
 	argc = parse_options(argc, argv, builtin_add_options,
 			  builtin_add_usage, 0);
 	if (patch_interactive)
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 2376e0a..83ed70c 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -147,4 +147,10 @@ test_expect_success 'add --mirror && prune' '
 	 git rev-parse --verify refs/heads/side)
 '
 
+test_expect_success 'add remote' '
+	(cd test &&
+	 git add remote -f another-one ../one &&
+	 git diff master remotes/another-one/master)
+'
+
 test_done
-- 
1.5.3.7.2157.g9598e
Linus Torvalds· Dec 5, 2007, 19:34 UTC · re: Johannes Schindelin · lore

Re: [PATCH 6/6] DWIM "git add remote ..."

On Wed, 5 Dec 2007, Johannes Schindelin wrote:
> 
> It is wrong to divert to "git remote add" when you typed the
> (more English) "git add remote".  But is it also pretty convenient.

Please don't do cute things like this. Suddenly "git add remote" has two different meanings depending on whether you have a file called "remote" or not. That is *not* ok.

Not to mention the fact that your patch is also a horrible piece of bug-infested shit, to put it less-than-politely. Namely, you now broke "git add remote" when "remote" is a symbolic link pointing to hyperspace.

But even if you fix that bug, it only goes to show just how misguided things like these are. It's a *lot* more important to make sense and not have surprising special cases, than to try to make git command lines act as if they were free-form English.

		Linus
Johannes Schindelin· Dec 5, 2007, 19:47 UTC · re: Linus Torvalds · lore

Re: [PATCH 6/6] DWIM "git add remote ..."

Hi,
On Wed, 5 Dec 2007, Linus Torvalds wrote:
Show 18 quoted lines
> On Wed, 5 Dec 2007, Johannes Schindelin wrote:
> > 
> > It is wrong to divert to "git remote add" when you typed the (more 
> > English) "git add remote".  But is it also pretty convenient.
> 
> Please don't do cute things like this. Suddenly "git add remote" has two 
> different meanings depending on whether you have a file called "remote" 
> or not. That is *not* ok.
> 
> Not to mention the fact that your patch is also a horrible piece of 
> bug-infested shit, to put it less-than-politely. Namely, you now broke 
> "git add remote" when "remote" is a symbolic link pointing to 
> hyperspace.
> 
> But even if you fix that bug, it only goes to show just how misguided 
> things like these are. It's a *lot* more important to make sense and not 
> have surprising special cases, than to try to make git command lines act 
> as if they were free-form English.

Hehe. You're right, of course. But I could not resist showing off that patch which I wrote after I mixed up the command line of "remote add" for the 926th time.

Ciao, Dscho

← back to recent threads