Merge branch 'jt/push-options-doc' into maint
authorJunio C Hamano <gitster@pobox.com>
Sun, 4 Jun 2017 01:21:05 +0000 (10:21 +0900)
committerJunio C Hamano <gitster@pobox.com>
Sun, 4 Jun 2017 01:21:05 +0000 (10:21 +0900)
The receive-pack program now makes sure that the push certificate
records the same set of push options used for pushing.

* jt/push-options-doc:
receive-pack: verify push options in cert
docs: correct receive.advertisePushOptions default

1  2 
Documentation/config.txt
Documentation/technical/pack-protocol.txt
builtin/receive-pack.c
diff --combined Documentation/config.txt
index c398d9a84650793b2d0257f6fc06f743cb5f1d4e,5eb326ed21ffaaa9d19768b409bb1c39cc58567e..1900732305001943907589ab1f36eb77cf924d45
@@@ -79,74 -79,18 +79,74 @@@ escape sequences) are invalid
  Includes
  ~~~~~~~~
  
 -You can include one config file from another by setting the special
 -`include.path` variable to the name of the file to be included. The
 -variable takes a pathname as its value, and is subject to tilde
 -expansion.
 +The `include` and `includeIf` sections allow you to include config
 +directives from another source. These sections behave identically to
 +each other with the exception that `includeIf` sections may be ignored
 +if their condition does not evaluate to true; see "Conditional includes"
 +below.
  
 -The
 -included file is expanded immediately, as if its contents had been
 -found at the location of the include directive. If the value of the
 -`include.path` variable is a relative path, the path is considered to be
 -relative to the configuration file in which the include directive was
 -found.  See below for examples.
 +You can include a config file from another by setting the special
 +`include.path` (or `includeIf.*.path`) variable to the name of the file
 +to be included. The variable takes a pathname as its value, and is
 +subject to tilde expansion. These variables can be given multiple times.
  
 +The contents of the included file are inserted immediately, as if they
 +had been found at the location of the include directive. If the value of the
 +variable is a relative path, the path is considered to
 +be relative to the configuration file in which the include directive
 +was found.  See below for examples.
 +
 +Conditional includes
 +~~~~~~~~~~~~~~~~~~~~
 +
 +You can include a config file from another conditionally by setting a
 +`includeIf.<condition>.path` variable to the name of the file to be
 +included.
 +
 +The condition starts with a keyword followed by a colon and some data
 +whose format and meaning depends on the keyword. Supported keywords
 +are:
 +
 +`gitdir`::
 +
 +      The data that follows the keyword `gitdir:` is used as a glob
 +      pattern. If the location of the .git directory matches the
 +      pattern, the include condition is met.
 ++
 +The .git location may be auto-discovered, or come from `$GIT_DIR`
 +environment variable. If the repository is auto discovered via a .git
 +file (e.g. from submodules, or a linked worktree), the .git location
 +would be the final location where the .git directory is, not where the
 +.git file is.
 ++
 +The pattern can contain standard globbing wildcards and two additional
 +ones, `**/` and `/**`, that can match multiple path components. Please
 +refer to linkgit:gitignore[5] for details. For convenience:
 +
 + * If the pattern starts with `~/`, `~` will be substituted with the
 +   content of the environment variable `HOME`.
 +
 + * If the pattern starts with `./`, it is replaced with the directory
 +   containing the current config file.
 +
 + * If the pattern does not start with either `~/`, `./` or `/`, `**/`
 +   will be automatically prepended. For example, the pattern `foo/bar`
 +   becomes `**/foo/bar` and would match `/any/path/to/foo/bar`.
 +
 + * If the pattern ends with `/`, `**` will be automatically added. For
 +   example, the pattern `foo/` becomes `foo/**`. In other words, it
 +   matches "foo" and everything inside, recursively.
 +
 +`gitdir/i`::
 +      This is the same as `gitdir` except that matching is done
 +      case-insensitively (e.g. on case-insensitive file sytems)
 +
 +A few more notes on matching via `gitdir` and `gitdir/i`:
 +
 + * Symlinks in `$GIT_DIR` are not resolved before matching.
 +
 + * Note that "../" is not special and will match literally, which is
 +   unlikely what you want.
  
  Example
  ~~~~~~~
  
        [include]
                path = /path/to/foo.inc ; include by absolute path
 -              path = foo ; expand "foo" relative to the current file
 -              path = ~/foo ; expand "foo" in your `$HOME` directory
 +              path = foo.inc ; find "foo.inc" relative to the current file
 +              path = ~/foo.inc ; find "foo.inc" in your `$HOME` directory
 +
 +      ; include if $GIT_DIR is /path/to/foo/.git
 +      [includeIf "gitdir:/path/to/foo/.git"]
 +              path = /path/to/foo.inc
 +
 +      ; include for all repositories inside /path/to/group
 +      [includeIf "gitdir:/path/to/group/"]
 +              path = /path/to/foo.inc
  
 +      ; include for all repositories inside $HOME/to/group
 +      [includeIf "gitdir:~/to/group/"]
 +              path = /path/to/foo.inc
 +
 +      ; relative paths are always relative to the including
 +      ; file (if the condition is true); their location is not
 +      ; affected by the condition
 +      [includeIf "gitdir:/path/to/group/"]
 +              path = foo.inc
  
  Values
  ~~~~~~
