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

1  2 
Documentation/git-push.txt
builtin-push.c
builtin-send-pack.c
transport.c
transport.h

@@@ -10,7 -10,7 +10,7 @@@ SYNOPSI
  --------
  [verse]
  'git-push' [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>]
 -           [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]
 +           [--repo=all] [-f | --force] [-v | --verbose] [<repository> <refspec>...]
  
  DESCRIPTION
  -----------
@@@ -63,6 -63,14 +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.
  
        transfer spends extra cycles to minimize the number of
        objects to be sent and meant to be used on slower connection.
  
 --v::
 +-v, \--verbose::
        Run verbosely.
  
  include::urls-remotes.txt[]
diff --combined builtin-push.c
@@@ -10,7 -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 +91,7 @@@ int cmd_push(int argc, const char **arg
  {
        int flags = 0;
        int all = 0;
+       int mirror = 0;
        int dry_run = 0;
        int force = 0;
        int tags = 0;
                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"),
                flags |= TRANSPORT_PUSH_FORCE;
        if (dry_run)
                flags |= TRANSPORT_PUSH_DRY_RUN;
 +      if (verbose)
 +              flags |= TRANSPORT_PUSH_VERBOSE;
        if (tags)
                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);
  }
diff --combined builtin-send-pack.c
@@@ -8,7 -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 +195,8 @@@ static void update_tracking_ref(struct 
                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");
        }
  }
  
- 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;
        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);
        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) {
        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;
                }
  
                    !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
                                 * 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));
                                        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);
                }
        }
  
  
        if (!args.dry_run && remote && ret == 0) {
                for (ref = remote_refs; ref; ref = ref->next)
 -                      update_tracking_ref(remote, ref);
 +                      if (!is_null_sha1(ref->new_sha1))
 +                              update_tracking_ref(remote, ref);
        }
  
        if (!new_refs && ret == 0)
@@@ -411,6 -472,10 +473,10 @@@ int cmd_send_pack(int argc, const char 
                                args.dry_run = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--mirror")) {
+                               args.send_mirror = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--force")) {
                                args.force_update = 1;
                                continue;
        }
        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 -531,7 +532,7 @@@ int send_pack(struct send_pack_args *my
        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);
diff --combined transport.c
@@@ -284,6 -284,9 +284,9 @@@ static int rsync_transport_push(struct 
        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,7 -390,10 +390,10 @@@ static int curl_transport_push(struct t
        int argc;
        int err;
  
 -      argv = xmalloc((refspec_nr + 11) * sizeof(char *));
+       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;
        if (flags & TRANSPORT_PUSH_ALL)
                argv[argc++] = "--force";
        if (flags & TRANSPORT_PUSH_DRY_RUN)
                argv[argc++] = "--dry-run";
 +      if (flags & TRANSPORT_PUSH_VERBOSE)
 +              argv[argc++] = "--verbose";
        argv[argc++] = transport->url;
        while (refspec_nr--)
                argv[argc++] = *refspec++;
@@@ -657,9 -661,10 +663,10 @@@ static int git_transport_push(struct tr
  
        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 = transport->verbose;
 +      args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
  
        return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec);
diff --combined transport.h
@@@ -30,7 -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 *);