{"thread":{"id":"10776","subject":"git push mirror mode V4 (replacement stack)","startedAt":"2007-11-09T23:30:41Z","lastAt":"2007-11-12T21:35:42Z","messageCount":7,"participants":["Andy Whitcroft","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"59141","messageId":"20071109233041.GC301@shadowen.org","threadId":"10776","inReplyTo":null,"subject":"git push mirror mode V4 (replacement stack)","fromName":"Andy Whitcroft","fromEmail":"apw@shadowen.org","sentAt":"2007-11-09T23:30:41Z","receivedAt":"2007-11-09T23:30:41Z","isPatch":false,"sender":{"key":"apw@shadowen.org","avatar":"https://gravatar.com/avatar/d3088262854661a913ef35cc40fedcc270142d4461791142bc1ea0b2a4e2e147?d=mp&s=160"},"body":"Following this mail is a complete replacement git push mirror mode\nstack (V4).  It folds down all the various patches into a logical\nsequence (thanks Dscho).  This stack passes the entire test suite,\nand I have been using the same code for real work here.\n\nPlease replace what you have in pu with this stack.\n\n-apw\n"},{"id":"59142","messageId":"1194651130.0@pinky","threadId":"10776","inReplyTo":"20071109233041.GC301@shadowen.org","subject":"[PATCH 1/4] Teach send-pack a mirror mode","fromName":"Andy Whitcroft","fromEmail":"apw@shadowen.org","sentAt":null,"receivedAt":"2007-11-09T23:30:41Z","isPatch":true,"sender":{"key":"apw@shadowen.org","avatar":"https://gravatar.com/avatar/d3088262854661a913ef35cc40fedcc270142d4461791142bc1ea0b2a4e2e147?d=mp&s=160"},"body":"\nExisting \"git push --all\" is almost perfect for backing up to\nanother repository, except that \"--all\" only means \"all\nbranches\" in modern git, and it does not delete old branches and\ntags that exist at the back-up repository that you have removed\nfrom your local repository.\n\nThis teaches \"git-send-pack\" a new \"--mirror\" option.  The\ndifference from the \"--all\" option are that (1) it sends all\nrefs, not just branches, and (2) it deletes old refs you no\nlonger have on the local side from the remote side.\n\nOriginal patch by Junio C Hamano.\n\nSigned-off-by: Andy Whitcroft <apw@shadowen.org>\n---\n builtin-send-pack.c |   46 ++++++++++++++++++++++++++++++++++------------\n http-push.c         |    4 ++--\n remote.c            |   15 ++++++++++-----\n remote.h            |    7 +++++++\n send-pack.h         |    1 +\n 5 files changed, 54 insertions(+), 19 deletions(-)\ndiff --git a/builtin-send-pack.c b/builtin-send-pack.c\nindex 5a0f5c6..d42164e 100644\n--- a/builtin-send-pack.c\n+++ b/builtin-send-pack.c\n@@ -8,7 +8,7 @@\n #include \"send-pack.h\"\n \n static const char send_pack_usage[] =\n-\"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\\n\"\n+\"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\\n\"\n \"  --all and explicit <ref> specification are mutually exclusive.\";\n \n static struct send_pack_args args = {\n@@ -227,6 +227,12 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest\n \tint allow_deleting_refs = 0;\n \tint expect_status_report = 0;\n \tint shown_dest = 0;\n+\tint flags = MATCH_REFS_NONE;\n+\n+\tif (args.send_all)\n+\t\tflags |= MATCH_REFS_ALL;\n+\tif (args.send_mirror)\n+\t\tflags |= MATCH_REFS_MIRROR;\n \n \t/* No funny business with the matcher */\n \tremote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);\n@@ -242,7 +248,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest\n \tif (!remote_tail)\n \t\tremote_tail = &remote_refs;\n \tif (match_refs(local_refs, remote_refs, &remote_tail,\n-\t\t       nr_refspec, refspec, args.send_all))\n+\t\t\t\t\t       nr_refspec, refspec, flags))\n \t\treturn -1;\n \n \tif (!remote_refs) {\n@@ -259,20 +265,28 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest\n \t\tchar old_hex[60], *new_hex;\n \t\tint will_delete_ref;\n \t\tconst char *pretty_ref;\n-\t\tconst char *pretty_peer;\n+\t\tconst char *pretty_peer = NULL; /* only used when not deleting */\n+\t\tconst unsigned char *new_sha1;\n \n-\t\tif (!ref->peer_ref)\n-\t\t\tcontinue;\n+\t\tif (!ref->peer_ref) {\n+\t\t\tif (!args.send_mirror)\n+\t\t\t\tcontinue;\n+\t\t\tnew_sha1 = null_sha1;\n+\t\t}\n+\t\telse\n+\t\t\tnew_sha1 = ref->peer_ref->new_sha1;\n \n \t\tif (!shown_dest) {\n \t\t\tfprintf(stderr, \"To %s\\n\", dest);\n \t\t\tshown_dest = 1;\n \t\t}\n \n+\t\twill_delete_ref = is_null_sha1(new_sha1);\n+\n \t\tpretty_ref = prettify_ref(ref->name);\n-\t\tpretty_peer = prettify_ref(ref->peer_ref->name);\n+\t\tif (!will_delete_ref)\n+\t\t\tpretty_peer = prettify_ref(ref->peer_ref->name);\n \n-\t\twill_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);\n \t\tif (will_delete_ref && !allow_deleting_refs) {\n \t\t\tfprintf(stderr, \" ! %-*s %s (remote does not support deleting refs)\\n\",\n \t\t\t\t\tSUMMARY_WIDTH, \"[rejected]\", pretty_ref);\n@@ -280,7 +294,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest\n \t\t\tcontinue;\n \t\t}\n \t\tif (!will_delete_ref &&\n-\t\t    !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {\n+\t\t    !hashcmp(ref->old_sha1, new_sha1)) {\n \t\t\tif (args.verbose)\n \t\t\t\tfprintf(stderr, \" = %-*s %s -> %s\\n\",\n \t\t\t\t\tSUMMARY_WIDTH, \"[up to date]\",\n@@ -312,8 +326,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest\n \t\t    !is_null_sha1(ref->old_sha1) &&\n \t\t    !ref->force) {\n \t\t\tif (!has_sha1_file(ref->old_sha1) ||\n-\t\t\t    !ref_newer(ref->peer_ref->new_sha1,\n-\t\t\t\t       ref->old_sha1)) {\n+\t\t\t    !ref_newer(new_sha1, ref->old_sha1)) {\n \t\t\t\t/* We do not have the remote ref, or\n \t\t\t\t * we know that the remote ref is not\n \t\t\t\t * an ancestor of what we are trying to\n@@ -328,7 +341,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n-\t\thashcpy(ref->new_sha1, ref->peer_ref->new_sha1);\n+\t\thashcpy(ref->new_sha1, new_sha1);\n \t\tif (!will_delete_ref)\n \t\t\tnew_refs++;\n \t\tstrcpy(old_hex, sha1_to_hex(ref->old_sha1));\n@@ -459,6 +472,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\targs.dry_run = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--mirror\")) {\n+\t\t\t\targs.send_mirror = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tif (!strcmp(arg, \"--force\")) {\n \t\t\t\targs.force_update = 1;\n \t\t\t\tcontinue;\n@@ -483,7 +500,12 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t}\n \tif (!dest)\n \t\tusage(send_pack_usage);\n-\tif (heads && args.send_all)\n+\t/*\n+\t * --all and --mirror are incompatible; neither makes sense\n+\t * with any refspecs.\n+\t */\n+\tif ((heads && (args.send_all || args.send_mirror)) ||\n+\t\t\t\t\t(args.send_all && args.send_mirror))\n \t\tusage(send_pack_usage);\n \n \tif (remote_name) {\ndiff --git a/http-push.c b/http-push.c\nindex 99328f5..66b81f1 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -78,7 +78,7 @@ static struct curl_slist *no_pragma_header;\n static struct curl_slist *default_headers;\n \n static int push_verbosely;\n-static int push_all;\n+static int push_all = MATCH_REFS_NONE;\n static int force_all;\n static int dry_run;\n \n@@ -2300,7 +2300,7 @@ int main(int argc, char **argv)\n \n \t\tif (*arg == '-') {\n \t\t\tif (!strcmp(arg, \"--all\")) {\n-\t\t\t\tpush_all = 1;\n+\t\t\t\tpush_all = MATCH_REFS_ALL;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--force\")) {\ndiff --git a/remote.c b/remote.c\nindex 59defdb..09b7aad 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -722,10 +722,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,\n  * without thinking.\n  */\n int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,\n-\t       int nr_refspec, const char **refspec, int all)\n+\t       int nr_refspec, const char **refspec, int flags)\n {\n \tstruct refspec *rs =\n \t\tparse_ref_spec(nr_refspec, (const char **) refspec);\n+\tint send_all = flags & MATCH_REFS_ALL;\n+\tint send_mirror = flags & MATCH_REFS_MIRROR;\n \n \tif (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))\n \t\treturn -1;\n@@ -742,7 +744,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,\n \t\t\tif (!pat)\n \t\t\t\tcontinue;\n \t\t}\n-\t\telse if (prefixcmp(src->name, \"refs/heads/\"))\n+\t\telse if (!send_mirror && prefixcmp(src->name, \"refs/heads/\"))\n \t\t\t/*\n \t\t\t * \"matching refs\"; traditionally we pushed everything\n \t\t\t * including refs outside refs/heads/ hierarchy, but\n@@ -763,10 +765,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,\n \t\tif (dst_peer && dst_peer->peer_ref)\n \t\t\t/* We're already sending something to this ref. */\n \t\t\tgoto free_name;\n-\t\tif (!dst_peer && !nr_refspec && !all)\n-\t\t\t/* Remote doesn't have it, and we have no\n+\n+\t\tif (!dst_peer && !nr_refspec && !(send_all || send_mirror))\n+\t\t\t/*\n+\t\t\t * Remote doesn't have it, and we have no\n \t\t\t * explicit pattern, and we don't have\n-\t\t\t * --all. */\n+\t\t\t * --all nor --mirror.\n+\t\t\t */\n \t\t\tgoto free_name;\n \t\tif (!dst_peer) {\n \t\t\t/* Create a new one and link it */\ndiff --git a/remote.h b/remote.h\nindex 6a4c7a0..b10036c 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -102,4 +102,11 @@ struct branch *branch_get(const char *name);\n int branch_has_merge_config(struct branch *branch);\n int branch_merge_matches(struct branch *, int n, const char *);\n \n+/* Flags to match_refs. */\n+enum match_refs_flags {\n+\tMATCH_REFS_NONE\t\t= 0,\n+\tMATCH_REFS_ALL \t\t= (1 << 0),\n+\tMATCH_REFS_MIRROR\t= (1 << 1),\n+};\n+\n #endif\ndiff --git a/send-pack.h b/send-pack.h\nindex 7a24f71..8ff1dc3 100644\n--- a/send-pack.h\n+++ b/send-pack.h\n@@ -5,6 +5,7 @@ struct send_pack_args {\n \tconst char *receivepack;\n \tunsigned verbose:1,\n \t\tsend_all:1,\n+\t\tsend_mirror:1,\n \t\tforce_update:1,\n \t\tuse_thin_pack:1,\n \t\tdry_run:1;\n"},{"id":"59143","messageId":"1194651161.0@pinky","threadId":"10776","inReplyTo":"20071109233041.GC301@shadowen.org","subject":"[PATCH 3/4] Add tests for git push'es mirror mode","fromName":"Andy Whitcroft","fromEmail":"apw@shadowen.org","sentAt":null,"receivedAt":"2007-11-09T23:30:41Z","isPatch":true,"sender":{"key":"apw@shadowen.org","avatar":"https://gravatar.com/avatar/d3088262854661a913ef35cc40fedcc270142d4461791142bc1ea0b2a4e2e147?d=mp&s=160"},"body":"\nAdd some tests for git push --mirror mode.\n\nSigned-off-by: Andy Whitcroft <apw@shadowen.org>\n---\n t/t5517-push-mirror.sh |  228 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 228 insertions(+), 0 deletions(-)\ndiff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh\nnew file mode 100755\nindex 0000000..0fc6778\n--- /dev/null\n+++ b/t/t5517-push-mirror.sh\n@@ -0,0 +1,228 @@\n+#!/bin/sh\n+\n+test_description='pushing to a mirror repository'\n+\n+. ./test-lib.sh\n+\n+D=`pwd`\n+\n+invert () {\n+\tif \"$@\"; then\n+\t\treturn 1\n+\telse\n+\t\treturn 0\n+\tfi\n+}\n+\n+mk_repo_pair () {\n+\trm -rf master mirror &&\n+\tmkdir mirror &&\n+\t(\n+\t\tcd mirror &&\n+\t\tgit init\n+\t) &&\n+\tmkdir master &&\n+\t(\n+\t\tcd master &&\n+\t\tgit init &&\n+\t\tgit config remote.up.url ../mirror\n+\t)\n+}\n+\n+\n+# BRANCH tests\n+test_expect_success 'push mirror does not create new branches' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/heads/master) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&\n+\ttest \"$master_master\" = \"$mirror_master\"\n+\n+'\n+\n+test_expect_success 'push mirror does not update existing branches' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit push --mirror up &&\n+\t\techo two >foo && git add foo && git commit -m two &&\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/heads/master) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&\n+\ttest \"$master_master\" = \"$mirror_master\"\n+\n+'\n+\n+test_expect_success 'push mirror does not force update existing branches' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit push --mirror up &&\n+\t\techo two >foo && git add foo && git commit -m two &&\n+\t\tgit push --mirror up &&\n+\t\tgit reset --hard HEAD^\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/heads/master) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&\n+\ttest \"$master_master\" = \"$mirror_master\"\n+\n+'\n+\n+test_expect_success 'push mirror does not remove branches' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit branch remove master &&\n+\t\tgit push --mirror up &&\n+\t\tgit branch -D remove\n+\t\tgit push --mirror up\n+\t) &&\n+\t(\n+\t\tcd mirror &&\n+\t\tinvert git show-ref -s --verify refs/heads/remove\n+\t)\n+\n+'\n+\n+test_expect_success 'push mirror does not add, update and remove branches together' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit branch remove master &&\n+\t\tgit push --mirror up &&\n+\t\tgit branch -D remove &&\n+\t\tgit branch add master &&\n+\t\techo two >foo && git add foo && git commit -m two &&\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/heads/master) &&\n+\tmaster_add=$(cd master && git show-ref -s --verify refs/heads/add) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&\n+\tmirror_add=$(cd mirror && git show-ref -s --verify refs/heads/add) &&\n+\ttest \"$master_master\" = \"$mirror_master\" &&\n+\ttest \"$master_add\" = \"$mirror_add\" &&\n+\t(\n+\t\tcd mirror &&\n+\t\tinvert git show-ref -s --verify refs/heads/remove\n+\t)\n+\n+'\n+\n+\n+# TAG tests\n+test_expect_success 'push mirror does not create new tags' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit tag -f tmaster master &&\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&\n+\ttest \"$master_master\" = \"$mirror_master\"\n+\n+'\n+\n+test_expect_success 'push mirror does not update existing tags' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit tag -f tmaster master &&\n+\t\tgit push --mirror up &&\n+\t\techo two >foo && git add foo && git commit -m two &&\n+\t\tgit tag -f tmaster master &&\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&\n+\ttest \"$master_master\" = \"$mirror_master\"\n+\n+'\n+\n+test_expect_success 'push mirror does not force update existing tags' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit tag -f tmaster master &&\n+\t\tgit push --mirror up &&\n+\t\techo two >foo && git add foo && git commit -m two &&\n+\t\tgit tag -f tmaster master &&\n+\t\tgit push --mirror up &&\n+\t\tgit reset --hard HEAD^\n+\t\tgit tag -f tmaster master &&\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&\n+\ttest \"$master_master\" = \"$mirror_master\"\n+\n+'\n+\n+test_expect_success 'push mirror does not remove tags' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit tag -f tremove master &&\n+\t\tgit push --mirror up &&\n+\t\tgit tag -d tremove\n+\t\tgit push --mirror up\n+\t) &&\n+\t(\n+\t\tcd mirror &&\n+\t\tinvert git show-ref -s --verify refs/tags/tremove\n+\t)\n+\n+'\n+\n+test_expect_success 'push mirror does not add, update and remove tags together' '\n+\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd master &&\n+\t\techo one >foo && git add foo && git commit -m one &&\n+\t\tgit tag -f tmaster master &&\n+\t\tgit tag -f tremove master &&\n+\t\tgit push --mirror up &&\n+\t\tgit tag -d tremove &&\n+\t\tgit tag tadd master &&\n+\t\techo two >foo && git add foo && git commit -m two &&\n+\t\tgit tag -f tmaster master &&\n+\t\tgit push --mirror up\n+\t) &&\n+\tmaster_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&\n+\tmaster_add=$(cd master && git show-ref -s --verify refs/tags/tadd) &&\n+\tmirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&\n+\tmirror_add=$(cd mirror && git show-ref -s --verify refs/tags/tadd) &&\n+\ttest \"$master_master\" = \"$mirror_master\" &&\n+\ttest \"$master_add\" = \"$mirror_add\" &&\n+\t(\n+\t\tcd mirror &&\n+\t\tinvert git show-ref -s --verify refs/tags/tremove\n+\t)\n+\n+'\n+\n+test_done\n"},{"id":"59144","messageId":"1194651145.0@pinky","threadId":"10776","inReplyTo":"20071109233041.GC301@shadowen.org","subject":"[PATCH 2/4] git-push: plumb in --mirror mode","fromName":"Andy Whitcroft","fromEmail":"apw@shadowen.org","sentAt":null,"receivedAt":"2007-11-09T23:30:41Z","isPatch":true,"sender":{"key":"apw@shadowen.org","avatar":"https://gravatar.com/avatar/d3088262854661a913ef35cc40fedcc270142d4461791142bc1ea0b2a4e2e147?d=mp&s=160"},"body":"\nPlumb in the --mirror mode for git-push.\n\nSigned-off-by: Andy Whitcroft <apw@shadowen.org>\n---\n builtin-push.c |   14 ++++++++++++--\n transport.c    |    7 +++++++\n transport.h    |    1 +\n 3 files changed, 20 insertions(+), 2 deletions(-)\ndiff --git a/builtin-push.c b/builtin-push.c\nindex 2c56195..d49157c 100644\n--- a/builtin-push.c\n+++ b/builtin-push.c\n@@ -10,7 +10,7 @@\n #include \"parse-options.h\"\n \n static const char * const push_usage[] = {\n-\t\"git-push [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]\",\n+\t\"git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]\",\n \tNULL,\n };\n \n@@ -91,6 +91,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n {\n \tint flags = 0;\n \tint all = 0;\n+\tint mirror = 0;\n \tint dry_run = 0;\n \tint force = 0;\n \tint tags = 0;\n@@ -100,6 +101,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&verbose),\n \t\tOPT_STRING( 0 , \"repo\", &repo, \"repository\", \"repository\"),\n \t\tOPT_BOOLEAN( 0 , \"all\", &all, \"push all refs\"),\n+\t\tOPT_BOOLEAN( 0 , \"mirror\", &mirror, \"mirror all refs\"),\n \t\tOPT_BOOLEAN( 0 , \"tags\", &tags, \"push tags\"),\n \t\tOPT_BOOLEAN( 0 , \"dry-run\", &dry_run, \"dry run\"),\n \t\tOPT_BOOLEAN('f', \"force\", &force, \"force updates\"),\n@@ -119,13 +121,21 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tadd_refspec(\"refs/tags/*\");\n \tif (all)\n \t\tflags |= TRANSPORT_PUSH_ALL;\n+\tif (mirror)\n+\t\tflags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);\n \n \tif (argc > 0) {\n \t\trepo = argv[0];\n \t\tset_refspecs(argv + 1, argc - 1);\n \t}\n-\tif ((flags & TRANSPORT_PUSH_ALL) && refspec)\n+\tif ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec)\n \t\tusage_with_options(push_usage, options);\n \n+\tif ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==\n+\t\t\t\t(TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {\n+\t\terror(\"--all and --mirror are incompatible\");\n+\t\tusage_with_options(push_usage, options);\n+\t}\n+\n \treturn do_push(repo, flags);\n }\ndiff --git a/transport.c b/transport.c\nindex 83677fc..fad97d7 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -284,6 +284,9 @@ static int rsync_transport_push(struct transport *transport,\n \tstruct child_process rsync;\n \tconst char *args[10];\n \n+\tif (flags & TRANSPORT_PUSH_MIRROR)\n+\t\treturn error(\"rsync transport does not support mirror mode\");\n+\n \t/* first push the objects */\n \n \tstrbuf_addstr(&buf, transport->url);\n@@ -387,6 +390,9 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons\n \tint argc;\n \tint err;\n \n+\tif (flags & TRANSPORT_PUSH_MIRROR)\n+\t\treturn error(\"http transport does not support mirror mode\");\n+\n \targv = xmalloc((refspec_nr + 11) * sizeof(char *));\n \targv[0] = \"http-push\";\n \targc = 1;\n@@ -655,6 +661,7 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const\n \n \targs.receivepack = data->receivepack;\n \targs.send_all = !!(flags & TRANSPORT_PUSH_ALL);\n+\targs.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);\n \targs.force_update = !!(flags & TRANSPORT_PUSH_FORCE);\n \targs.use_thin_pack = data->thin;\n \targs.verbose = transport->verbose;\ndiff --git a/transport.h b/transport.h\nindex d27f562..7f337d2 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -30,6 +30,7 @@ struct transport {\n #define TRANSPORT_PUSH_ALL 1\n #define TRANSPORT_PUSH_FORCE 2\n #define TRANSPORT_PUSH_DRY_RUN 4\n+#define TRANSPORT_PUSH_MIRROR 8\n \n /* Returns a transport suitable for the url */\n struct transport *transport_get(struct remote *, const char *);\n"},{"id":"59145","messageId":"1194651177.0@pinky","threadId":"10776","inReplyTo":"20071109233041.GC301@shadowen.org","subject":"[PATCH 4/4] git-push: add documentation for the newly added --mirror mode","fromName":"Andy Whitcroft","fromEmail":"apw@shadowen.org","sentAt":null,"receivedAt":"2007-11-09T23:30:41Z","isPatch":true,"sender":{"key":"apw@shadowen.org","avatar":"https://gravatar.com/avatar/d3088262854661a913ef35cc40fedcc270142d4461791142bc1ea0b2a4e2e147?d=mp&s=160"},"body":"\nAdd some basic documentation on the --mirror mode for git-push.\n\nSigned-off-by: Andy Whitcroft <apw@shadowen.org>\n---\n Documentation/git-push.txt |    8 ++++++++\n 1 files changed, 8 insertions(+), 0 deletions(-)\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex e5dd4c1..3fa5992 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -63,6 +63,14 @@ the remote repository.\n \tInstead of naming each ref to push, specifies that all\n \trefs under `$GIT_DIR/refs/heads/` be pushed.\n \n+\\--mirror::\n+\tInstead of naming each ref to push, specifies that all\n+\trefs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/`\n+\tbe mirrored to the remote repository.  Newly created local\n+\trefs will be pushed to the remote end, locally updated refs\n+\twill be force updated on the remote end, and deleted refs\n+\twill be removed from the remote end.\n+\n \\--dry-run::\n \tDo everything except actually send the updates.\n \n"},{"id":"59462","messageId":"20071112110016.GG301@shadowen.org","threadId":"10776","inReplyTo":"20071109233041.GC301@shadowen.org","subject":"Re: git push mirror mode V4 (replacement stack)","fromName":"Andy Whitcroft","fromEmail":"apw@shadowen.org","sentAt":"2007-11-12T11:00:16Z","receivedAt":"2007-11-12T11:00:16Z","isPatch":false,"sender":{"key":"apw@shadowen.org","avatar":"https://gravatar.com/avatar/d3088262854661a913ef35cc40fedcc270142d4461791142bc1ea0b2a4e2e147?d=mp&s=160"},"body":"On Fri, Nov 09, 2007 at 11:30:41PM +0000, Andy Whitcroft wrote:\n> Following this mail is a complete replacement git push mirror mode\n> stack (V4).  It folds down all the various patches into a logical\n> sequence (thanks Dscho).  This stack passes the entire test suite,\n> and I have been using the same code for real work here.\n\nOk, I have spotted one oddity with this feature.  The symbolic refs are\ngetting converted to real refs in the mirror.  Generally speaking this\nis the <remote>/HEAD refs but I guess it may be possible to have others.\n\nI have had a looked about and I am actually confused as to whether we\nmaintain remote symbolic refs at all?  Cirtainly git-clone.sh seems to\ndo some hoop jumping, comparing the sha1's of all of the fetched branches\nand replacing the HEAD reference with a symbolic reference should it find\na match.\n\nI am unsure if this a huge problem or not.  Its not preventing me using\nit as an effective mirror, but if we assume one is making the mirror as\na backup, then there would be slightly more than an rsync to convert the\nthe repo back into your original, though I guess there already is as you\nwould want to insert your config into the remote also.\n\nPerhaps someone with a better understanding could point me to where we\nwe maintain these refs, if we indeed do?\n\n-apw\n"},{"id":"59577","messageId":"7vd4uf2lld.fsf@gitster.siamese.dyndns.org","threadId":"10776","inReplyTo":"20071112110016.GG301@shadowen.org","subject":"Re: git push mirror mode V4 (replacement stack)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-12T21:35:42Z","receivedAt":"2007-11-12T21:35:42Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andy Whitcroft <apw@shadowen.org> writes:\n\n> On Fri, Nov 09, 2007 at 11:30:41PM +0000, Andy Whitcroft wrote:\n>> Following this mail is a complete replacement git push mirror mode\n>> stack (V4).  It folds down all the various patches into a logical\n>> sequence (thanks Dscho).  This stack passes the entire test suite,\n>> and I have been using the same code for real work here.\n>\n> Ok, I have spotted one oddity with this feature.  The symbolic refs are\n> getting converted to real refs in the mirror.  Generally speaking this\n> is the <remote>/HEAD refs but I guess it may be possible to have others.\n\nCurrently there is no way to remotely create a symref.\n\nIt should be easy to make DAV based http push create a symref\nfile.\n\nThe native \"send-pack\" protocol does not support such feature,\nso this needs a protocol extension, which hopefully would be\nnot too involved.\n\nAlso, to be symmetric, we would need a protocol extension on the\n\"fetch-pack\" protocol side to allow fetchers to find out which\none is a symref pointing out what other ref.  The lack of the\ninformation is why git-clone has to guess.\n"}]}