Merge branch 'jc/push-follow-tag'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Mar 2013 21:00:40 +0000 (14:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Mar 2013 21:00:41 +0000 (14:00 -0700)
The new "--follow-tags" option tells "git push" to push relevant
annotated tags when pushing branches out.

* jc/push-follow-tag:
push: --follow-tags
commit.c: use clear_commit_marks_many() in in_merge_bases_many()
commit.c: add in_merge_bases_many()
commit.c: add clear_commit_marks_many()

1  2 
Documentation/git-push.txt
builtin/push.c
commit.h
remote.c
remote.h
t/t5516-fetch-push.sh
transport.c
transport.h
index 577d201c006ab6c72acef9bd800aa36f6cc356d3,40377ba4589bd4a6a91088210b368372df97e988..eb2883c94cfad91efcc1f70bbdbc1609a3702d3b
@@@ -9,7 -9,7 +9,7 @@@ git-push - Update remote refs along wit
  SYNOPSIS
  --------
  [verse]
- 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
+ 'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
           [<repository> [<refspec>...]]
  
@@@ -23,17 -23,6 +23,17 @@@ You can make interesting things happen 
  every time you push into it, by setting up 'hooks' there.  See
  documentation for linkgit:git-receive-pack[1].
  
 +When the command line does not specify where to push with the
 +`<repository>` argument, `branch.*.remote` configuration for the
 +current branch is consulted to determine where to push.  If the
 +configuration is missing, it defaults to 'origin'.
 +
 +When the command line does not specify what to push with `<refspec>...`
 +arguments or `--all`, `--mirror`, `--tags` options, the command finds
 +the default `<refspec>` by consulting `remote.*.push` configuration,
 +and if it is not found, honors `push.default` configuration to decide
 +what to push (See gitlink:git-config[1] for the meaning of `push.default`).
 +
  
  OPTIONS[[OPTIONS]]
  ------------------
        of a remote (see the section <<REMOTES,REMOTES>> below).
  
  <refspec>...::
 +      Specify what destination ref to update with what source object.
        The format of a <refspec> parameter is an optional plus
 -      `+`, followed by the source ref <src>, followed
 +      `+`, followed by the source object <src>, followed
        by a colon `:`, followed by the destination ref <dst>.
 -      It is used to specify with what <src> object the <dst> ref
 -      in the remote repository is to be updated.  If not specified,
 -      the behavior of the command is controlled by the `push.default`
 -      configuration variable.
  +
  The <src> is often the name of the branch you would want to push, but
  it can be any arbitrary "SHA-1 expression", such as `master~4` or
@@@ -59,11 -51,10 +59,11 @@@ be named. If `:`<dst> is omitted, the s
  updated.
  +
  The object referenced by <src> is used to update the <dst> reference
 -on the remote side, but by default this is only allowed if the
 -update can fast-forward <dst>.  By having the optional leading `+`,
 -you can tell git to update the <dst> ref even when the update is not a
 -fast-forward.  This does *not* attempt to merge <src> into <dst>.  See
 +on the remote side.  By default this is only allowed if <dst> is not
 +a tag (annotated or lightweight), and then only if it can fast-forward
 +<dst>.  By having the optional leading `+`, you can tell Git to update
 +the <dst> ref even if it is not allowed by default (e.g., it is not a
 +fast-forward.)  This does *not* attempt to merge <src> into <dst>.  See
  EXAMPLES below for details.
  +
  `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
@@@ -72,9 -63,12 +72,9 @@@ Pushing an empty <src> allows you to de
  the remote repository.
  +
  The special refspec `:` (or `+:` to allow non-fast-forward updates)
 -directs git to push "matching" branches: for every branch that exists on
 +directs Git to push "matching" branches: for every branch that exists on
  the local side, the remote side is updated if a branch of the same name
 -already exists on the remote side.  This is the default operation mode
 -if no explicit refspec is found (that is neither on the command line
 -nor in any Push line of the corresponding remotes file---see below) and
 -no `push.default` configuration variable is set.
 +already exists on the remote side.
  
  --all::
        Instead of naming each ref to push, specifies that all
        addition to refspecs explicitly listed on the command
        line.
  
+ --follow-tags::
+       Push all the refs that would be pushed without this option,
+       and also push annotated tags in `refs/tags` that are missing
+       from the remote but are pointing at committish that are
+       reachable from the refs being pushed.
  --receive-pack=<git-receive-pack>::
  --exec=<git-receive-pack>::
        Path to the 'git-receive-pack' program on the remote
@@@ -182,7 -182,7 +188,7 @@@ useful if you write an alias or script 
  --recurse-submodules=check|on-demand::
        Make sure all submodule commits used by the revisions to be
        pushed are available on a remote-tracking branch. If 'check' is
 -      used git will verify that all submodule commits that changed in
 +      used Git will verify that all submodule commits that changed in
        the revisions to be pushed are available on at least one remote
        of the submodule. If any commits are missing the push will be
        aborted and exit with non-zero status. If 'on-demand' is used
@@@ -197,7 -197,7 +203,7 @@@ OUTPU
  ------
  
  The output of "git push" depends on the transport method used; this
 -section describes the output when pushing over the git protocol (either
 +section describes the output when pushing over the Git protocol (either
  locally or via ssh).
  
  The status of the push is output in tabular form, with each line
diff --combined builtin/push.c
index 42b129d36cf615ed264be0f1bff523a7fc327b12,34a82711d44102db96d451b924ae246f587c3001..5e4a0e958f3649c5a5a9f317876e8afd6f6bea6f
@@@ -220,67 -220,31 +220,67 @@@ static const char message_advice_checko
           "(e.g. 'git pull') before pushing again.\n"
           "See the 'Note about fast-forwards' in 'git push --help' for details.");
  
 +static const char message_advice_ref_fetch_first[] =
 +      N_("Updates were rejected because the remote contains work that you do\n"
 +         "not have locally. This is usually caused by another repository pushing\n"
 +         "to the same ref. You may want to first merge the remote changes (e.g.,\n"
 +         "'git pull') before pushing again.\n"
 +         "See the 'Note about fast-forwards' in 'git push --help' for details.");
 +
 +static const char message_advice_ref_already_exists[] =
 +      N_("Updates were rejected because the tag already exists in the remote.");
 +
 +static const char message_advice_ref_needs_force[] =
 +      N_("You cannot update a remote ref that points at a non-commit object,\n"
 +         "or update a remote ref to make it point at a non-commit object,\n"
 +         "without using the '--force' option.\n");
 +
  static void advise_pull_before_push(void)
  {
 -      if (!advice_push_non_ff_current || !advice_push_nonfastforward)
 +      if (!advice_push_non_ff_current || !advice_push_update_rejected)
                return;
        advise(_(message_advice_pull_before_push));
  }
  
  static void advise_use_upstream(void)
  {
 -      if (!advice_push_non_ff_default || !advice_push_nonfastforward)
 +      if (!advice_push_non_ff_default || !advice_push_update_rejected)
                return;
        advise(_(message_advice_use_upstream));
  }
  
  static void advise_checkout_pull_push(void)
  {
 -      if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
 +      if (!advice_push_non_ff_matching || !advice_push_update_rejected)
                return;
        advise(_(message_advice_checkout_pull_push));
  }
  
 +static void advise_ref_already_exists(void)
 +{
 +      if (!advice_push_already_exists || !advice_push_update_rejected)
 +              return;
 +      advise(_(message_advice_ref_already_exists));
 +}
 +
 +static void advise_ref_fetch_first(void)
 +{
 +      if (!advice_push_fetch_first || !advice_push_update_rejected)
 +              return;
 +      advise(_(message_advice_ref_fetch_first));
 +}
 +
 +static void advise_ref_needs_force(void)
 +{
 +      if (!advice_push_needs_force || !advice_push_update_rejected)
 +              return;
 +      advise(_(message_advice_ref_needs_force));
 +}
 +
  static int push_with_options(struct transport *transport, int flags)
  {
        int err;
 -      int nonfastforward;
 +      unsigned int reject_reasons;
  
        transport_set_verbosity(transport, verbosity, progress);
  
        if (verbosity > 0)
                fprintf(stderr, _("Pushing to %s\n"), transport->url);
        err = transport_push(transport, refspec_nr, refspec, flags,
 -                           &nonfastforward);
 +                           &reject_reasons);
        if (err != 0)
                error(_("failed to push some refs to '%s'"), transport->url);
  
        if (!err)
                return 0;
  
 -      switch (nonfastforward) {
 -      default:
 -              break;
 -      case NON_FF_HEAD:
 +      if (reject_reasons & REJECT_NON_FF_HEAD) {
                advise_pull_before_push();
 -              break;
 -      case NON_FF_OTHER:
 +      } else if (reject_reasons & REJECT_NON_FF_OTHER) {
                if (default_matching_used)
                        advise_use_upstream();
                else
                        advise_checkout_pull_push();
 -              break;
 +      } else if (reject_reasons & REJECT_ALREADY_EXISTS) {
 +              advise_ref_already_exists();
 +      } else if (reject_reasons & REJECT_FETCH_FIRST) {
 +              advise_ref_fetch_first();
 +      } else if (reject_reasons & REJECT_NEEDS_FORCE) {
 +              advise_ref_needs_force();
        }
  
        return 1;
@@@ -436,7 -399,8 +436,9 @@@ int cmd_push(int argc, const char **arg
                OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
                OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
                        TRANSPORT_PUSH_PRUNE),
 +              OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
+               OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
+                       TRANSPORT_PUSH_FOLLOW_TAGS),
                OPT_END()
        };
  
diff --combined commit.h
index 4138bb4c0850f95ba7215cb62f457cd682495968,5057f141f047aa3ca448e2a329f12d2bd3f24019..2d90d9c27c8fb947e30a368cb5fb266f643b8bae
+++ b/commit.h
@@@ -89,8 -89,6 +89,8 @@@ struct pretty_print_context 
        char *notes_message;
        struct reflog_walk_info *reflog_info;
        const char *output_encoding;
 +      struct string_list *mailmap;
 +      int color;
  };
  
  struct userformat_want {
@@@ -101,7 -99,6 +101,7 @@@ extern int has_non_ascii(const char *te
  struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
  extern char *logmsg_reencode(const struct commit *commit,
                             const char *output_encoding);
 +extern void logmsg_free(char *msg, const struct commit *commit);
  extern void get_commit_format(const char *arg, struct rev_info *);
  extern const char *format_subject(struct strbuf *sb, const char *msg,
                                  const char *line_separator);
@@@ -137,6 -134,7 +137,7 @@@ struct commit *pop_most_recent_commit(s
  struct commit *pop_commit(struct commit_list **stack);
  
  void clear_commit_marks(struct commit *commit, unsigned int mark);
+ void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
  void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
  
  /*
@@@ -164,9 -162,6 +165,9 @@@ extern struct commit_list *get_merge_ba
  extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);
  extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
  
 +/* largest postive number a signed 32-bit integer can contain */
 +#define INFINITE_DEPTH 0x7fffffff
 +
  extern int register_shallow(const unsigned char *sha1);
  extern int unregister_shallow(const unsigned char *sha1);
  extern int for_each_commit_graft(each_commit_graft_fn, void *);
@@@ -176,6 -171,7 +177,7 @@@ extern struct commit_list *get_shallow_
  
  int is_descendant_of(struct commit *, struct commit_list *);
  int in_merge_bases(struct commit *, struct commit *);
+ int in_merge_bases_many(struct commit *, int, struct commit **);
  
  extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
  extern int run_add_interactive(const char *revision, const char *patch_mode,
diff --combined remote.c
index 174e48eb7414a0bce0fb1720967e2343985d87ea,f035af30747822077442d2af158dc872ddb5c660..57f36e14da7a8a33ae8c02a4bf46492936c79fdd
+++ b/remote.c
@@@ -15,7 -15,6 +15,7 @@@ static struct refspec s_tag_refspec = 
        0,
        1,
        0,
 +      0,
        "refs/tags/*",
        "refs/tags/*"
  };
@@@ -539,7 -538,7 +539,7 @@@ static struct refspec *parse_refspec_in
  
                /*
                 * Before going on, special case ":" (or "+:") as a refspec
 -               * for matching refs.
 +               * for pushing matching refs.
                 */
                if (!fetch && rhs == lhs && rhs[1] == '\0') {
                        rs[i].matching = 1;
                flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
  
                if (fetch) {
 -                      /*
 -                       * LHS
 -                       * - empty is allowed; it means HEAD.
 -                       * - otherwise it must be a valid looking ref.
 -                       */
 +                      unsigned char unused[40];
 +
 +                      /* LHS */
                        if (!*rs[i].src)
 -                              ; /* empty is ok */
 -                      else if (check_refname_format(rs[i].src, flags))
 +                              ; /* empty is ok; it means "HEAD" */
 +                      else if (llen == 40 && !get_sha1_hex(rs[i].src, unused))
 +                              rs[i].exact_sha1 = 1; /* ok */
 +                      else if (!check_refname_format(rs[i].src, flags))
 +                              ; /* valid looking ref is ok */
 +                      else
                                goto invalid;
 -                      /*
 -                       * RHS
 -                       * - missing is ok, and is same as empty.
 -                       * - empty is ok; it means not to store.
 -                       * - otherwise it must be a valid looking ref.
 -                       */
 +                      /* RHS */
                        if (!rs[i].dst)
 -                              ; /* ok */
 +                              ; /* missing is ok; it is the same as empty */
                        else if (!*rs[i].dst)
 -                              ; /* ok */
 -                      else if (check_refname_format(rs[i].dst, flags))
 +                              ; /* empty is ok; it means "do not store" */
 +                      else if (!check_refname_format(rs[i].dst, flags))
 +                              ; /* valid looking ref is ok */
 +                      else
                                goto invalid;
                } else {
                        /*
@@@ -1195,6 -1195,101 +1195,101 @@@ static struct ref **tail_ref(struct re
        return tail;
  }
  
+ struct tips {
+       struct commit **tip;
+       int nr, alloc;
+ };
+ static void add_to_tips(struct tips *tips, const unsigned char *sha1)
+ {
+       struct commit *commit;
+       if (is_null_sha1(sha1))
+               return;
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit || (commit->object.flags & TMP_MARK))
+               return;
+       commit->object.flags |= TMP_MARK;
+       ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc);
+       tips->tip[tips->nr++] = commit;
+ }
+ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
+ {
+       struct string_list dst_tag = STRING_LIST_INIT_NODUP;
+       struct string_list src_tag = STRING_LIST_INIT_NODUP;
+       struct string_list_item *item;
+       struct ref *ref;
+       struct tips sent_tips;
+       /*
+        * Collect everything we know they would have at the end of
+        * this push, and collect all tags they have.
+        */
+       memset(&sent_tips, 0, sizeof(sent_tips));
+       for (ref = *dst; ref; ref = ref->next) {
+               if (ref->peer_ref &&
+                   !is_null_sha1(ref->peer_ref->new_sha1))
+                       add_to_tips(&sent_tips, ref->peer_ref->new_sha1);
+               else
+                       add_to_tips(&sent_tips, ref->old_sha1);
+               if (!prefixcmp(ref->name, "refs/tags/"))
+                       string_list_append(&dst_tag, ref->name);
+       }
+       clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
+       sort_string_list(&dst_tag);
+       /* Collect tags they do not have. */
+       for (ref = src; ref; ref = ref->next) {
+               if (prefixcmp(ref->name, "refs/tags/"))
+                       continue; /* not a tag */
+               if (string_list_has_string(&dst_tag, ref->name))
+                       continue; /* they already have it */
+               if (sha1_object_info(ref->new_sha1, NULL) != OBJ_TAG)
+                       continue; /* be conservative */
+               item = string_list_append(&src_tag, ref->name);
+               item->util = ref;
+       }
+       string_list_clear(&dst_tag, 0);
+       /*
+        * At this point, src_tag lists tags that are missing from
+        * dst, and sent_tips lists the tips we are pushing or those
+        * that we know they already have. An element in the src_tag
+        * that is an ancestor of any of the sent_tips needs to be
+        * sent to the other side.
+        */
+       if (sent_tips.nr) {
+               for_each_string_list_item(item, &src_tag) {
+                       struct ref *ref = item->util;
+                       struct ref *dst_ref;
+                       struct commit *commit;
+                       if (is_null_sha1(ref->new_sha1))
+                               continue;
+                       commit = lookup_commit_reference_gently(ref->new_sha1, 1);
+                       if (!commit)
+                               /* not pushing a commit, which is not an error */
+                               continue;
+                       /*
+                        * Is this tag, which they do not have, reachable from
+                        * any of the commits we are sending?
+                        */
+                       if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip))
+                               continue;
+                       /* Add it in */
+                       dst_ref = make_linked_ref(ref->name, dst_tail);
+                       hashcpy(dst_ref->new_sha1, ref->new_sha1);
+                       dst_ref->peer_ref = copy_ref(ref);
+               }
+       }
+       string_list_clear(&src_tag, 0);
+       free(sent_tips.tip);
+ }
  /*
   * Given the set of refs the local repository has, the set of refs the
   * remote repository has, and the refspec used for push, determine
@@@ -1257,6 -1352,10 +1352,10 @@@ int match_push_refs(struct ref *src, st
        free_name:
                free(dst_name);
        }
+       if (flags & MATCH_REFS_FOLLOW_TAGS)
+               add_missing_tags(src, dst, &dst_tail);
        if (send_prune) {
                /* check for missing refs on the remote */
                for (ref = *dst; ref; ref = ref->next) {
@@@ -1285,8 -1384,6 +1384,8 @@@ void set_ref_status_for_push(struct re
        struct ref *ref;
  
        for (ref = remote_refs; ref; ref = ref->next) {
 +              int force_ref_update = ref->force || force_update;
 +
                if (ref->peer_ref)
                        hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
                else if (!send_mirror)
                        continue;
                }
  
 -              /* This part determines what can overwrite what.
 -               * The rules are:
 -               *
 -               * (0) you can always use --force or +A:B notation to
 -               *     selectively force individual ref pairs.
 +              /*
 +               * Decide whether an individual refspec A:B can be
 +               * pushed.  The push will succeed if any of the
 +               * following are true:
                 *
 -               * (1) if the old thing does not exist, it is OK.
 +               * (1) the remote reference B does not exist
                 *
 -               * (2) if you do not have the old thing, you are not allowed
 -               *     to overwrite it; you would not know what you are losing
 -               *     otherwise.
 +               * (2) the remote reference B is being removed (i.e.,
 +               *     pushing :B where no source is specified)
                 *
 -               * (3) if both new and old are commit-ish, and new is a
 -               *     descendant of old, it is OK.
 +               * (3) the destination is not under refs/tags/, and
 +               *     if the old and new value is a commit, the new
 +               *     is a descendant of the old.
                 *
 -               * (4) regardless of all of the above, removing :B is
 -               *     always allowed.
 +               * (4) it is forced using the +A:B notation, or by
 +               *     passing the --force argument
                 */
  
 -              ref->nonfastforward =
 -                      !ref->deletion &&
 -                      !is_null_sha1(ref->old_sha1) &&
 -                      (!has_sha1_file(ref->old_sha1)
 -                        || !ref_newer(ref->new_sha1, ref->old_sha1));
 -
 -              if (ref->nonfastforward && !ref->force && !force_update) {
 -                      ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
 -                      continue;
 +              if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
 +                      int why = 0; /* why would this push require --force? */
 +
 +                      if (!prefixcmp(ref->name, "refs/tags/"))
 +                              why = REF_STATUS_REJECT_ALREADY_EXISTS;
 +                      else if (!has_sha1_file(ref->old_sha1))
 +                              why = REF_STATUS_REJECT_FETCH_FIRST;
 +                      else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
 +                               !lookup_commit_reference_gently(ref->new_sha1, 1))
 +                              why = REF_STATUS_REJECT_NEEDS_FORCE;
 +                      else if (!ref_newer(ref->new_sha1, ref->old_sha1))
 +                              why = REF_STATUS_REJECT_NONFASTFORWARD;
 +
 +                      if (!force_ref_update)
 +                              ref->status = why;
 +                      else if (why)
 +                              ref->forced_update = 1;
                }
        }
  }
@@@ -1466,12 -1556,7 +1565,12 @@@ int get_fetch_map(const struct ref *rem
        } else {
                const char *name = refspec->src[0] ? refspec->src : "HEAD";
  
 -              ref_map = get_remote_ref(remote_refs, name);
 +              if (refspec->exact_sha1) {
 +                      ref_map = alloc_ref(name);
 +                      get_sha1_hex(name, ref_map->old_sha1);
 +              } else {
 +                      ref_map = get_remote_ref(remote_refs, name);
 +              }
                if (!missing_ok && !ref_map)
                        die("Couldn't find remote ref %s", name);
                if (ref_map) {
@@@ -1532,8 -1617,7 +1631,8 @@@ int ref_newer(const unsigned char *new_
        struct commit_list *list, *used;
        int found = 0;
  
 -      /* Both new and old must be commit-ish and new is descendant of
 +      /*
 +       * Both new and old must be commit-ish and new is descendant of
         * old.  Otherwise we require --force.
         */
        o = deref_tag(parse_object(old_sha1), NULL, 0);
diff --combined remote.h
index f7b08f15de3f87ce75d16c81d08e84c47f028877,a0731f1df152c1290dbd93368f2f8d2862c5308c..8743d6ef9df69167c08f3ea9b79b91fbd6ab09d3
+++ b/remote.h
@@@ -62,7 -62,6 +62,7 @@@ struct refspec 
        unsigned force : 1;
        unsigned pattern : 1;
        unsigned matching : 1;
 +      unsigned exact_sha1 : 1;
  
        char *src;
        char *dst;
@@@ -149,7 -148,8 +149,8 @@@ enum match_refs_flags 
        MATCH_REFS_NONE         = 0,
        MATCH_REFS_ALL          = (1 << 0),
        MATCH_REFS_MIRROR       = (1 << 1),
-       MATCH_REFS_PRUNE        = (1 << 2)
+       MATCH_REFS_PRUNE        = (1 << 2),
+       MATCH_REFS_FOLLOW_TAGS  = (1 << 3)
  };
  
  /* Reporting of tracking info */
diff --combined t/t5516-fetch-push.sh
index 6fd125aecf128a6c4a2ae8b986d5a311c6d0c133,4ff2eb2ba872a22f3f18259a2c8c68f7d7084cd5..d3dc5df7b145a23a8d0ab55f69d7ca7351d78274
@@@ -368,7 -368,7 +368,7 @@@ test_expect_success 'push with colon-le
                git branch -D frotz
        fi &&
        git tag -f frotz &&
 -      git push testrepo frotz &&
 +      git push -f testrepo frotz &&
        check_push_result $the_commit tags/frotz &&
        check_push_result $the_first_commit heads/frotz
  
@@@ -929,27 -929,6 +929,27 @@@ test_expect_success 'push into aliased 
        )
  '
  
 +test_expect_success 'push requires --force to update lightweight tag' '
 +      mk_test heads/master &&
 +      mk_child child1 &&
 +      mk_child child2 &&
 +      (
 +              cd child1 &&
 +              git tag Tag &&
 +              git push ../child2 Tag &&
 +              git push ../child2 Tag &&
 +              >file1 &&
 +              git add file1 &&
 +              git commit -m "file1" &&
 +              git tag -f Tag &&
 +              test_must_fail git push ../child2 Tag &&
 +              git push --force ../child2 Tag &&
 +              git tag -f Tag &&
 +              test_must_fail git push ../child2 Tag HEAD~ &&
 +              git push --force ../child2 Tag
 +      )
 +'
 +
  test_expect_success 'push --porcelain' '
        mk_empty &&
        echo >.git/foo  "To testrepo" &&
@@@ -1016,65 -995,77 +1016,138 @@@ test_expect_success 'push --prune refsp
        ! check_push_result $the_first_commit tmp/foo tmp/bar
  '
  
 +for configsection in transfer receive
 +do
 +      test_expect_success "push to update a ref hidden by $configsection.hiderefs" '
 +              mk_test heads/master hidden/one hidden/two hidden/three &&
 +              (
 +                      cd testrepo &&
 +                      git config $configsection.hiderefs refs/hidden
 +              ) &&
 +
 +              # push to unhidden ref succeeds normally
 +              git push testrepo master:refs/heads/master &&
 +              check_push_result $the_commit heads/master &&
 +
 +              # push to update a hidden ref should fail
 +              test_must_fail git push testrepo master:refs/hidden/one &&
 +              check_push_result $the_first_commit hidden/one &&
 +
 +              # push to delete a hidden ref should fail
 +              test_must_fail git push testrepo :refs/hidden/two &&
 +              check_push_result $the_first_commit hidden/two &&
 +
 +              # idempotent push to update a hidden ref should fail
 +              test_must_fail git push testrepo $the_first_commit:refs/hidden/three &&
 +              check_push_result $the_first_commit hidden/three
 +      '
 +done
 +
 +test_expect_success 'fetch exact SHA1' '
 +      mk_test heads/master hidden/one &&
 +      git push testrepo master:refs/hidden/one &&
 +      (
 +              cd testrepo &&
 +              git config transfer.hiderefs refs/hidden
 +      ) &&
 +      check_push_result $the_commit hidden/one &&
 +
 +      mk_child child &&
 +      (
 +              cd child &&
 +
 +              # make sure $the_commit does not exist here
 +              git repack -a -d &&
 +              git prune &&
 +              test_must_fail git cat-file -t $the_commit &&
 +
 +              # fetching the hidden object should fail by default
 +              test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy &&
 +              test_must_fail git rev-parse --verify refs/heads/copy &&
 +
 +              # the server side can allow it to succeed
 +              (
 +                      cd ../testrepo &&
 +                      git config uploadpack.allowtipsha1inwant true
 +              ) &&
 +
 +              git fetch -v ../testrepo $the_commit:refs/heads/copy &&
 +              result=$(git rev-parse --verify refs/heads/copy) &&
 +              test "$the_commit" = "$result"
 +      )
 +'
 +
+ test_expect_success 'fetch follows tags by default' '
+       mk_test heads/master &&
+       rm -fr src dst &&
+       git init src &&
+       (
+               cd src &&
+               git pull ../testrepo master &&
+               git tag -m "annotated" tag &&
+               git for-each-ref >tmp1 &&
+               (
+                       cat tmp1
+                       sed -n "s|refs/heads/master$|refs/remotes/origin/master|p" tmp1
+               ) |
+               sort -k 3 >../expect
+       ) &&
+       git init dst &&
+       (
+               cd dst &&
+               git remote add origin ../src &&
+               git config branch.master.remote origin &&
+               git config branch.master.merge refs/heads/master &&
+               git pull &&
+               git for-each-ref >../actual
+       ) &&
+       test_cmp expect actual
+ '
+ test_expect_success 'push does not follow tags by default' '
+       mk_test heads/master &&
+       rm -fr src dst &&
+       git init src &&
+       git init --bare dst &&
+       (
+               cd src &&
+               git pull ../testrepo master &&
+               git tag -m "annotated" tag &&
+               git checkout -b another &&
+               git commit --allow-empty -m "future commit" &&
+               git tag -m "future" future &&
+               git checkout master &&
+               git for-each-ref refs/heads/master >../expect &&
+               git push ../dst master
+       ) &&
+       (
+               cd dst &&
+               git for-each-ref >../actual
+       ) &&
+       test_cmp expect actual
+ '
+ test_expect_success 'push --follow-tag only pushes relevant tags' '
+       mk_test heads/master &&
+       rm -fr src dst &&
+       git init src &&
+       git init --bare dst &&
+       (
+               cd src &&
+               git pull ../testrepo master &&
+               git tag -m "annotated" tag &&
+               git checkout -b another &&
+               git commit --allow-empty -m "future commit" &&
+               git tag -m "future" future &&
+               git checkout master &&
+               git for-each-ref refs/heads/master refs/tags/tag >../expect
+               git push --follow-tag ../dst master
+       ) &&
+       (
+               cd dst &&
+               git for-each-ref >../actual
+       ) &&
+       test_cmp expect actual
+ '
  test_done
diff --combined transport.c
index 42a61b30b3778181a06afad8afcd5bfcfc56f05a,9fe4aa3e8f8a1b2b134f35635945690a8e52a255..eb9eb33e0f0f151156ca646bb998c538f2471d7f
@@@ -106,8 -106,7 +106,8 @@@ static void insert_packed_refs(const ch
                return;
  
        for (;;) {
 -              int cmp = cmp, len;
 +              int cmp = 0; /* assigned before used */
 +              int len;
  
                if (!fgets(buffer, sizeof(buffer), f)) {
                        fclose(f);
@@@ -519,9 -518,11 +519,9 @@@ static int fetch_refs_via_pack(struct t
                               int nr_heads, struct ref **to_fetch)
  {
        struct git_transport_data *data = transport->data;
 -      struct string_list sought = STRING_LIST_INIT_DUP;
        const struct ref *refs;
        char *dest = xstrdup(transport->url);
        struct fetch_pack_args args;
 -      int i;
        struct ref *refs_tmp = NULL;
  
        memset(&args, 0, sizeof(args));
        args.no_progress = !transport->progress;
        args.depth = data->options.depth;
  
 -      for (i = 0; i < nr_heads; i++)
 -              string_list_append(&sought, to_fetch[i]->name);
 -
        if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
                get_remote_heads(data->fd[0], &refs_tmp, 0, NULL);
  
        refs = fetch_pack(&args, data->fd, data->conn,
                          refs_tmp ? refs_tmp : transport->remote_refs,
 -                        dest, &sought, &transport->pack_lockfile);
 +                        dest, to_fetch, nr_heads,
 +                        &transport->pack_lockfile);
        close(data->fd[0]);
        close(data->fd[1]);
        if (finish_connect(data->conn))
  
        free_refs(refs_tmp);
  
 -      string_list_clear(&sought, 0);
        free(dest);
        return (refs ? 0 : -1);
  }
@@@ -655,7 -659,7 +655,7 @@@ static void print_ok_ref_status(struct 
                const char *msg;
  
                strcpy(quickref, status_abbrev(ref->old_sha1));
 -              if (ref->nonfastforward) {
 +              if (ref->forced_update) {
                        strcat(quickref, "...");
                        type = '+';
                        msg = "forced update";
@@@ -691,18 -695,6 +691,18 @@@ static int print_one_push_status(struc
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
                                                 "non-fast-forward", porcelain);
                break;
 +      case REF_STATUS_REJECT_ALREADY_EXISTS:
 +              print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 +                                               "already exists", porcelain);
 +              break;
 +      case REF_STATUS_REJECT_FETCH_FIRST:
 +              print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 +                                               "fetch first", porcelain);
 +              break;
 +      case REF_STATUS_REJECT_NEEDS_FORCE:
 +              print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 +                                               "needs force", porcelain);
 +              break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
                                                 ref->deletion ? NULL : ref->peer_ref,
  }
  
  void transport_print_push_status(const char *dest, struct ref *refs,
 -                                int verbose, int porcelain, int *nonfastforward)
 +                                int verbose, int porcelain, unsigned int *reject_reasons)
  {
        struct ref *ref;
        int n = 0;
                if (ref->status == REF_STATUS_OK)
                        n += print_one_push_status(ref, dest, n, porcelain);
  
 -      *nonfastforward = 0;
 +      *reject_reasons = 0;
        for (ref = refs; ref; ref = ref->next) {
                if (ref->status != REF_STATUS_NONE &&
                    ref->status != REF_STATUS_UPTODATE &&
                    ref->status != REF_STATUS_OK)
                        n += print_one_push_status(ref, dest, n, porcelain);
 -              if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&
 -                  *nonfastforward != NON_FF_HEAD) {
 +              if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {
                        if (head != NULL && !strcmp(head, ref->name))
 -                              *nonfastforward = NON_FF_HEAD;
 +                              *reject_reasons |= REJECT_NON_FF_HEAD;
                        else
 -                              *nonfastforward = NON_FF_OTHER;
 +                              *reject_reasons |= REJECT_NON_FF_OTHER;
 +              } else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) {
 +                      *reject_reasons |= REJECT_ALREADY_EXISTS;
 +              } else if (ref->status == REF_STATUS_REJECT_FETCH_FIRST) {
 +                      *reject_reasons |= REJECT_FETCH_FIRST;
 +              } else if (ref->status == REF_STATUS_REJECT_NEEDS_FORCE) {
 +                      *reject_reasons |= REJECT_NEEDS_FORCE;
                }
        }
  }
@@@ -1042,67 -1029,11 +1042,67 @@@ static void die_with_unpushed_submodule
        die("Aborting.");
  }
  
 +static int run_pre_push_hook(struct transport *transport,
 +                           struct ref *remote_refs)
 +{
 +      int ret = 0, x;
 +      struct ref *r;
 +      struct child_process proc;
 +      struct strbuf buf;
 +      const char *argv[4];
 +
 +      if (!(argv[0] = find_hook("pre-push")))
 +              return 0;
 +
 +      argv[1] = transport->remote->name;
 +      argv[2] = transport->url;
 +      argv[3] = NULL;
 +
 +      memset(&proc, 0, sizeof(proc));
 +      proc.argv = argv;
 +      proc.in = -1;
 +
 +      if (start_command(&proc)) {
 +              finish_command(&proc);
 +              return -1;
 +      }
 +
 +      strbuf_init(&buf, 256);
 +
 +      for (r = remote_refs; r; r = r->next) {
 +              if (!r->peer_ref) continue;
 +              if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
 +              if (r->status == REF_STATUS_UPTODATE) continue;
 +
 +              strbuf_reset(&buf);
 +              strbuf_addf( &buf, "%s %s %s %s\n",
 +                       r->peer_ref->name, sha1_to_hex(r->new_sha1),
 +                       r->name, sha1_to_hex(r->old_sha1));
 +
 +              if (write_in_full(proc.in, buf.buf, buf.len) != buf.len) {
 +                      ret = -1;
 +                      break;
 +              }
 +      }
 +
 +      strbuf_release(&buf);
 +
 +      x = close(proc.in);
 +      if (!ret)
 +              ret = x;
 +
 +      x = finish_command(&proc);
 +      if (!ret)
 +              ret = x;
 +
 +      return ret;
 +}
 +
  int transport_push(struct transport *transport,
                   int refspec_nr, const char **refspec, int flags,
 -                 int *nonfastforward)
 +                 unsigned int *reject_reasons)
  {
 -      *nonfastforward = 0;
 +      *reject_reasons = 0;
        transport_verify_remote_names(refspec_nr, refspec);
  
        if (transport->push) {
                        match_flags |= MATCH_REFS_MIRROR;
                if (flags & TRANSPORT_PUSH_PRUNE)
                        match_flags |= MATCH_REFS_PRUNE;
+               if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
+                       match_flags |= MATCH_REFS_FOLLOW_TAGS;
  
                if (match_push_refs(local_refs, &remote_refs,
                                    refspec_nr, refspec, match_flags)) {
                        flags & TRANSPORT_PUSH_MIRROR,
                        flags & TRANSPORT_PUSH_FORCE);
  
 +              if (!(flags & TRANSPORT_PUSH_NO_HOOK))
 +                      if (run_pre_push_hook(transport, remote_refs))
 +                              return -1;
 +
                if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
                        struct ref *ref = remote_refs;
                        for (; ref; ref = ref->next)
                if (!quiet || err)
                        transport_print_push_status(transport->url, remote_refs,
                                        verbose | porcelain, porcelain,
 -                                      nonfastforward);
 +                                      reject_reasons);
  
                if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
                        set_upstreams(transport, remote_refs, pretend);
diff --combined transport.h
index a3450e97c0d0d94778a4b635efdd53b724eb70c2,8c493f7f637e1d7e555a7692282aca3d9a086efe..93544627087bdaed1373efb2205fc6f94b89527c
@@@ -104,7 -104,7 +104,8 @@@ struct transport 
  #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
  #define TRANSPORT_PUSH_PRUNE 128
  #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
 +#define TRANSPORT_PUSH_NO_HOOK 512
+ #define TRANSPORT_PUSH_FOLLOW_TAGS 1024
  
  #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
  #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
@@@ -141,15 -141,11 +142,15 @@@ int transport_set_option(struct transpo
  void transport_set_verbosity(struct transport *transport, int verbosity,
        int force_progress);
  
 -#define NON_FF_HEAD 1
 -#define NON_FF_OTHER 2
 +#define REJECT_NON_FF_HEAD     0x01
 +#define REJECT_NON_FF_OTHER    0x02
 +#define REJECT_ALREADY_EXISTS  0x04
 +#define REJECT_FETCH_FIRST     0x08
 +#define REJECT_NEEDS_FORCE     0x10
 +
  int transport_push(struct transport *connection,
                   int refspec_nr, const char **refspec, int flags,
 -                 int * nonfastforward);
 +                 unsigned int * reject_reasons);
  
  const struct ref *transport_get_remote_refs(struct transport *transport);
  
@@@ -175,7 -171,7 +176,7 @@@ void transport_update_tracking_ref(stru
  int transport_refs_pushed(struct ref *ref);
  
  void transport_print_push_status(const char *dest, struct ref *refs,
 -                int verbose, int porcelain, int *nonfastforward);
 +                int verbose, int porcelain, unsigned int *reject_reasons);
  
  typedef void alternate_ref_fn(const struct ref *, void *);
  extern void for_each_alternate_ref(alternate_ref_fn, void *);