@@@ -407,10 -334,6 +407,10 @@@ core.trustctime:
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
  
 +core.splitIndex::
 +      If true, the split-index feature of the index will be used.
 +      See linkgit:git-update-index[1]. False by default.
 +
  core.untrackedCache::
        Determines what to do about the untracked cache feature of the
        index. It will be kept, if this variable is unset or set to
@@@ -427,19 -350,16 +427,19 @@@ core.checkStat:
        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
 -      "unusual" characters in the pathname by enclosing the
 -      pathname in a double-quote pair and with backslashes the
 -      same way strings in C source code are quoted.  If this
 -      variable is set to false, the bytes higher than 0x80 are
 -      not quoted but output as verbatim.  Note that double
 -      quote, backslash and control characters are always
 -      quoted without `-z` regardless of the setting of this
 -      variable.
 +      Commands that output paths (e.g. 'ls-files', 'diff'), will
 +      quote "unusual" characters in the pathname by enclosing the
 +      pathname in double-quotes and escaping those characters with
 +      backslashes in the same way C escapes control characters (e.g.
 +      `\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with
 +      values larger than 0x80 (e.g. octal `\302\265` for "micro" in
 +      UTF-8).  If this variable is set to false, bytes higher than
 +      0x80 are not considered "unusual" any more. Double-quotes,
 +      backslash and control characters are always escaped regardless
 +      of the setting of this variable.  A simple space character is
 +      not considered "unusual".  Many commands can output pathnames
 +      completely verbatim using the `-z` option. The default value
 +      is true.
  
  core.eol::
        Sets the line ending type to use in the working directory for
@@@ -2005,10 -1925,7 +2005,10 @@@ http.<url>.*:
    must match exactly between the config key and the URL.
  
  . Host/domain name (e.g., `example.com` in `https://example.com/`).
 -  This field must match exactly between the config key and the URL.
 +  This field must match between the config key and the URL. It is
 +  possible to specify a `*` as part of the host name to match all subdomains
 +  at this level. `https://*.example.com/` for example would match
 +  `https://foo.example.com/`, but not `https://foo.bar.example.com/`.
  
  . Port number (e.g., `8080` in `http://example.com:8080/`).
    This field must match exactly between the config key and the URL.
@@@ -2043,17 -1960,6 +2043,17 @@@ Environment variable settings always ov
  matched against are those given directly to Git commands.  This means any URLs
  visited as a result of a redirection do not participate in matching.
  
 +ssh.variant::
 +      Depending on the value of the environment variables `GIT_SSH` or
 +      `GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git
 +      auto-detects whether to adjust its command-line parameters for use
 +      with plink or tortoiseplink, as opposed to the default (OpenSSH).
 ++
 +The config variable `ssh.variant` can be set to override this auto-detection;
 +valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value
 +will be treated as normal ssh. This setting can be overridden via the
 +environment variable `GIT_SSH_VARIANT`.
 +
  i18n.commitEncoding::
        Character encoding the commit messages are stored in; Git itself
        does not care per se, but this information is necessary e.g. when
@@@ -2631,9 -2537,8 +2631,8 @@@ receive.advertiseAtomic:
        capability, set this variable to false.
  
  receive.advertisePushOptions::
-       By default, git-receive-pack will advertise the push options
-       capability to its clients. If you don't want to advertise this
-       capability, set this variable to false.
+       When set to true, git-receive-pack will advertise the push options
+       capability to its clients. False by default.
  
  receive.autogc::
        By default, git-receive-pack will run "git-gc --auto" after
@@@ -2929,31 -2834,6 +2928,31 @@@ showbranch.default:
        The default set of branches for linkgit:git-show-branch[1].
        See linkgit:git-show-branch[1].
  
 +splitIndex.maxPercentChange::
 +      When the split index feature is used, this specifies the
 +      percent of entries the split index can contain compared to the
 +      total number of entries in both the split index and the shared
 +      index before a new shared index is written.
 +      The value should be between 0 and 100. If the value is 0 then
 +      a new shared index is always written, if it is 100 a new
 +      shared index is never written.
 +      By default the value is 20, so a new shared index is written
 +      if the number of entries in the split index would be greater
 +      than 20 percent of the total number of entries.
 +      See linkgit:git-update-index[1].
 +
 +splitIndex.sharedIndexExpire::
 +      When the split index feature is used, shared index files that
 +      were not modified since the time this variable specifies will
 +      be removed when a new shared index file is created. The value
 +      "now" expires all entries immediately, and "never" suppresses
 +      expiration altogether.
 +      The default value is "2.weeks.ago".
 +      Note that a shared index file is considered modified (for the
 +      purpose of expiration) each time a new split-index file is
 +      either created based on it or read from it.
 +      See linkgit:git-update-index[1].
 +
  status.relativePaths::
        By default, linkgit:git-status[1] shows paths relative to the
        current directory. Setting this variable to `false` shows paths
@@@ -3024,9 -2904,8 +3023,9 @@@ submodule.<name>.url:
        The URL for a submodule. This variable is copied from the .gitmodules
        file to the git config via 'git submodule init'. The user can change
        the configured URL before obtaining the submodule via 'git submodule
 -      update'. After obtaining the submodule, the presence of this variable
 -      is used as a sign whether the submodule is of interest to git commands.
 +      update'. If neither submodule.<name>.active or submodule.active are
 +      set, the presence of this variable is used as a fallback to indicate
 +      whether the submodule is of interest to git commands.
        See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  
  submodule.<name>.update::
@@@ -3064,16 -2943,6 +3063,16 @@@ submodule.<name>.ignore:
        "--ignore-submodules" option. The 'git submodule' commands are not
        affected by this setting.
  
 +submodule.<name>.active::
 +      Boolean value indicating if the submodule is of interest to git
 +      commands.  This config option takes precedence over the
 +      submodule.active config option.
 +
 +submodule.active::
 +      A repeated field which contains a pathspec used to match against a
 +      submodule's path to determine if the submodule is of interest to git
 +      commands.
 +
  submodule.fetchJobs::
        Specifies how many submodules are fetched/cloned at the same time.
        A positive integer allows up to that number of submodules fetched
index 5b0ba3ef20144d1676cedbd8435e7bffddcde2f7,d7358f748abe570a84663150ef90902f86a820bb..a34917153fd3dc10fefe71a1ba05e5dacb9af7f9
@@@ -351,19 -351,14 +351,19 @@@ ACK after 'done' if there is at least o
  multi_ack_detailed is enabled. The server always sends NAK after 'done'
  if there is no common base found.
  
 +Instead of 'ACK' or 'NAK', the server may send an error message (for
 +example, if it does not recognize an object in a 'want' line received
 +from the client).
 +
  Then the server will start sending its packfile data.
  
  ----
 -  server-response = *ack_multi ack / nak
 +  server-response = *ack_multi ack / nak / error-line
    ack_multi       = PKT-LINE("ACK" SP obj-id ack_status)
    ack_status      = "continue" / "common" / "ready"
    ack             = PKT-LINE("ACK" SP obj-id)
    nak             = PKT-LINE("NAK")
 +  error-line     =  PKT-LINE("ERR" SP explanation-text)
  ----
  
  A simple clone may look like this (with no 'have' lines):
@@@ -473,13 -468,10 +473,10 @@@ that it wants to update, it sends a lin
  the server, the obj-id the client would like to update it to and the name
  of the reference.
  
- This list is followed by a flush-pkt. Then the push options are transmitted
- one per packet followed by another flush-pkt. After that the packfile that
- should contain all the objects that the server will need to complete the new
- references will be sent.
+ This list is followed by a flush-pkt.
  
  ----
-   update-request    =  *shallow ( command-list | push-cert ) [packfile]
+   update-requests   =  *shallow ( command-list | push-cert )
  
    shallow           =  PKT-LINE("shallow" SP obj-id)
  
                      PKT-LINE("pusher" SP ident LF)
                      PKT-LINE("pushee" SP url LF)
                      PKT-LINE("nonce" SP nonce LF)
+                     *PKT-LINE("push-option" SP push-option LF)
                      PKT-LINE(LF)
                      *PKT-LINE(command LF)
                      *PKT-LINE(gpg-signature-lines LF)
                      PKT-LINE("push-cert-end" LF)
  
-   packfile          = "PACK" 28*(OCTET)
+   push-option       =  1*( VCHAR | SP )
+ ----
+ If the server has advertised the 'push-options' capability and the client has
+ specified 'push-options' as part of the capability list above, the client then
+ sends its push options followed by a flush-pkt.
+ ----
+   push-options      =  *PKT-LINE(push-option) flush-pkt
+ ----
+ For backwards compatibility with older Git servers, if the client sends a push
+ cert and push options, it MUST send its push options both embedded within the
+ push cert and after the push cert. (Note that the push options within the cert
+ are prefixed, but the push options after the cert are not.) Both these lists
+ MUST be the same, modulo the prefix.
+ After that the packfile that
+ should contain all the objects that the server will need to complete the new
+ references will be sent.
+ ----
+   packfile          =  "PACK" 28*(OCTET)
  ----
  
  If the receiving end does not support delete-refs, the sending end MUST
diff --combined builtin/receive-pack.c
index 48c07ddb65930ed6fd529aed9d90da5618da678b,ed21142349444cac66eca006dc077a17c40f3708..da9a3a2c9dc271af3e5d842e2483e81dddbe9a77
@@@ -21,7 -21,6 +21,7 @@@
  #include "sigchain.h"
  #include "fsck.h"
  #include "tmp-objdir.h"
 +#include "oidset.h"
  
  static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@@ -225,10 -224,10 +225,10 @@@ static int receive_pack_config(const ch
        return git_default_config(var, value, cb);
  }
  
 -static void show_ref(const char *path, const unsigned char *sha1)
 +static void show_ref(const char *path, const struct object_id *oid)
  {
        if (sent_capabilities) {
 -              packet_write_fmt(1, "%s %s\n", sha1_to_hex(sha1), path);
 +              packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), path);
        } else {
                struct strbuf cap = STRBUF_INIT;
  
                        strbuf_addstr(&cap, " push-options");
                strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
                packet_write_fmt(1, "%s %s%c%s\n",
 -                           sha1_to_hex(sha1), path, 0, cap.buf);
 +                           oid_to_hex(oid), path, 0, cap.buf);
                strbuf_release(&cap);
                sent_capabilities = 1;
        }
  }
  
  static int show_ref_cb(const char *path_full, const struct object_id *oid,
 -                     int flag, void *unused)
 +                     int flag, void *data)
  {
 +      struct oidset *seen = data;
        const char *path = strip_namespace(path_full);
  
        if (ref_is_hidden(path, path_full))
        /*
         * Advertise refs outside our current namespace as ".have"
         * refs, so that the client can use them to minimize data
 -       * transfer but will otherwise ignore them. This happens to
 -       * cover ".have" that are thrown in by add_one_alternate_ref()
 -       * to mark histories that are complete in our alternates as
 -       * well.
 +       * transfer but will otherwise ignore them.
         */
 -      if (!path)
 +      if (!path) {
 +              if (oidset_insert(seen, oid))
 +                      return 0;
                path = ".have";
 -      show_ref(path, oid->hash);
 +      } else {
 +              oidset_insert(seen, oid);
 +      }
 +      show_ref(path, oid);
        return 0;
  }
  
 -static int show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
 +static void show_one_alternate_ref(const char *refname,
 +                                 const struct object_id *oid,
 +                                 void *data)
  {
 -      show_ref(".have", sha1);
 -      return 0;
 -}
 +      struct oidset *seen = data;
  
 -static void collect_one_alternate_ref(const struct ref *ref, void *data)
 -{
 -      struct sha1_array *sa = data;
 -      sha1_array_append(sa, ref->old_oid.hash);
 +      if (oidset_insert(seen, oid))
 +              return;
 +
 +      show_ref(".have", oid);
  }
  
  static void write_head_info(void)
  {
 -      struct sha1_array sa = SHA1_ARRAY_INIT;
 +      static struct oidset seen = OIDSET_INIT;
  
 -      for_each_alternate_ref(collect_one_alternate_ref, &sa);
 -      sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
 -      sha1_array_clear(&sa);
 -      for_each_ref(show_ref_cb, NULL);
 +      for_each_ref(show_ref_cb, &seen);
 +      for_each_alternate_ref(show_one_alternate_ref, &seen);
 +      oidset_clear(&seen);
        if (!sent_capabilities)
 -              show_ref("capabilities^{}", null_sha1);
 +              show_ref("capabilities^{}", &null_oid);
  
        advertise_shallow_grafts(1);
  
@@@ -309,8 -306,8 +309,8 @@@ struct command 
        unsigned int skip_update:1,
                     did_not_exist:1;
        int index;
 -      unsigned char old_sha1[20];
 -      unsigned char new_sha1[20];
 +      struct object_id old_oid;
 +      struct object_id new_oid;
        char ref_name[FLEX_ARRAY]; /* more */
  };
  
@@@ -473,7 -470,8 +473,8 @@@ static char *prepare_push_cert_nonce(co
   * after dropping "_commit" from its name and possibly moving it out
   * of commit.c
   */
- static char *find_header(const char *msg, size_t len, const char *key)
+ static char *find_header(const char *msg, size_t len, const char *key,
+                        const char **next_line)
  {
        int key_len = strlen(key);
        const char *line = msg;
                if (line + key_len < eol &&
                    !memcmp(line, key, key_len) && line[key_len] == ' ') {
                        int offset = key_len + 1;
+                       if (next_line)
+                               *next_line = *eol ? eol + 1 : eol;
                        return xmemdupz(line + offset, (eol - line) - offset);
                }
                line = *eol ? eol + 1 : NULL;
  
  static const char *check_nonce(const char *buf, size_t len)
  {
-       char *nonce = find_header(buf, len, "nonce");
+       char *nonce = find_header(buf, len, "nonce", NULL);
        unsigned long stamp, ostamp;
        char *bohmac, *expect = NULL;
        const char *retval = NONCE_BAD;
@@@ -575,6 -575,45 +578,45 @@@ leave
        return retval;
  }
  
+ /*
+  * Return 1 if there is no push_cert or if the push options in push_cert are
+  * the same as those in the argument; 0 otherwise.
+  */
+ static int check_cert_push_options(const struct string_list *push_options)
+ {
+       const char *buf = push_cert.buf;
+       int len = push_cert.len;
+       char *option;
+       const char *next_line;
+       int options_seen = 0;
+       int retval = 1;
+       if (!len)
+               return 1;
+       while ((option = find_header(buf, len, "push-option", &next_line))) {
+               len -= (next_line - buf);
+               buf = next_line;
+               options_seen++;
+               if (options_seen > push_options->nr
+                   || strcmp(option,
+                             push_options->items[options_seen - 1].string)) {
+                       retval = 0;
+                       goto leave;
+               }
+               free(option);
+       }
+       if (options_seen != push_options->nr)
+               retval = 0;
+ leave:
+       free(option);
+       return retval;
+ }
  static void prepare_push_cert_sha1(struct child_process *proc)
  {
        static int already_done;
@@@ -723,7 -762,7 +765,7 @@@ static int feed_receive_hook(void *stat
                return -1; /* EOF */
        strbuf_reset(&state->buf);
        strbuf_addf(&state->buf, "%s %s %s\n",
 -                  sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
 +                  oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
                    cmd->ref_name);
        state->cmd = cmd->next;
        if (bufp) {
@@@ -764,14 -803,15 +806,14 @@@ static int run_update_hook(struct comma
                return 0;
  
        argv[1] = cmd->ref_name;
 -      argv[2] = sha1_to_hex(cmd->old_sha1);
 -      argv[3] = sha1_to_hex(cmd->new_sha1);
 +      argv[2] = oid_to_hex(&cmd->old_oid);
 +      argv[3] = oid_to_hex(&cmd->new_oid);
        argv[4] = NULL;
  
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
        proc.err = use_sideband ? -1 : 0;
        proc.argv = argv;
 -      proc.env = tmp_objdir_env(tmp_objdir);
  
        code = start_command(&proc);
        if (code)
@@@ -830,7 -870,7 +872,7 @@@ static int command_singleton_iterator(v
  static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
  {
        static struct lock_file shallow_lock;
 -      struct sha1_array extra = SHA1_ARRAY_INIT;
 +      struct oid_array extra = OID_ARRAY_INIT;
        struct check_connected_options opt = CHECK_CONNECTED_INIT;
        uint32_t mask = 1 << (cmd->index % 32);
        int i;
                if (si->used_shallow[i] &&
                    (si->used_shallow[i][cmd->index / 32] & mask) &&
                    !delayed_reachability_test(si, i))
 -                      sha1_array_append(&extra, si->shallow->sha1[i]);
 +                      oid_array_append(&extra, &si->shallow->oid[i]);
  
        opt.env = tmp_objdir_env(tmp_objdir);
        setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
        if (check_connected(command_singleton_iterator, cmd, &opt)) {
                rollback_lock_file(&shallow_lock);
 -              sha1_array_clear(&extra);
 +              oid_array_clear(&extra);
                return -1;
        }
  
         * not lose these new roots..
         */
        for (i = 0; i < extra.nr; i++)
 -              register_shallow(extra.sha1[i]);
 +              register_shallow(extra.oid[i].hash);
  
        si->shallow_ref[cmd->index] = 0;
 -      sha1_array_clear(&extra);
 +      oid_array_clear(&extra);
        return 0;
  }
  
@@@ -986,10 -1026,9 +1028,10 @@@ static const char *update(struct comman
  {
        const char *name = cmd->ref_name;
        struct strbuf namespaced_name_buf = STRBUF_INIT;
 -      const char *namespaced_name, *ret;
 -      unsigned char *old_sha1 = cmd->old_sha1;
 -      unsigned char *new_sha1 = cmd->new_sha1;
 +      static char *namespaced_name;
 +      const char *ret;
 +      struct object_id *old_oid = &cmd->old_oid;
 +      struct object_id *new_oid = &cmd->new_oid;
  
        /* only refs/... are allowed */
        if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
        }
  
        strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
 +      free(namespaced_name);
        namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
  
        if (is_ref_checked_out(namespaced_name)) {
                                refuse_unconfigured_deny();
                        return "branch is currently checked out";
                case DENY_UPDATE_INSTEAD:
 -                      ret = update_worktree(new_sha1);
 +                      ret = update_worktree(new_oid->hash);
                        if (ret)
                                return ret;
                        break;
                }
        }
  
 -      if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
 +      if (!is_null_oid(new_oid) && !has_object_file(new_oid)) {
                error("unpack should have generated %s, "
 -                    "but I can't find it!", sha1_to_hex(new_sha1));
 +                    "but I can't find it!", oid_to_hex(new_oid));
                return "bad pack";
        }
  
 -      if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
 +      if (!is_null_oid(old_oid) && is_null_oid(new_oid)) {
                if (deny_deletes && starts_with(name, "refs/heads/")) {
                        rp_error("denying ref deletion for %s", name);
                        return "deletion prohibited";
                }
        }
  
 -      if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
 -          !is_null_sha1(old_sha1) &&
 +      if (deny_non_fast_forwards && !is_null_oid(new_oid) &&
 +          !is_null_oid(old_oid) &&
            starts_with(name, "refs/heads/")) {
                struct object *old_object, *new_object;
                struct commit *old_commit, *new_commit;
  
 -              old_object = parse_object(old_sha1);
 -              new_object = parse_object(new_sha1);
 +              old_object = parse_object(old_oid->hash);
 +              new_object = parse_object(new_oid->hash);
  
                if (!old_object || !new_object ||
                    old_object->type != OBJ_COMMIT ||
                return "hook declined";
        }
  
 -      if (is_null_sha1(new_sha1)) {
 +      if (is_null_oid(new_oid)) {
                struct strbuf err = STRBUF_INIT;
 -              if (!parse_object(old_sha1)) {
 -                      old_sha1 = NULL;
 +              if (!parse_object(old_oid->hash)) {
 +                      old_oid = NULL;
                        if (ref_exists(name)) {
                                rp_warning("Allowing deletion of corrupt ref.");
                        } else {
                }
                if (ref_transaction_delete(transaction,
                                           namespaced_name,
 -                                         old_sha1,
 +                                         old_oid->hash,
                                           0, "push", &err)) {
                        rp_error("%s", err.buf);
                        strbuf_release(&err);
  
                if (ref_transaction_update(transaction,
                                           namespaced_name,
 -                                         new_sha1, old_sha1,
 +                                         new_oid->hash, old_oid->hash,
                                           0, "push",
                                           &err)) {
                        rp_error("%s", err.buf);
@@@ -1163,7 -1201,7 +1205,7 @@@ static void check_aliased_update(struc
        const char *dst_name;
        struct string_list_item *item;
        struct command *dst_cmd;
 -      unsigned char sha1[GIT_SHA1_RAWSZ];
 +      unsigned char sha1[GIT_MAX_RAWSZ];
        int flag;
  
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
  
        dst_cmd = (struct command *) item->util;
  
 -      if (!hashcmp(cmd->old_sha1, dst_cmd->old_sha1) &&
 -          !hashcmp(cmd->new_sha1, dst_cmd->new_sha1))
 +      if (!oidcmp(&cmd->old_oid, &dst_cmd->old_oid) &&
 +          !oidcmp(&cmd->new_oid, &dst_cmd->new_oid))
                return;
  
        dst_cmd->skip_update = 1;
        rp_error("refusing inconsistent update between symref '%s' (%s..%s) and"
                 " its target '%s' (%s..%s)",
                 cmd->ref_name,
 -               find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV),
 -               find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV),
 +               find_unique_abbrev(cmd->old_oid.hash, DEFAULT_ABBREV),
 +               find_unique_abbrev(cmd->new_oid.hash, DEFAULT_ABBREV),
                 dst_cmd->ref_name,
 -               find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV),
 -               find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV));
 +               find_unique_abbrev(dst_cmd->old_oid.hash, DEFAULT_ABBREV),
 +               find_unique_abbrev(dst_cmd->new_oid.hash, DEFAULT_ABBREV));
  
        cmd->error_string = dst_cmd->error_string =
                "inconsistent aliased update";
@@@ -1232,10 -1270,10 +1274,10 @@@ static int command_singleton_iterator(v
        struct command **cmd_list = cb_data;
        struct command *cmd = *cmd_list;
  
 -      if (!cmd || is_null_sha1(cmd->new_sha1))
 +      if (!cmd || is_null_oid(&cmd->new_oid))
                return -1; /* end of list */
        *cmd_list = NULL; /* this returns only one */
 -      hashcpy(sha1, cmd->new_sha1);
 +      hashcpy(sha1, cmd->new_oid.hash);
        return 0;
  }
  
@@@ -1276,8 -1314,8 +1318,8 @@@ static int iterate_receive_command_list
                if (shallow_update && data->si->shallow_ref[cmd->index])
                        /* to be checked in update_shallow_ref() */
                        continue;
 -              if (!is_null_sha1(cmd->new_sha1) && !cmd->skip_update) {
 -                      hashcpy(sha1, cmd->new_sha1);
 +              if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) {
 +                      hashcpy(sha1, cmd->new_oid.hash);
                        *cmd_list = cmd->next;
                        return 0;
                }
@@@ -1304,7 -1342,7 +1346,7 @@@ static void reject_updates_to_hidden(st
  
                if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
                        continue;
 -              if (is_null_sha1(cmd->new_sha1))
 +              if (is_null_oid(&cmd->new_oid))
                        cmd->error_string = "deny deleting a hidden ref";
                else
                        cmd->error_string = "deny updating a hidden ref";
@@@ -1415,7 -1453,7 +1457,7 @@@ static void execute_commands(struct com
  {
        struct check_connected_options opt = CHECK_CONNECTED_INIT;
        struct command *cmd;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct iterate_data data;
        struct async muxer;
        int err_fd = 0;
        check_aliased_updates(commands);
  
        free(head_name_to_free);
 -      head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
 +      head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL);
  
        if (use_atomic)
                execute_commands_atomic(commands, si);
@@@ -1487,23 -1525,23 +1529,23 @@@ static struct command **queue_command(s
                                      const char *line,
                                      int linelen)
  {
 -      unsigned char old_sha1[20], new_sha1[20];
 +      struct object_id old_oid, new_oid;
        struct command *cmd;
        const char *refname;
        int reflen;
 +      const char *p;
  
 -      if (linelen < 83 ||
 -          line[40] != ' ' ||
 -          line[81] != ' ' ||
 -          get_sha1_hex(line, old_sha1) ||
 -          get_sha1_hex(line + 41, new_sha1))
 +      if (parse_oid_hex(line, &old_oid, &p) ||
 +          *p++ != ' ' ||
 +          parse_oid_hex(p, &new_oid, &p) ||
 +          *p++ != ' ')
                die("protocol error: expected old/new/ref, got '%s'", line);
  
 -      refname = line + 82;
 -      reflen = linelen - 82;
 +      refname = p;
 +      reflen = linelen - (p - line);
        FLEX_ALLOC_MEM(cmd, ref_name, refname, reflen);
 -      hashcpy(cmd->old_sha1, old_sha1);
 -      hashcpy(cmd->new_sha1, new_sha1);
 +      oidcpy(&cmd->old_oid, &old_oid);
 +      oidcpy(&cmd->new_oid, &new_oid);
        *tail = cmd;
        return &cmd->next;
  }
@@@ -1525,12 -1563,12 +1567,12 @@@ static void queue_commands_from_cert(st
  
        while (boc < eoc) {
                const char *eol = memchr(boc, '\n', eoc - boc);
 -              tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol);
 +              tail = queue_command(tail, boc, eol ? eol - boc : eoc - boc);
                boc = eol ? eol + 1 : eoc;
        }
  }
  
 -static struct command *read_head_info(struct sha1_array *shallow)
 +static struct command *read_head_info(struct oid_array *shallow)
  {
        struct command *commands = NULL;
        struct command **p = &commands;
                if (!line)
                        break;
  
 -              if (len == 48 && starts_with(line, "shallow ")) {
 -                      unsigned char sha1[20];
 -                      if (get_sha1_hex(line + 8, sha1))
 +              if (len 8 && starts_with(line, "shallow ")) {
 +                      struct object_id oid;
 +                      if (get_oid_hex(line + 8, &oid))
                                die("protocol error: expected shallow sha, got '%s'",
                                    line + 8);
 -                      sha1_array_append(shallow, sha1);
 +                      oid_array_append(shallow, &oid);
                        continue;
                }
  
@@@ -1635,17 -1673,12 +1677,17 @@@ static const char *parse_pack_header(st
  
  static const char *pack_lockfile;
  
 +static void push_header_arg(struct argv_array *args, struct pack_header *hdr)
 +{
 +      argv_array_pushf(args, "--pack_header=%"PRIu32",%"PRIu32,
 +                      ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries));
 +}
 +
  static const char *unpack(int err_fd, struct shallow_info *si)
  {
        struct pack_header hdr;
        const char *hdr_err;
        int status;
 -      char hdr_arg[38];
        struct child_process child = CHILD_PROCESS_INIT;
        int fsck_objects = (receive_fsck_objects >= 0
                            ? receive_fsck_objects
                        close(err_fd);
                return hdr_err;
        }
 -      snprintf(hdr_arg, sizeof(hdr_arg),
 -                      "--pack_header=%"PRIu32",%"PRIu32,
 -                      ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
  
        if (si->nr_ours || si->nr_theirs) {
                alt_shallow_file = setup_temporary_shallow(si->shallow);
        tmp_objdir_add_as_alternate(tmp_objdir);
  
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
 -              argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
 +              argv_array_push(&child.args, "unpack-objects");
 +              push_header_arg(&child.args, &hdr);
                if (quiet)
                        argv_array_push(&child.args, "-q");
                if (fsck_objects)
                if (status)
                        return "unpack-objects abnormal exit";
        } else {
 -              char hostname[256];
 +              char hostname[HOST_NAME_MAX + 1];
  
 -              argv_array_pushl(&child.args, "index-pack",
 -                               "--stdin", hdr_arg, NULL);
 +              argv_array_pushl(&child.args, "index-pack", "--stdin", NULL);
 +              push_header_arg(&child.args, &hdr);
  
 -              if (gethostname(hostname, sizeof(hostname)))
 +              if (xgethostname(hostname, sizeof(hostname)))
                        xsnprintf(hostname, sizeof(hostname), "localhost");
                argv_array_pushf(&child.args,
                                 "--keep=receive-pack %"PRIuMAX" on %s",
@@@ -1808,7 -1843,7 +1850,7 @@@ static void prepare_shallow_update(stru
  
  static void update_shallow_info(struct command *commands,
                                struct shallow_info *si,
 -                              struct sha1_array *ref)
 +                              struct oid_array *ref)
  {
        struct command *cmd;
        int *ref_status;
        }
  
        for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (is_null_sha1(cmd->new_sha1))
 +              if (is_null_oid(&cmd->new_oid))
                        continue;
 -              sha1_array_append(ref, cmd->new_sha1);
 +              oid_array_append(ref, &cmd->new_oid);
                cmd->index = ref->nr - 1;
        }
        si->ref = ref;
        ALLOC_ARRAY(ref_status, ref->nr);
        assign_shallow_commits_to_refs(si, NULL, ref_status);
        for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (is_null_sha1(cmd->new_sha1))
 +              if (is_null_oid(&cmd->new_oid))
                        continue;
                if (ref_status[cmd->index]) {
                        cmd->error_string = "shallow update not allowed";
@@@ -1872,7 -1907,7 +1914,7 @@@ static int delete_only(struct command *
  {
        struct command *cmd;
        for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (!is_null_sha1(cmd->new_sha1))
 +              if (!is_null_oid(&cmd->new_oid))
                        return 0;
        }
        return 1;
@@@ -1882,8 -1917,8 +1924,8 @@@ int cmd_receive_pack(int argc, const ch
  {
        int advertise_refs = 0;
        struct command *commands;
 -      struct sha1_array shallow = SHA1_ARRAY_INIT;
 -      struct sha1_array ref = SHA1_ARRAY_INIT;
 +      struct oid_array shallow = OID_ARRAY_INIT;
 +      struct oid_array ref = OID_ARRAY_INIT;
        struct shallow_info si;
  
        struct option options[] = {
  
                if (use_push_options)
                        read_push_options(&push_options);
+               if (!check_cert_push_options(&push_options)) {
+                       struct command *cmd;
+                       for (cmd = commands; cmd; cmd = cmd->next)
+                               cmd->error_string = "inconsistent push options";
+               }
  
                prepare_shallow_info(&si, &shallow);
                if (!si.nr_ours && !si.nr_theirs)
        }
        if (use_sideband)
                packet_flush(1);
 -      sha1_array_clear(&shallow);
 -      sha1_array_clear(&ref);
 +      oid_array_clear(&shallow);
 +      oid_array_clear(&ref);
        free((void *)push_cert_nonce);
        return 0;
  }