Merge branch 'aw/mirror-push' into jk/send-pack
authorJunio C Hamano <gitster@pobox.com>
Wed, 14 Nov 2007 11:13:30 +0000 (03:13 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 14 Nov 2007 11:13:30 +0000 (03:13 -0800)
* aw/mirror-push:
  git-push: add documentation for the newly added --mirror mode
  Add tests for git push'es mirror mode
  git-push: plumb in --mirror mode
  Teach send-pack a mirror mode
  send-pack: segfault fix on forced push
  send-pack: require --verbose to show update of tracking refs
  receive-pack: don't mention successful updates
  more terse push output

Conflicts:

transport.c
transport.h

12 files changed:
Documentation/git-push.txt
builtin-push.c
builtin-send-pack.c
http-push.c
receive-pack.c
remote.c
remote.h
send-pack.h
t/t5405-send-pack-rewind.sh [new file with mode: 0755]
t/t5517-push-mirror.sh [new file with mode: 0755]
transport.c
transport.h

index 4a68aab..b8003c6 100644 (file)
@@ -63,6 +63,14 @@ the remote repository.
        Instead of naming each ref to push, specifies that all
        refs under `$GIT_DIR/refs/heads/` be pushed.
 
+\--mirror::
+       Instead of naming each ref to push, specifies that all
+       refs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/`
+       be mirrored to the remote repository.  Newly created local
+       refs will be pushed to the remote end, locally updated refs
+       will be force updated on the remote end, and deleted refs
+       will be removed from the remote end.
+
 \--dry-run::
        Do everything except actually send the updates.
 
index 6d1da07..41df717 100644 (file)
@@ -10,7 +10,7 @@
 #include "parse-options.h"
 
 static const char * const push_usage[] = {
-       "git-push [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]",
+       "git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]",
        NULL,
 };
 
@@ -91,6 +91,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 {
        int flags = 0;
        int all = 0;
+       int mirror = 0;
        int dry_run = 0;
        int force = 0;
        int tags = 0;
@@ -100,6 +101,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT__VERBOSE(&verbose),
                OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
                OPT_BOOLEAN( 0 , "all", &all, "push all refs"),
+               OPT_BOOLEAN( 0 , "mirror", &mirror, "mirror all refs"),
                OPT_BOOLEAN( 0 , "tags", &tags, "push tags"),
                OPT_BOOLEAN( 0 , "dry-run", &dry_run, "dry run"),
                OPT_BOOLEAN('f', "force", &force, "force updates"),
@@ -121,13 +123,21 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                add_refspec("refs/tags/*");
        if (all)
                flags |= TRANSPORT_PUSH_ALL;
+       if (mirror)
+               flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
 
        if (argc > 0) {
                repo = argv[0];
                set_refspecs(argv + 1, argc - 1);
        }
-       if ((flags & TRANSPORT_PUSH_ALL) && refspec)
+       if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec)
                usage_with_options(push_usage, options);
 
+       if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+                               (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+               error("--all and --mirror are incompatible");
+               usage_with_options(push_usage, options);
+       }
+
        return do_push(repo, flags);
 }
index 22cb50b..418925e 100644 (file)
@@ -8,7 +8,7 @@
 #include "send-pack.h"
 
 static const char send_pack_usage[] =
-"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
 "  --all and explicit <ref> specification are mutually exclusive.";
 
 static struct send_pack_args args = {
@@ -195,7 +195,8 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref)
                return;
 
        if (!remote_find_tracking(remote, &rs)) {
-               fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
+               if (args.verbose)
+                       fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
                if (is_null_sha1(ref->peer_ref->new_sha1)) {
                        if (delete_ref(rs.dst, NULL))
                                error("Failed to delete");
@@ -206,7 +207,18 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref)
        }
 }
 
-static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, const char **refspec)
+static const char *prettify_ref(const char *name)
+{
+       return name + (
+               !prefixcmp(name, "refs/heads/") ? 11 :
+               !prefixcmp(name, "refs/tags/") ? 10 :
+               !prefixcmp(name, "refs/remotes/") ? 13 :
+               0);
+}
+
+#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+
+static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec)
 {
        struct ref *ref;
        int new_refs;
@@ -214,6 +226,13 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec,
        int ask_for_status_report = 0;
        int allow_deleting_refs = 0;
        int expect_status_report = 0;
+       int shown_dest = 0;
+       int flags = MATCH_REFS_NONE;
+
+       if (args.send_all)
+               flags |= MATCH_REFS_ALL;
+       if (args.send_mirror)
+               flags |= MATCH_REFS_MIRROR;
 
        /* No funny business with the matcher */
        remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
@@ -229,7 +248,7 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec,
        if (!remote_tail)
                remote_tail = &remote_refs;
        if (match_refs(local_refs, remote_refs, &remote_tail,
-                      nr_refspec, refspec, args.send_all))
+                                              nr_refspec, refspec, flags))
                return -1;
 
        if (!remote_refs) {
@@ -245,21 +264,41 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec,
        for (ref = remote_refs; ref; ref = ref->next) {
                char old_hex[60], *new_hex;
                int will_delete_ref;
+               const char *pretty_ref;
+               const char *pretty_peer = NULL; /* only used when not deleting */
+               const unsigned char *new_sha1;
 
-               if (!ref->peer_ref)
-                       continue;
+               if (!ref->peer_ref) {
+                       if (!args.send_mirror)
+                               continue;
+                       new_sha1 = null_sha1;
+               }
+               else
+                       new_sha1 = ref->peer_ref->new_sha1;
+
+               if (!shown_dest) {
+                       fprintf(stderr, "To %s\n", dest);
+                       shown_dest = 1;
+               }
 
+               will_delete_ref = is_null_sha1(new_sha1);
+
+               pretty_ref = prettify_ref(ref->name);
+               if (!will_delete_ref)
+                       pretty_peer = prettify_ref(ref->peer_ref->name);
 
-               will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
                if (will_delete_ref && !allow_deleting_refs) {
-                       error("remote does not support deleting refs");
+                       fprintf(stderr, " ! %-*s %s (remote does not support deleting refs)\n",
+                                       SUMMARY_WIDTH, "[rejected]", pretty_ref);
                        ret = -2;
                        continue;
                }
                if (!will_delete_ref &&
-                   !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
+                   !hashcmp(ref->old_sha1, new_sha1)) {
                        if (args.verbose)
-                               fprintf(stderr, "'%s': up-to-date\n", ref->name);
+                               fprintf(stderr, " = %-*s %s -> %s\n",
+                                       SUMMARY_WIDTH, "[up to date]",
+                                       pretty_peer, pretty_ref);
                        continue;
                }
 
@@ -287,8 +326,7 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec,
                    !is_null_sha1(ref->old_sha1) &&
                    !ref->force) {
                        if (!has_sha1_file(ref->old_sha1) ||
-                           !ref_newer(ref->peer_ref->new_sha1,
-                                      ref->old_sha1)) {
+                           !ref_newer(new_sha1, ref->old_sha1)) {
                                /* We do not have the remote ref, or
                                 * we know that the remote ref is not
                                 * an ancestor of what we are trying to
@@ -296,17 +334,14 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec,
                                 * commits at the remote end and likely
                                 * we were not up to date to begin with.
                                 */
-                               error("remote '%s' is not an ancestor of\n"
-                                     " local  '%s'.\n"
-                                     " Maybe you are not up-to-date and "
-                                     "need to pull first?",
-                                     ref->name,
-                                     ref->peer_ref->name);
+                               fprintf(stderr, " ! %-*s %s -> %s (non-fast forward)\n",
+                                               SUMMARY_WIDTH, "[rejected]",
+                                               pretty_peer, pretty_ref);
                                ret = -2;
                                continue;
                        }
                }
-               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+               hashcpy(ref->new_sha1, new_sha1);
                if (!will_delete_ref)
                        new_refs++;
                strcpy(old_hex, sha1_to_hex(ref->old_sha1));
@@ -325,14 +360,41 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec,
                                        old_hex, new_hex, ref->name);
                }
                if (will_delete_ref)
-                       fprintf(stderr, "deleting '%s'\n", ref->name);
+                       fprintf(stderr, " - %-*s %s\n",
+                               SUMMARY_WIDTH, "[deleting]",
+                               pretty_ref);
+               else if (is_null_sha1(ref->old_sha1)) {
+                       const char *msg;
+
+                       if (!prefixcmp(ref->name, "refs/tags/"))
+                               msg = "[new tag]";
+                       else
+                               msg = "[new branch]";
+                       fprintf(stderr, " * %-*s %s -> %s\n",
+                               SUMMARY_WIDTH, msg,
+                               pretty_peer, pretty_ref);
+               }
                else {
-                       fprintf(stderr, "updating '%s'", ref->name);
-                       if (strcmp(ref->name, ref->peer_ref->name))
-                               fprintf(stderr, " using '%s'",
-                                       ref->peer_ref->name);
-                       fprintf(stderr, "\n  from %s\n  to   %s\n",
-                               old_hex, new_hex);
+                       char quickref[83];
+                       char type = ' ';
+                       const char *msg = "";
+                       const char *old_abb;
+                       old_abb = find_unique_abbrev(ref->old_sha1, DEFAULT_ABBREV);
+                       strcpy(quickref, old_abb ? old_abb : old_hex);
+                       if (ref_newer(ref->peer_ref->new_sha1, ref->old_sha1))
+                               strcat(quickref, "..");
+                       else {
+                               strcat(quickref, "...");
+                               type = '+';
+                               msg = " (forced update)";
+                       }
+                       strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+
+                       fprintf(stderr, " %c %-*s %s -> %s%s\n",
+                               type,
+                               SUMMARY_WIDTH, quickref,
+                               pretty_peer, pretty_ref,
+                               msg);
                }
        }
 
@@ -411,6 +473,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.dry_run = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--mirror")) {
+                               args.send_mirror = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--force")) {
                                args.force_update = 1;
                                continue;
@@ -435,7 +501,12 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        }
        if (!dest)
                usage(send_pack_usage);
-       if (heads && args.send_all)
+       /*
+        * --all and --mirror are incompatible; neither makes sense
+        * with any refspecs.
+        */
+       if ((heads && (args.send_all || args.send_mirror)) ||
+                                       (args.send_all && args.send_mirror))
                usage(send_pack_usage);
 
        if (remote_name) {
@@ -461,7 +532,7 @@ int send_pack(struct send_pack_args *my_args,
        verify_remote_names(nr_heads, heads);
 
        conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
-       ret = do_send_pack(fd[0], fd[1], remote, nr_heads, heads);
+       ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
        close(fd[0]);
        close(fd[1]);
        ret |= finish_connect(conn);
index 99328f5..66b81f1 100644 (file)
@@ -78,7 +78,7 @@ static struct curl_slist *no_pragma_header;
 static struct curl_slist *default_headers;
 
 static int push_verbosely;
-static int push_all;
+static int push_all = MATCH_REFS_NONE;
 static int force_all;
 static int dry_run;
 
@@ -2300,7 +2300,7 @@ int main(int argc, char **argv)
 
                if (*arg == '-') {
                        if (!strcmp(arg, "--all")) {
-                               push_all = 1;
+                               push_all = MATCH_REFS_ALL;
                                continue;
                        }
                        if (!strcmp(arg, "--force")) {
index 38e35c0..ed44b89 100644 (file)
@@ -204,8 +204,6 @@ static const char *update(struct command *cmd)
                        error("failed to delete %s", name);
                        return "failed to delete";
                }
-               fprintf(stderr, "%s: %s -> deleted\n", name,
-                       sha1_to_hex(old_sha1));
                return NULL; /* good */
        }
        else {
@@ -217,8 +215,6 @@ static const char *update(struct command *cmd)
                if (write_ref_sha1(lock, new_sha1, "push")) {
                        return "failed to write"; /* error() already called */
                }
-               fprintf(stderr, "%s: %s -> %s\n", name,
-                       sha1_to_hex(old_sha1), sha1_to_hex(new_sha1));
                return NULL; /* good */
        }
 }
index 59defdb..09b7aad 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -722,10 +722,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
  * without thinking.
  */
 int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
-              int nr_refspec, const char **refspec, int all)
+              int nr_refspec, const char **refspec, int flags)
 {
        struct refspec *rs =
                parse_ref_spec(nr_refspec, (const char **) refspec);
+       int send_all = flags & MATCH_REFS_ALL;
+       int send_mirror = flags & MATCH_REFS_MIRROR;
 
        if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
                return -1;
@@ -742,7 +744,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                        if (!pat)
                                continue;
                }
-               else if (prefixcmp(src->name, "refs/heads/"))
+               else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
                        /*
                         * "matching refs"; traditionally we pushed everything
                         * including refs outside refs/heads/ hierarchy, but
@@ -763,10 +765,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                if (dst_peer && dst_peer->peer_ref)
                        /* We're already sending something to this ref. */
                        goto free_name;
-               if (!dst_peer && !nr_refspec && !all)
-                       /* Remote doesn't have it, and we have no
+
+               if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
+                       /*
+                        * Remote doesn't have it, and we have no
                         * explicit pattern, and we don't have
-                        * --all. */
+                        * --all nor --mirror.
+                        */
                        goto free_name;
                if (!dst_peer) {
                        /* Create a new one and link it */
index 6a4c7a0..b10036c 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -102,4 +102,11 @@ struct branch *branch_get(const char *name);
 int branch_has_merge_config(struct branch *branch);
 int branch_merge_matches(struct branch *, int n, const char *);
 
+/* Flags to match_refs. */
+enum match_refs_flags {
+       MATCH_REFS_NONE         = 0,
+       MATCH_REFS_ALL          = (1 << 0),
+       MATCH_REFS_MIRROR       = (1 << 1),
+};
+
 #endif
index 7a24f71..8ff1dc3 100644 (file)
@@ -5,6 +5,7 @@ struct send_pack_args {
        const char *receivepack;
        unsigned verbose:1,
                send_all:1,
+               send_mirror:1,
                force_update:1,
                use_thin_pack:1,
                dry_run:1;
diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh
new file mode 100755 (executable)
index 0000000..86abc62
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='forced push to replace commit we do not have'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       >file1 && git add file1 && test_tick &&
+       git commit -m Initial &&
+
+       mkdir another && (
+               cd another &&
+               git init &&
+               git fetch .. master:master
+       ) &&
+
+       >file2 && git add file2 && test_tick &&
+       git commit -m Second
+
+'
+
+test_expect_success 'non forced push should die not segfault' '
+
+       (
+               cd another &&
+               git push .. master:master
+               test $? = 1
+       )
+
+'
+
+test_expect_success 'forced push should succeed' '
+
+       (
+               cd another &&
+               git push .. +master:master
+       )
+
+'
+
+test_done
diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh
new file mode 100755 (executable)
index 0000000..ed3fec1
--- /dev/null
@@ -0,0 +1,228 @@
+#!/bin/sh
+
+test_description='pushing to a mirror repository'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+invert () {
+       if "$@"; then
+               return 1
+       else
+               return 0
+       fi
+}
+
+mk_repo_pair () {
+       rm -rf master mirror &&
+       mkdir mirror &&
+       (
+               cd mirror &&
+               git init
+       ) &&
+       mkdir master &&
+       (
+               cd master &&
+               git init &&
+               git config remote.up.url ../mirror
+       )
+}
+
+
+# BRANCH tests
+test_expect_success 'push mirror creates new branches' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+       test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror updates existing branches' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git push --mirror up &&
+               echo two >foo && git add foo && git commit -m two &&
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+       test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror force updates existing branches' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git push --mirror up &&
+               echo two >foo && git add foo && git commit -m two &&
+               git push --mirror up &&
+               git reset --hard HEAD^
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+       test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror removes branches' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git branch remove master &&
+               git push --mirror up &&
+               git branch -D remove
+               git push --mirror up
+       ) &&
+       (
+               cd mirror &&
+               invert git show-ref -s --verify refs/heads/remove
+       )
+
+'
+
+test_expect_success 'push mirror adds, updates and removes branches together' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git branch remove master &&
+               git push --mirror up &&
+               git branch -D remove &&
+               git branch add master &&
+               echo two >foo && git add foo && git commit -m two &&
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+       master_add=$(cd master && git show-ref -s --verify refs/heads/add) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+       mirror_add=$(cd mirror && git show-ref -s --verify refs/heads/add) &&
+       test "$master_master" = "$mirror_master" &&
+       test "$master_add" = "$mirror_add" &&
+       (
+               cd mirror &&
+               invert git show-ref -s --verify refs/heads/remove
+       )
+
+'
+
+
+# TAG tests
+test_expect_success 'push mirror creates new tags' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git tag -f tmaster master &&
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+       test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror updates existing tags' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git tag -f tmaster master &&
+               git push --mirror up &&
+               echo two >foo && git add foo && git commit -m two &&
+               git tag -f tmaster master &&
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+       test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror force updates existing tags' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git tag -f tmaster master &&
+               git push --mirror up &&
+               echo two >foo && git add foo && git commit -m two &&
+               git tag -f tmaster master &&
+               git push --mirror up &&
+               git reset --hard HEAD^
+               git tag -f tmaster master &&
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+       test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror removes tags' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git tag -f tremove master &&
+               git push --mirror up &&
+               git tag -d tremove
+               git push --mirror up
+       ) &&
+       (
+               cd mirror &&
+               invert git show-ref -s --verify refs/tags/tremove
+       )
+
+'
+
+test_expect_success 'push mirror adds, updates and removes tags together' '
+
+       mk_repo_pair &&
+       (
+               cd master &&
+               echo one >foo && git add foo && git commit -m one &&
+               git tag -f tmaster master &&
+               git tag -f tremove master &&
+               git push --mirror up &&
+               git tag -d tremove &&
+               git tag tadd master &&
+               echo two >foo && git add foo && git commit -m two &&
+               git tag -f tmaster master &&
+               git push --mirror up
+       ) &&
+       master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+       master_add=$(cd master && git show-ref -s --verify refs/tags/tadd) &&
+       mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+       mirror_add=$(cd mirror && git show-ref -s --verify refs/tags/tadd) &&
+       test "$master_master" = "$mirror_master" &&
+       test "$master_add" = "$mirror_add" &&
+       (
+               cd mirror &&
+               invert git show-ref -s --verify refs/tags/tremove
+       )
+
+'
+
+test_done
index 5cb809b..8d9bdbe 100644 (file)
@@ -284,6 +284,9 @@ static int rsync_transport_push(struct transport *transport,
        struct child_process rsync;
        const char *args[10];
 
+       if (flags & TRANSPORT_PUSH_MIRROR)
+               return error("rsync transport does not support mirror mode");
+
        /* first push the objects */
 
        strbuf_addstr(&buf, transport->url);
@@ -387,6 +390,9 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons
        int argc;
        int err;
 
+       if (flags & TRANSPORT_PUSH_MIRROR)
+               return error("http transport does not support mirror mode");
+
        argv = xmalloc((refspec_nr + 12) * sizeof(char *));
        argv[0] = "http-push";
        argc = 1;
@@ -657,6 +663,7 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const
 
        args.receivepack = data->receivepack;
        args.send_all = !!(flags & TRANSPORT_PUSH_ALL);
+       args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
        args.use_thin_pack = data->thin;
        args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
index a2a36d0..6fb4526 100644 (file)
@@ -30,7 +30,8 @@ struct transport {
 #define TRANSPORT_PUSH_ALL 1
 #define TRANSPORT_PUSH_FORCE 2
 #define TRANSPORT_PUSH_DRY_RUN 4
-#define TRANSPORT_PUSH_VERBOSE 8
+#define TRANSPORT_PUSH_MIRROR 8
+#define TRANSPORT_PUSH_VERBOSE 16
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);