git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC/PATCH] remote: add new sync command

From
Felipe Contreras <felipe.contreras@gmail.com>
Date
Nov 7, 2011, 16:07 UTC
Message-ID
<1320682032-12698-1-git-send-email-felipe.contreras@gmail.com>

This is useful to mirror all the branches in the current repo to another.

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 Documentation/git-remote.txt |   17 +++++++
 builtin/remote.c             |  108 ++++++++++++++++++++++++++++++++++++++++++
 t/t5505-remote.sh            |   15 ++++++
 3 files changed, 140 insertions(+), 0 deletions(-)
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 5a8c506..643fc8b 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -21,6 +21,7 @@ SYNOPSIS
 '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 sync' <name> [-a | --all] [-n | --new] [-p | --prune] [-f | --force] [--dry-run]
 
 DESCRIPTION
 -----------
@@ -169,6 +170,22 @@ be updated.  (See linkgit:git-config[1]).
 +
 With `--prune` option, prune all the remotes that are updated.
 
+'sync'::
+
+Synchronizes local branches with certain remote. This is useful to backup all
+the branches in a local repository to a remote one, regardless of what upstream
+is configured for each branch.
++
+With `--prune`, remote branches will be deleted if they are not also locally.
++
+With `--new`, local branches that are not yet in the remote will be pushed too.
++
+With `--all`, basically both `--prune` and `--new` will be selected.
++
+With `--force`, existing branches will be forced to update, like `git push
+--force`.
++
+With `--dry-run`, all the changes will be reported, but not really happen.
 
 DISCUSSION
 ----------
diff --git a/builtin/remote.c b/builtin/remote.c
index e1285be..b3b9b19 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -64,6 +64,11 @@ static const char * const builtin_remote_update_usage[] = {
 	NULL
 };
 
+static const char * const builtin_remote_sync_usage[] = {
+	"git remote sync <name> [-a|--all] [-n|--new] [-p|--prune] [<options>]",
+	NULL
+};
+
 static const char * const builtin_remote_seturl_usage[] = {
 	"git remote set-url [--push] <name> <newurl> [<oldurl>]",
 	"git remote set-url --add <name> <newurl>",
@@ -1492,6 +1497,107 @@ static int set_url(int argc, const char **argv)
 	return 0;
 }
 
