Merge branch 'jc/push-reject-reasons'
authorJunio C Hamano <gitster@pobox.com>
Mon, 4 Feb 2013 18:25:04 +0000 (10:25 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 4 Feb 2013 18:25:04 +0000 (10:25 -0800)
Improve error and advice messages given locally when "git push"
refuses when it cannot compute fast-forwardness by separating these
cases from the normal "not a fast-forward; merge first and push
again" case.

* jc/push-reject-reasons:
push: finishing touches to explain REJECT_ALREADY_EXISTS better
push: introduce REJECT_FETCH_FIRST and REJECT_NEEDS_FORCE
push: further simplify the logic to assign rejection reason
push: further clean up fields of "struct ref"

1  2 
Documentation/config.txt
builtin/push.c
cache.h
remote.c
transport.c
transport.h
diff --combined Documentation/config.txt
index 10225cfaf745524ddd01dd7957135bca1c435d91,1f47761c890768555f39c8d2b816c9a91b359114..c8abe86ed5cf6679914f7726b4852b679f6a36ba
@@@ -143,7 -143,8 +143,8 @@@ advice.*:
        pushUpdateRejected::
                Set this variable to 'false' if you want to disable
                'pushNonFFCurrent', 'pushNonFFDefault',
-               'pushNonFFMatching', and 'pushAlreadyExists'
+               'pushNonFFMatching', 'pushAlreadyExists',
+               'pushFetchFirst', and 'pushNeedsForce'
                simultaneously.
        pushNonFFCurrent::
                Advice shown when linkgit:git-push[1] fails due to a
        pushAlreadyExists::
                Shown when linkgit:git-push[1] rejects an update that
                does not qualify for fast-forwarding (e.g., a tag.)
+       pushFetchFirst::
+               Shown when linkgit:git-push[1] rejects an update that
+               tries to overwrite a remote ref that points at an
+               object we do not have.
+       pushNeedsForce::
+               Shown when linkgit:git-push[1] rejects an update that
+               tries to overwrite a remote ref that points at an
+               object that is not a committish, or make the remote
+               ref point at an object that is not a committish.
        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.
@@@ -235,12 -244,6 +245,12 @@@ core.trustctime:
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
  
 +core.checkstat::
 +      Determines which stat fields to match between the index
 +      and work tree. The user can set this to 'default' or
 +      'minimal'. Default (or explicitly 'default'), is to check
 +      all fields, including the sub-second part of mtime and ctime.
 +
  core.quotepath::
        The commands that output paths (e.g. 'ls-files',
        'diff'), when not given the `-z` option, will quote
@@@ -534,12 -537,6 +544,12 @@@ core.editor:
        variable when it is set, and the environment variable
        `GIT_EDITOR` is not set.  See linkgit:git-var[1].
  
 +core.commentchar::
 +      Commands such as `commit` and `tag` that lets you edit
 +      messages consider a line that begins with this character
 +      commented, and removes them after the editor returns
 +      (default '#').
 +
  sequence.editor::
        Text editor used by `git rebase -i` for editing the rebase insn file.
        The value is meant to be interpreted by the shell when it is used.
@@@ -751,12 -748,6 +761,12 @@@ branch.<name>.rebase:
  it unless you understand the implications (see linkgit:git-rebase[1]
  for details).
  
 +branch.<name>.description::
 +      Branch description, can be edited with
 +      `git branch --edit-description`. Branch description is
 +      automatically added in the format-patch cover letter or
 +      request-pull summary.
 +
  browser.<tool>.cmd::
        Specify the command to invoke the specified browser. The
        specified command is evaluated in shell with the URLs passed
@@@ -935,15 -926,6 +945,15 @@@ column.tag:
        Specify whether to output tag listing in `git tag` in columns.
        See `column.ui` for details.
  
 +commit.cleanup::
 +      This setting overrides the default of the `--cleanup` option in
 +      `git commit`. See linkgit:git-commit[1] for details. Changing the
 +      default can be useful when you always want to keep lines that begin
 +      with comment character `#` in your log message, in which case you
 +      would do `git config commit.cleanup whitespace` (note that you will
 +      have to remove the help lines that begin with `#` in the commit log
 +      template yourself, if you do this).
 +
  commit.status::
        A boolean to enable/disable inclusion of status information in the
        commit message template when using an editor to prepare the commit
@@@ -994,6 -976,12 +1004,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
@@@ -1382,12 -1370,6 +1392,12 @@@ help.autocorrect:
        value is 0 - the command will be just shown but not executed.
        This is the default.
  
 +help.htmlpath::
 +      Specify the path where the HTML documentation resides. File system paths
 +      and URLs are supported. HTML pages will be prefixed with this path when
 +      help is displayed in the 'web' format. This defaults to the documentation
 +      path of your Git installation.
 +
  http.proxy::
        Override the HTTP proxy, normally configured using the 'http_proxy',
        'https_proxy', and 'all_proxy' environment variables (see
@@@ -1546,10 -1528,6 +1556,10 @@@ log.showroot:
        Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
        normally hide the root commit will now show it. True by default.
  
 +log.mailmap::
 +      If true, makes linkgit:git-log[1], linkgit:git-show[1], and
 +      linkgit:git-whatchanged[1] assume `--use-mailmap`.
 +
  mailmap.file::
        The location of an augmenting mailmap file. The default
        mailmap, located in the root of the repository, is loaded
        subdirectory, or somewhere outside of the repository itself.
        See linkgit:git-shortlog[1] and linkgit:git-blame[1].
  
 +mailmap.blob::
 +      Like `mailmap.file`, but consider the value as a reference to a
 +      blob in the repository. If both `mailmap.file` and
 +      `mailmap.blob` are given, both are parsed, with entries from
 +      `mailmap.file` taking precedence. In a bare repository, this
 +      defaults to `HEAD:.mailmap`. In a non-bare repository, it
 +      defaults to empty.
 +
  man.viewer::
        Specify the programs that may be used to display help in the
        'man' format. See linkgit:git-help[1].
@@@ -2044,12 -2014,6 +2054,12 @@@ submodule.<name>.update:
        URL and other values found in the `.gitmodules` file.  See
        linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  
 +submodule.<name>.branch::
 +      The remote branch name for a submodule, used by `git submodule
 +      update --remote`.  Set this option to override the value found in
 +      the `.gitmodules` file.  See linkgit:git-submodule[1] and
 +      linkgit:gitmodules[5] for details.
 +
  submodule.<name>.fetchRecurseSubmodules::
        This option can be used to control recursive fetching of this
        submodule. It can be overridden by using the --[no-]recurse-submodules
diff --combined builtin/push.c
index b158028be81e38c37ec8dd69506cdc662ff1379b,3963fbb01466d44e5e9a99e34622fc1a01987dfe..42b129d36cf615ed264be0f1bff523a7fc327b12
@@@ -220,9 -220,20 +220,20 @@@ 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 destination reference already exists\n"
-          "in the remote.");
+       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)
  {
@@@ -252,6 -263,20 +263,20 @@@ static void advise_ref_already_exists(v
        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;
                        advise_checkout_pull_push();
        } 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;
@@@ -407,7 -436,6 +436,7 @@@ 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_END()
        };
  
