Merge branch 'cr/push-force-tag-update'
authorJunio C Hamano <gitster@pobox.com>
Sun, 6 Jan 2013 07:41:34 +0000 (23:41 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 6 Jan 2013 07:41:34 +0000 (23:41 -0800)
Require "-f" for push to update a tag, even if it is a fast-forward.

* cr/push-force-tag-update:
push: allow already-exists advice to be disabled
push: rename config variable for more general use
push: cleanup push rules comment
push: clarify rejection of update to non-commit-ish
push: require force for annotated tags
push: require force for refs under refs/tags/
push: flag updates that require force
push: keep track of "update" state separately
push: add advice for rejected tag reference
push: return reject reasons as a bitset

1  2 
Documentation/config.txt
Documentation/git-push.txt
cache.h
remote.c
diff --combined Documentation/config.txt
index bf8f911e1ffe7820a8e903ec669c690d39612150,90e7d10baddeecbf11dd09145abab1e5dbd3e7cc..50a228899974ea5dc98623647a5bd9f591452fd0
@@@ -140,10 -140,11 +140,11 @@@ advice.*:
        can tell Git that you do not need help by setting these to 'false':
  +
  --
-       pushNonFastForward::
+       pushUpdateRejected::
                Set this variable to 'false' if you want to disable
-               'pushNonFFCurrent', 'pushNonFFDefault', and
-               'pushNonFFMatching' simultaneously.
+               'pushNonFFCurrent', 'pushNonFFDefault',
+               'pushNonFFMatching', and 'pushAlreadyExists'
+               simultaneously.
        pushNonFFCurrent::
                Advice shown when linkgit:git-push[1] fails due to a
                non-fast-forward update to the current branch.
                'matching refs' explicitly (i.e. you used ':', or
                specified a refspec that isn't your current branch) and
                it resulted in a non-fast-forward error.
+       pushAlreadyExists::
+               Shown when linkgit:git-push[1] rejects an update that
+               does not qualify for fast-forwarding (e.g., a tag.)
        statusHints::
                Show directions on how to proceed from the current
 -              state in the output of linkgit:git-status[1] and in
 +              state in the output of linkgit:git-status[1], in
                the template shown when writing commit messages in
 -              linkgit:git-commit[1].
 +              linkgit:git-commit[1], and in the help message shown
 +              by linkgit:git-checkout[1] when switching branch.
        commitBeforeMerge::
                Advice shown when linkgit:git-merge[1] refuses to
                merge to avoid overwriting local changes.
@@@ -963,6 -966,12 +967,6 @@@ difftool.<tool>.cmd:
  difftool.prompt::
        Prompt before each invocation of the diff tool.
  
 -diff.wordRegex::
 -      A POSIX Extended Regular Expression used to determine what is a "word"
 -      when performing word-by-word difference calculations.  Character
 -      sequences that match the regular expression are "words", all other
 -      characters are *ignorable* whitespace.
 -
  fetch.recurseSubmodules::
        This option can be either set to a boolean value or to 'on-demand'.
        Setting it to a boolean changes the behavior of fetch and pull to
index 8b637d339f522e9dd6f3310efdc0691c5bcb14bf,7a04ce5f21429af05b95dcd323245369b8ec33a3..c964b796be742f01aa974ee4d770891b6a65b2cb
@@@ -51,10 -51,11 +51,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>`.
@@@ -286,8 -287,7 +287,8 @@@ leading to commit A.  The history look
  ----------------
  
  Further suppose that the other person already pushed changes leading to A
 -back to the original repository you two obtained the original commit X.
 +back to the original repository from which you two obtained the original
 +commit X.
  
  The push done by the other person updated the branch that used to point at
  commit X to point at commit A.  It is a fast-forward.
@@@ -385,23 -385,11 +386,23 @@@ the ones in the examples below) can be 
        A handy way to push the current branch to the same name on the
        remote.
  
 -`git push origin master:satellite/master dev:satellite/dev`::
 +`git push mothership master:satellite/master dev:satellite/dev`::
        Use the source ref that matches `master` (e.g. `refs/heads/master`)
        to update the ref that matches `satellite/master` (most probably
 -      `refs/remotes/satellite/master`) in the `origin` repository, then
 +      `refs/remotes/satellite/master`) in the `mothership` repository;
        do the same for `dev` and `satellite/dev`.
 ++
 +This is to emulate `git fetch` run on the `mothership` using `git
 +push` that is run in the opposite direction in order to integrate
 +the work done on `satellite`, and is often necessary when you can
 +only make connection in one way (i.e. satellite can ssh into
 +mothership but mothership cannot initiate connection to satellite
 +because the latter is behind a firewall or does not run sshd).
 ++
 +After running this `git push` on the `satellite` machine, you would
 +ssh into the `mothership` and run `git merge` there to complete the
 +emulation of `git pull` that were run on `mothership` to pull changes
 +made on `satellite`.
  
  `git push origin HEAD:master`::
        Push the current branch to the remote ref matching `master` in the
diff --combined cache.h
index 843689b68fcc96c149bbe7332c5945eb7165f16d,a32a0ea91c11f747a4612cc5c0b773635ec426c9..8f89f17a0c3be6520921cf98256bb6e202b2c345
+++ b/cache.h
@@@ -473,8 -473,6 +473,8 @@@ extern int index_name_is_other(const st
  extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
  extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
  
 +#define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
 +
  struct pathspec {
        const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
        int nr;
        struct pathspec_item {
                const char *match;
                int len;
 -              unsigned int use_wildcard:1;
 +              int nowildcard_len;
 +              int flags;
        } *items;
  };
  
@@@ -717,11 -714,10 +717,11 @@@ static inline int is_absolute_path(cons
  }
  int is_directory(const char *);
  const char *real_path(const char *path);
 +const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
  const char *relative_path(const char *abs, const char *base);
  int normalize_path_copy(char *dst, const char *src);
 -int longest_ancestor_length(const char *path, const char *prefix_list);
 +int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
  int daemon_avoid_alias(const char *path);
  int offset_1st_component(const char *path);
@@@ -1003,14 -999,19 +1003,19 @@@ struct ref 
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
        char *symref;
-       unsigned int force:1,
+       unsigned int
+               force:1,
+               requires_force:1,
                merge:1,
                nonfastforward:1,
+               not_forwardable:1,
+               update:1,
                deletion:1;
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_OK,
                REF_STATUS_REJECT_NONFASTFORWARD,
+               REF_STATUS_REJECT_ALREADY_EXISTS,
                REF_STATUS_REJECT_NODELETE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
@@@ -1153,8 -1154,11 +1158,8 @@@ struct config_include_data 
  #define CONFIG_INCLUDE_INIT { 0 }
  extern int git_config_include(const char *name, const char *value, void *data);
  
 -#define IDENT_NAME_GIVEN 01
 -#define IDENT_MAIL_GIVEN 02
 -#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)
 -extern int user_ident_explicitly_given;
 -extern int user_ident_sufficiently_given(void);
 +extern int committer_ident_sufficiently_given(void);
 +extern int author_ident_sufficiently_given(void);
  
  extern const char *git_commit_encoding;
  extern const char *git_log_output_encoding;
diff --combined remote.c
index 6aa49c03df13245486b20b04315437811b260882,aa6b7199b7c0a5abec5dfb697fef6f03f2b419b2..225073909a6e50a393fb9cc58791bb0f260e041b
+++ b/remote.c
@@@ -1279,12 -1279,34 +1279,34 @@@ int match_push_refs(struct ref *src, st
        return 0;
  }
  
+ static inline int is_forwardable(struct ref* ref)
+ {
+       struct object *o;
+       if (!prefixcmp(ref->name, "refs/tags/"))
+               return 0;
+       /* old object must be a commit */
+       o = parse_object(ref->old_sha1);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       /* new object must be commit-ish */
+       o = deref_tag(parse_object(ref->new_sha1), NULL, 0);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       return 1;
+ }
  void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
        int force_update)
  {
        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:
+               /*
+                * The below logic determines whether an individual
+                * refspec A:B can be pushed.  The push will succeed
+                * if any of the following are true:
+                *
+                * (1) the remote reference B does not exist
                 *
-                * (0) you can always use --force or +A:B notation to
-                *     selectively force individual ref pairs.
+                * (2) the remote reference B is being removed (i.e.,
+                *     pushing :B where no source is specified)
                 *
-                * (1) if the old thing does not exist, it is OK.
+                * (3) the update meets all fast-forwarding criteria:
                 *
-                * (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.
+                *     (a) the destination is not under refs/tags/
+                *     (b) the old is a commit
+                *     (c) the new is a descendant of the old
                 *
-                * (3) if both new and old are commit-ish, and new is a
-                *     descendant of old, it is OK.
+                *     NOTE: We must actually have the old object in
+                *     order to overwrite it in the remote reference,
+                *     and the new object must be commit-ish.  These are
+                *     implied by (b) and (c) respectively.
                 *
-                * (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));
+               ref->not_forwardable = !is_forwardable(ref);
  
-               if (ref->nonfastforward && !ref->force && !force_update) {
-                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
-                       continue;
+               ref->update =
+                       !ref->deletion &&
+                       !is_null_sha1(ref->old_sha1);
+               if (ref->update) {
+                       ref->nonfastforward =
+                               !has_sha1_file(ref->old_sha1)
+                                 || !ref_newer(ref->new_sha1, ref->old_sha1);
+                       if (ref->not_forwardable) {
+                               ref->requires_force = 1;
+                               if (!force_ref_update) {
+                                       ref->status = REF_STATUS_REJECT_ALREADY_EXISTS;
+                                       continue;
+                               }
+                       } else if (ref->nonfastforward) {
+                               ref->requires_force = 1;
+                               if (!force_ref_update) {
+                                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+                                       continue;
+                               }
+                       }
                }
        }
  }
@@@ -1458,8 -1501,8 +1501,8 @@@ int get_fetch_map(const struct ref *rem
  
        for (rmp = &ref_map; *rmp; ) {
                if ((*rmp)->peer_ref) {
 -                      if (check_refname_format((*rmp)->peer_ref->name + 5,
 -                              REFNAME_ALLOW_ONELEVEL)) {
 +                      if (prefixcmp((*rmp)->peer_ref->name, "refs/") ||
 +                          check_refname_format((*rmp)->peer_ref->name, 0)) {
                                struct ref *ignore = *rmp;
                                error("* Ignoring funny ref '%s' locally",
                                      (*rmp)->peer_ref->name);
@@@ -1627,16 -1670,13 +1670,16 @@@ int format_tracking_info(struct branch 
  
        base = branch->merge[0]->dst;
        base = shorten_unambiguous_ref(base, 0);
 -      if (!num_theirs)
 +      if (!num_theirs) {
                strbuf_addf(sb,
                        Q_("Your branch is ahead of '%s' by %d commit.\n",
                           "Your branch is ahead of '%s' by %d commits.\n",
                           num_ours),
                        base, num_ours);
 -      else if (!num_ours)
 +              if (advice_status_hints)
 +                      strbuf_addf(sb,
 +                              _("  (use \"git push\" to publish your local commits)\n"));
 +      } else if (!num_ours) {
                strbuf_addf(sb,
                        Q_("Your branch is behind '%s' by %d commit, "
                               "and can be fast-forwarded.\n",
                               "and can be fast-forwarded.\n",
                           num_theirs),
                        base, num_theirs);
 -      else
 +              if (advice_status_hints)
 +                      strbuf_addf(sb,
 +                              _("  (use \"git pull\" to update your local branch)\n"));
 +      } else {
                strbuf_addf(sb,
                        Q_("Your branch and '%s' have diverged,\n"
                               "and have %d and %d different commit each, "
                               "respectively.\n",
                           num_theirs),
                        base, num_ours, num_theirs);
 +              if (advice_status_hints)
 +                      strbuf_addf(sb,
 +                              _("  (use \"git pull\" to merge the remote branch into yours)\n"));
 +      }
        return 1;
  }