+struct action {
+	struct string_list *list;
+	int type;
+};
+
+static int ref_cb(const char *refname,
+	const unsigned char *sha1, int flags, void *cb_data)
+{
+	struct action *action = cb_data;
+	struct string_list_item *item;
+	if (strcmp(refname, "HEAD") == 0)
+		return 0;
+	item = string_list_insert(action->list, refname);
+	item->util = (void *)((long)item->util | action->type);
+	return 0;
+}
+
+static int for_each_in_remote_ref(const char *name, each_ref_fn fn, void *cb_data)
+{
+	char prefix[1000];
+	sprintf(prefix, "refs/remotes/%s/", name);
+	return for_each_ref_in(prefix, fn, cb_data);
+}
+
+static int do_sync(int argc, const char **argv)
+{
+	const char *remotename;
+	struct string_list list;
+	struct action action;
+	struct remote *remote;
+	struct transport *transport;
+	int i, r, nonff;
+	char **refspec;
+	int refspec_nr = 0;
+	int prune = 0, new = 0, all = 0, flags = 0;
+
+	struct option options[] = {
+		OPT_BOOLEAN('p', "prune", &prune, "prune remote branches"),
+		OPT_BOOLEAN('n', "new", &new, "push new branches"),
+		OPT_BOOLEAN('a', "all", &all, "synchronize everything"),
+		OPT_BIT(0, "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
+		OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+		OPT_END()
+	};
+	argc = parse_options(argc, argv, NULL, options, builtin_remote_sync_usage,
+			     PARSE_OPT_KEEP_ARGV0);
+	if (argc < 2 || argc > 2)
+		usage_with_options(builtin_remote_sync_usage, options);
+
+	if (all)
+		prune = new = 1;
+
+	remotename = argv[1];
+	if (!remote_is_configured(remotename))
+		die("No such remote '%s'", remotename);
+
+	memset(&list, 0, sizeof(list));
+
+	action.list = &list;
+
+	action.type = 1;
+	for_each_in_remote_ref(remotename, ref_cb, &action);
+
+	action.type = 2;
+	for_each_branch_ref(ref_cb, &action);
+
+	refspec = xmalloc(sizeof(*refspec) * list.nr);
+
+	for (i = 0; i < list.nr; i++) {
+		const char *str = list.items[i].string;
+		char *t = NULL;
+
+		switch ((long)list.items[i].util) {
+		case 1:
+			if (prune)
+				asprintf(&t, ":%s", str);
+			break;
+		case 2:
+			if (new)
+				t = strdup(str);
+			break;
+		case 3:
+			t = strdup(str);
+			break;
+		}
+		if (t)
+			refspec[refspec_nr++] = t;
+	}
+
+	remote = remote_get(remotename);
+	transport = transport_get(remote, NULL);
+	r = transport_push(transport, refspec_nr, (const char **)refspec, flags, &nonff);
+
+	for (i = 0; i < refspec_nr; i++)
+		free(refspec[i]);
+
+	string_list_clear(&list, 0);
+
+	return r;
+}
+
 static int get_one_entry(struct remote *remote, void *priv)
 {
 	struct string_list *list = priv;
@@ -1581,6 +1687,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
 		result = prune(argc, argv);
 	else if (!strcmp(argv[0], "update"))
 		result = update(argc, argv);
+	else if (!strcmp(argv[0], "sync"))
+		result = do_sync(argc, argv);
 	else {
 		error("Unknown subcommand: %s", argv[0]);
 		usage_with_options(builtin_remote_usage, options);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index e8af615..13378c5 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -997,4 +997,19 @@ test_expect_success 'remote set-url --delete baz' '
 	cmp expect actual
 '
 
+test_expect_success 'remote sync' '
+	setup_repository sync-origin &&
+	(cd sync-origin &&
+	 git branch another &&
+	 git config receive.denyCurrentBranch ignore) &&
+	git clone sync-origin sync &&
+	(cd sync &&
+	 git branch -a > /tmp/a &&
+	 git remote sync origin &&
+	 git commit --allow-empty -m "Test" &&
+	 git checkout side &&
+	 git commit --allow-empty -m "Test" &&
+	 git remote sync -a origin)
+'
+
 test_done
-- 
1.7.7
Next: Jeff King
Message 1 of 26 in “remote: add new sync command”
  1. remote: add new sync commandFelipe Contreras, Nov 7, 2011
  2. Jeff KingNov 7, 2011
  3. Felipe ContrerasNov 7, 2011
  4. Jeff KingNov 7, 2011
  5. Felipe ContrerasNov 7, 2011
  6. Jeff KingNov 7, 2011
  7. Junio C HamanoNov 7, 2011
  8. Jeff KingNov 7, 2011
  9. Felipe ContrerasNov 8, 2011
  10. Junio C HamanoNov 8, 2011
  11. Felipe ContrerasNov 8, 2011
  12. Junio C HamanoNov 9, 2011
  13. Jakub NarebskiNov 11, 2011
  14. Junio C HamanoNov 11, 2011
  15. Jakub NarebskiNov 11, 2011
  16. Felipe ContrerasNov 8, 2011
  17. Jeff KingNov 8, 2011
  18. Felipe ContrerasNov 11, 2011
  19. Jeff KingNov 11, 2011
  20. Felipe ContrerasNov 12, 2011
  21. Jeff KingNov 14, 2011
  22. Felipe ContrerasNov 14, 2011
  23. Jeff KingNov 21, 2011
  24. Felipe ContrerasNov 21, 2011
  25. Jeff KingNov 30, 2011
  26. Felipe ContrerasNov 30, 2011

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.