diff --combined cache.h
index 9038fb500e45e926a696598b91a3759c22e3b306,377a3df157664c7ec71aba3423808ea495eeca9b..e493563f4c07e6adcd00a1b2476926d69a4a67f8
+++ b/cache.h
@@@ -362,7 -362,6 +362,7 @@@ static inline enum object_type object_t
  #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
 +#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
  
  /*
   * Repository-local GIT_* environment variables
@@@ -474,8 -473,6 +474,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;
  };
  
@@@ -494,8 -490,6 +494,8 @@@ extern int init_pathspec(struct pathspe
  extern void free_pathspec(struct pathspec *);
  extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
  
 +extern int limit_pathspec_to_literal(void);
 +
  #define HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
@@@ -536,7 -530,6 +536,7 @@@ extern int delete_ref(const char *, con
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
 +extern int check_stat;
  extern int quote_path_fully;
  extern int has_symlinks;
  extern int minimum_abbrev, default_abbrev;
@@@ -563,12 -556,6 +563,12 @@@ extern int core_preload_index
  extern int core_apply_sparse_checkout;
  extern int precomposed_unicode;
  
 +/*
 + * The character that begins a commented line in user-editable file
 + * that is subject to stripspace.
 + */
 +extern char comment_line_char;
 +
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@@ -727,11 -714,10 +727,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);
@@@ -1015,10 -1001,8 +1015,8 @@@ struct ref 
        char *symref;
        unsigned int
                force:1,
-               requires_force:1,
+               forced_update:1,
                merge:1,
-               nonfastforward:1,
-               update:1,
                deletion:1;
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_REJECT_NONFASTFORWARD,
                REF_STATUS_REJECT_ALREADY_EXISTS,
                REF_STATUS_REJECT_NODELETE,
