This is useful to mirror all the branches in the current repo to another. Signed-off-by: Felipe Contreras <felipe.contreras@xxxxxxxxx> --- 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 -- 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