+               REF_STATUS_REJECT_FETCH_FIRST,
+               REF_STATUS_REJECT_NEEDS_FORCE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
@@@ -1154,9 -1140,6 +1154,9 @@@ extern int check_repository_format_vers
  extern int git_env_bool(const char *, int);
  extern int git_config_system(void);
  extern int config_error_nonbool(const char *);
 +#if defined(__GNUC__) && ! defined(__clang__)
 +#define config_error_nonbool(s) (config_error_nonbool(s), -1)
 +#endif
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
  
@@@ -1170,28 -1153,15 +1170,28 @@@ 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);
 +/*
 + * Match and parse a config key of the form:
 + *
 + *   section.(subsection.)?key
 + *
 + * (i.e., what gets handed to a config_fn_t). The caller provides the section;
 + * we return -1 if it does not match, 0 otherwise. The subsection and key
 + * out-parameters are filled by the function (and subsection is NULL if it is
 + * missing).
 + */
 +extern int parse_config_key(const char *var,
 +                          const char *section,
 +                          const char **subsection, int *subsection_len,
 +                          const char **key);
 +
 +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;
  extern const char *git_mailmap_file;
 +extern const char *git_mailmap_blob;
  
  /* IO helper functions */
  extern void maybe_flush_or_die(FILE *, const char *);
diff --combined remote.c
index 9e21b1d78755cec7b30224f10c7f8ecbef3c9a49,a772e747b7d595e8802ffa031734a6c09de5b54f..e53a6eb7769e2884f77819c73423c31e49b0114e
+++ b/remote.c
@@@ -1317,28 -1317,23 +1317,23 @@@ void set_ref_status_for_push(struct re
                 *     passing the --force argument
                 */
  
-               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 (!prefixcmp(ref->name, "refs/tags/")) {
-                               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;
-                               }
-                       }
+               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;
                }
        }
  }
@@@ -1384,16 -1379,6 +1379,16 @@@ int branch_merge_matches(struct branch 
        return refname_match(branch->merge[i]->src, refname, ref_fetch_rules);
  }
  
 +static int ignore_symref_update(const char *refname)
 +{
 +      unsigned char sha1[20];
 +      int flag;
 +
 +      if (!resolve_ref_unsafe(refname, sha1, 0, &flag))
 +              return 0; /* non-existing refs are OK */
 +      return (flag & REF_ISSYMREF);
 +}
 +
  static struct ref *get_expanded_map(const struct ref *remote_refs,
                                    const struct refspec *refspec)
  {
                if (strchr(ref->name, '^'))
                        continue; /* a dereference item */
                if (match_name_with_pattern(refspec->src, ref->name,
 -                                          refspec->dst, &expn_name)) {
 +                                          refspec->dst, &expn_name) &&
 +                  !ignore_symref_update(expn_name)) {
                        struct ref *cpy = copy_ref(ref);
  
                        cpy->peer_ref = alloc_ref(expn_name);
@@@ -1483,8 -1467,8 +1478,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);
@@@ -1532,7 -1516,8 +1527,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);
@@@ -1652,16 -1637,13 +1648,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;
  }
  
diff --combined transport.c
index 0750a5fbbee7f192ce90d3d800921435cbb6208a,5105562d6e1d4a2d11e0047be57114daf16540c1..384ff9acf1e1c6e192f1f3f75d5fb20c431053ce
@@@ -659,7 -659,7 +659,7 @@@ static void print_ok_ref_status(struct 
                const char *msg;
  
                strcpy(quickref, status_abbrev(ref->old_sha1));
-               if (ref->requires_force) {
+               if (ref->forced_update) {
                        strcat(quickref, "...");
                        type = '+';
                        msg = "forced update";
@@@ -699,6 -699,14 +699,14 @@@ static int print_one_push_status(struc
                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,
@@@ -750,6 -758,10 +758,10 @@@ void transport_print_push_status(const 
                                *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;
                }
        }
  }
@@@ -1034,62 -1046,6 +1046,62 @@@ 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,
                   unsigned int *reject_reasons)
                        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)
diff --combined transport.h
index ac5a9f57d1051ba74fbf641f11d8c965faf8f078,c818763c469fd62e239fc66c6b18f80aec301b72..a3450e97c0d0d94778a4b635efdd53b724eb70c2
@@@ -104,7 -104,6 +104,7 @@@ 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_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
  #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
@@@ -144,6 -143,8 +144,8 @@@ void transport_set_verbosity(struct tra
  #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,