Merge branch 'mk/http-backend-content-length'
authorJunio C Hamano <gitster@pobox.com>
Fri, 17 Aug 2018 20:09:57 +0000 (13:09 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 17 Aug 2018 20:09:57 +0000 (13:09 -0700)
The http-backend (used for smart-http transport) used to slurp the
whole input until EOF, without paying attention to CONTENT_LENGTH
that is supplied in the environment and instead expecting the Web
server to close the input stream. This has been fixed.

* mk/http-backend-content-length:
t5562: avoid non-portable "export FOO=bar" construct
http-backend: respect CONTENT_LENGTH for receive-pack
http-backend: respect CONTENT_LENGTH as specified by rfc3875
http-backend: cleanup writing to child process

1  2 
config.c
config.h
help.c
http-backend.c
diff --combined config.c
index 10522f399e6eb5300b55eaeaf8fcbf6546e53d11,158afa858b6c9fce1f23940e41d83aaf229eeed2..4446909229a0f7b14d7428ad9860c9a655c77df8
+++ b/config.c
@@@ -14,7 -14,6 +14,7 @@@
  #include "quote.h"
  #include "hashmap.h"
  #include "string-list.h"
 +#include "object-store.h"
  #include "utf8.h"
  #include "dir.h"
  #include "color.h"
@@@ -32,7 -31,7 +32,7 @@@ struct config_source 
        enum config_origin_type origin_type;
        const char *name;
        const char *path;
 -      int die_on_error;
 +      enum config_error_action default_error_action;
        int linenr;
        int eof;
        struct strbuf value;
@@@ -117,12 -116,12 +117,12 @@@ static long config_buf_ftell(struct con
  }
  
  #define MAX_INCLUDE_DEPTH 10
 -static const char include_depth_advice[] =
 +static const char include_depth_advice[] = N_(
  "exceeded maximum include depth (%d) while including\n"
  "     %s\n"
  "from\n"
  "     %s\n"
 -"Do you have circular includes?";
 +"Do you have circular includes?");
  static int handle_path_include(const char *path, struct config_include_data *inc)
  {
        int ret = 0;
  
        expanded = expand_user_path(path, 0);
        if (!expanded)
 -              return error("could not expand include path '%s'", path);
 +              return error(_("could not expand include path '%s'"), path);
        path = expanded;
  
        /*
                char *slash;
  
                if (!cf || !cf->path)
 -                      return error("relative config includes must come from files");
 +                      return error(_("relative config includes must come from files"));
  
                slash = find_last_dir_sep(cf->path);
                if (slash)
  
        if (!access_or_die(path, R_OK, 0)) {
                if (++inc->depth > MAX_INCLUDE_DEPTH)
 -                      die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
 +                      die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
                            !cf ? "<unknown>" :
                            cf->name ? cf->name :
                            "the command line");
@@@ -343,13 -342,13 +343,13 @@@ static int git_config_parse_key_1(cons
  
        if (last_dot == NULL || last_dot == key) {
                if (!quiet)
 -                      error("key does not contain a section: %s", key);
 +                      error(_("key does not contain a section: %s"), key);
                return -CONFIG_NO_SECTION_OR_NAME;
        }
  
        if (!last_dot[1]) {
                if (!quiet)
 -                      error("key does not contain variable name: %s", key);
 +                      error(_("key does not contain variable name: %s"), key);
                return -CONFIG_NO_SECTION_OR_NAME;
        }
  
                        if (!iskeychar(c) ||
                            (i == baselen + 1 && !isalpha(c))) {
                                if (!quiet)
 -                                      error("invalid key: %s", key);
 +                                      error(_("invalid key: %s"), key);
                                goto out_free_ret_1;
                        }
                        c = tolower(c);
                } else if (c == '\n') {
                        if (!quiet)
 -                              error("invalid key (newline): %s", key);
 +                              error(_("invalid key (newline): %s"), key);
                        goto out_free_ret_1;
                }
                if (store_key)
@@@ -415,7 -414,7 +415,7 @@@ int git_config_parse_parameter(const ch
  
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
 -              return error("bogus config parameter: %s", text);
 +              return error(_("bogus config parameter: %s"), text);
  
        if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') {
                strbuf_setlen(pair[0], pair[0]->len - 1);
        strbuf_trim(pair[0]);
        if (!pair[0]->len) {
                strbuf_list_free(pair);
 -              return error("bogus config parameter: %s", text);
 +              return error(_("bogus config parameter: %s"), text);
        }
  
        if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
@@@ -462,7 -461,7 +462,7 @@@ int git_config_from_parameters(config_f
        envw = xstrdup(env);
  
        if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
 -              ret = error("bogus format in " CONFIG_DATA_ENVIRONMENT);
 +              ret = error(_("bogus format in %s"), CONFIG_DATA_ENVIRONMENT);
                goto out;
        }
  
@@@ -810,21 -809,10 +810,21 @@@ static int git_parse_source(config_fn_
                                      cf->linenr, cf->name);
        }
  
 -      if (cf->die_on_error)
 +      switch (opts && opts->error_action ?
 +              opts->error_action :
 +              cf->default_error_action) {
 +      case CONFIG_ERROR_DIE:
                die("%s", error_msg);
 -      else
 +              break;
 +      case CONFIG_ERROR_ERROR:
                error_return = error("%s", error_msg);
 +              break;
 +      case CONFIG_ERROR_SILENT:
 +              error_return = -1;
 +              break;
 +      case CONFIG_ERROR_UNSET:
 +              BUG("config error action unset");
 +      }
  
        free(error_msg);
        return error_return;
@@@ -933,7 -921,7 +933,7 @@@ int git_parse_ulong(const char *value, 
        return 1;
  }
  
static int git_parse_ssize_t(const char *value, ssize_t *ret)
+ int git_parse_ssize_t(const char *value, ssize_t *ret)
  {
        intmax_t tmp;
        if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t)))
@@@ -1166,7 -1154,7 +1166,7 @@@ static int git_default_core_config(cons
                else {
                        int abbrev = git_config_int(var, value);
                        if (abbrev < minimum_abbrev || abbrev > 40)
 -                              return error("abbrev length out of range: %d", abbrev);
 +                              return error(_("abbrev length out of range: %d"), abbrev);
                        default_abbrev = abbrev;
                }
                return 0;
                }
                eol_rndtrp_die = git_config_bool(var, value);
                global_conv_flags_eol = eol_rndtrp_die ?
 -                      CONV_EOL_RNDTRP_DIE : CONV_EOL_RNDTRP_WARN;
 +                      CONV_EOL_RNDTRP_DIE : 0;
                return 0;
        }
  
                        comment_line_char = value[0];
                        auto_comment_line_char = 0;
                } else
 -                      return error("core.commentChar should only be one character");
 +                      return error(_("core.commentChar should only be one character"));
                return 0;
        }
  
                return 0;
        }
  
 -      if (!strcmp(var, "core.commitgraph")) {
 -              core_commit_graph = git_config_bool(var, value);
 -              return 0;
 -      }
 -
        if (!strcmp(var, "core.sparsecheckout")) {
                core_apply_sparse_checkout = git_config_bool(var, value);
                return 0;
                                         var, value);
        }
  
 +      if (!strcmp(var, "core.usereplacerefs")) {
 +              read_replace_refs = git_config_bool(var, value);
 +              return 0;
 +      }
 +
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
  }
@@@ -1396,7 -1384,7 +1396,7 @@@ static int git_default_branch_config(co
                else if (!strcmp(value, "always"))
                        autorebase = AUTOREBASE_ALWAYS;
                else
 -                      return error("malformed value for %s", var);
 +                      return error(_("malformed value for %s"), var);
                return 0;
        }
  
@@@ -1422,9 -1410,9 +1422,9 @@@ static int git_default_push_config(cons
                else if (!strcmp(value, "current"))
                        push_default = PUSH_DEFAULT_CURRENT;
                else {
 -                      error("malformed value for %s: %s", var, value);
 -                      return error("Must be one of nothing, matching, simple, "
 -                                   "upstream or current.");
 +                      error(_("malformed value for %s: %s"), var, value);
 +                      return error(_("must be one of nothing, matching, simple, "
 +                                     "upstream or current"));
                }
                return 0;
        }
@@@ -1532,7 -1520,7 +1532,7 @@@ static int do_config_from_file(config_f
        top.origin_type = origin_type;
        top.name = name;
        top.path = path;
 -      top.die_on_error = 1;
 +      top.default_error_action = CONFIG_ERROR_DIE;
        top.do_fgetc = config_file_fgetc;
        top.do_ungetc = config_file_ungetc;
        top.do_ftell = config_file_ftell;
@@@ -1570,10 -1558,8 +1570,10 @@@ int git_config_from_file(config_fn_t fn
        return git_config_from_file_with_options(fn, filename, data, NULL);
  }
  
 -int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type,
 -                      const char *name, const char *buf, size_t len, void *data)
 +int git_config_from_mem(config_fn_t fn,
 +                      const enum config_origin_type origin_type,
 +                      const char *name, const char *buf, size_t len,
 +                      void *data, const struct config_options *opts)
  {
        struct config_source top;
  
        top.origin_type = origin_type;
        top.name = name;
        top.path = NULL;
 -      top.die_on_error = 0;
 +      top.default_error_action = CONFIG_ERROR_ERROR;
        top.do_fgetc = config_buf_fgetc;
        top.do_ungetc = config_buf_ungetc;
        top.do_ftell = config_buf_ftell;
  
 -      return do_config_from(&top, fn, data, NULL);
 +      return do_config_from(&top, fn, data, opts);
  }
  
  int git_config_from_blob_oid(config_fn_t fn,
  
        buf = read_object_file(oid, &type, &size);
        if (!buf)
 -              return error("unable to load config blob object '%s'", name);
 +              return error(_("unable to load config blob object '%s'"), name);
        if (type != OBJ_BLOB) {
                free(buf);
 -              return error("reference '%s' does not point to a blob", name);
 +              return error(_("reference '%s' does not point to a blob"), name);
        }
  
 -      ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size, data);
 +      ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,
 +                                data, NULL);
        free(buf);
  
        return ret;
@@@ -1623,7 -1608,7 +1623,7 @@@ static int git_config_from_blob_ref(con
        struct object_id oid;
  
        if (get_oid(name, &oid) < 0)
 -              return error("unable to resolve config blob '%s'", name);
 +              return error(_("unable to resolve config blob '%s'"), name);
        return git_config_from_blob_oid(fn, name, &oid, data);
  }
  
@@@ -1653,7 -1638,7 +1653,7 @@@ unsigned long git_env_ulong(const char 
  {
        const char *v = getenv(k);
        if (v && !git_parse_ulong(v, &val))
 -              die("failed to parse %s", k);
 +              die(_("failed to parse %s"), k);
        return val;
  }
  
@@@ -2187,6 -2172,23 +2187,6 @@@ int git_config_get_pathname(const char 
        return repo_config_get_pathname(the_repository, key, dest);
  }
  
 -/*
 - * Note: This function exists solely to maintain backward compatibility with
 - * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
 - * NOT be used anywhere else.
 - *
 - * Runs the provided config function on the '.gitmodules' file found in the
 - * working directory.
 - */
 -void config_from_gitmodules(config_fn_t fn, void *data)
 -{
 -      if (the_repository->worktree) {
 -              char *file = repo_worktree_path(the_repository, GITMODULES_FILE);
 -              git_config_from_file(fn, file, data);
 -              free(file);
 -      }
 -}
 -
  int git_config_get_expiry(const char *key, const char **output)
  {
        int ret = git_config_get_string_const(key, output);
@@@ -2370,7 -2372,7 +2370,7 @@@ static int store_aux_event(enum config_
  
        if (type == CONFIG_EVENT_SECTION) {
                if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
 -                      return error("invalid section name '%s'", cf->var.buf);
 +                      return error(_("invalid section name '%s'"), cf->var.buf);
  
                /* Is this the section we were looking for? */
                store->is_keys_section =
@@@ -2426,7 -2428,7 +2426,7 @@@ static int store_aux(const char *key, c
  
  static int write_error(const char *filename)
  {
 -      error("failed to write new configuration file %s", filename);
 +      error(_("failed to write new configuration file %s"), filename);
  
        /* Same error code as "failed to rename". */
        return 4;
@@@ -2677,7 -2679,7 +2677,7 @@@ int git_config_set_multivar_in_file_gen
         */
        fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (fd < 0) {
 -              error_errno("could not lock config file %s", config_filename);
 +              error_errno(_("could not lock config file %s"), config_filename);
                ret = CONFIG_NO_LOCK;
                goto out_free;
        }
        in_fd = open(config_filename, O_RDONLY);
        if ( in_fd < 0 ) {
                if ( ENOENT != errno ) {
 -                      error_errno("opening %s", config_filename);
 +                      error_errno(_("opening %s"), config_filename);
                        ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */
                        goto out_free;
                }
                        store.value_regex = (regex_t*)xmalloc(sizeof(regex_t));
                        if (regcomp(store.value_regex, value_regex,
                                        REG_EXTENDED)) {
 -                              error("invalid pattern: %s", value_regex);
 +                              error(_("invalid pattern: %s"), value_regex);
                                FREE_AND_NULL(store.value_regex);
                                ret = CONFIG_INVALID_PATTERN;
                                goto out_free;
                if (git_config_from_file_with_options(store_aux,
                                                      config_filename,
                                                      &store, &opts)) {
 -                      error("invalid config file %s", config_filename);
 +                      error(_("invalid config file %s"), config_filename);
                        ret = CONFIG_INVALID_FILE;
                        goto out_free;
                }
                if (contents == MAP_FAILED) {
                        if (errno == ENODEV && S_ISDIR(st.st_mode))
                                errno = EISDIR;
 -                      error_errno("unable to mmap '%s'", config_filename);
 +                      error_errno(_("unable to mmap '%s'"), config_filename);
                        ret = CONFIG_INVALID_FILE;
                        contents = NULL;
                        goto out_free;
                in_fd = -1;
  
                if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
 -                      error_errno("chmod on %s failed", get_lock_file_path(&lock));
 +                      error_errno(_("chmod on %s failed"), get_lock_file_path(&lock));
                        ret = CONFIG_NO_WRITE;
                        goto out_free;
                }
        }
  
        if (commit_lock_file(&lock) < 0) {
 -              error_errno("could not write config file %s", config_filename);
 +              error_errno(_("could not write config file %s"), config_filename);
                ret = CONFIG_NO_WRITE;
                goto out_free;
        }
@@@ -2992,7 -2994,7 +2992,7 @@@ static int git_config_copy_or_rename_se
        memset(&store, 0, sizeof(store));
  
        if (new_name && !section_name_is_ok(new_name)) {
 -              ret = error("invalid section name: %s", new_name);
 +              ret = error(_("invalid section name: %s"), new_name);
                goto out_no_rollback;
        }
  
  
        out_fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (out_fd < 0) {
 -              ret = error("could not lock config file %s", config_filename);
 +              ret = error(_("could not lock config file %s"), config_filename);
                goto out;
        }
  
        }
  
        if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
 -              ret = error_errno("chmod on %s failed",
 +              ret = error_errno(_("chmod on %s failed"),
                                  get_lock_file_path(&lock));
                goto out;
        }
        config_file = NULL;
  commit_and_out:
        if (commit_lock_file(&lock) < 0)
 -              ret = error_errno("could not write config file %s",
 +              ret = error_errno(_("could not write config file %s"),
                                  config_filename);
  out:
        if (config_file)
@@@ -3159,7 -3161,7 +3159,7 @@@ int git_config_copy_section(const char 
  #undef config_error_nonbool
  int config_error_nonbool(const char *var)
  {
 -      return error("missing value for '%s'", var);
 +      return error(_("missing value for '%s'"), var);
  }
  
  int parse_config_key(const char *var,
@@@ -3243,16 -3245,3 +3243,16 @@@ enum config_scope current_config_scope(
        else
                return current_parsing_scope;
  }
 +
 +int lookup_config(const char **mapping, int nr_mapping, const char *var)
 +{
 +      int i;
 +
 +      for (i = 0; i < nr_mapping; i++) {
 +              const char *name = mapping[i];
 +
 +              if (name && !strcasecmp(var, name))
 +                      return i;
 +      }
 +      return -1;
 +}
diff --combined config.h
index bb2f506b27137e8d66ab4cd96439143dced94780,7808413bd07a496bf2aed3aca99cfea3e2a42d97..1d56fe6aa28fe1a244118f00733e3829217b71ca
+++ b/config.h
@@@ -54,12 -54,6 +54,12 @@@ struct config_options 
        const char *git_dir;
        config_parser_event_fn_t event_fn;
        void *event_fn_data;
 +      enum config_error_action {
 +              CONFIG_ERROR_UNSET = 0, /* use source-specific default */
 +              CONFIG_ERROR_DIE, /* die() on error */
 +              CONFIG_ERROR_ERROR, /* error() on error, return -1 */
 +              CONFIG_ERROR_SILENT, /* return -1 */
 +      } error_action;
  };
  
  typedef int (*config_fn_t)(const char *, const char *, void *);
@@@ -68,11 -62,8 +68,11 @@@ extern int git_config_from_file(config_
  extern int git_config_from_file_with_options(config_fn_t fn, const char *,
                                             void *,
                                             const struct config_options *);
 -extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
 -                                      const char *name, const char *buf, size_t len, void *data);
 +extern int git_config_from_mem(config_fn_t fn,
 +                             const enum config_origin_type,
 +                             const char *name,
 +                             const char *buf, size_t len,
 +                             void *data, const struct config_options *opts);
  extern int git_config_from_blob_oid(config_fn_t fn, const char *name,
                                    const struct object_id *oid, void *data);
  extern void git_config_push_parameter(const char *text);
@@@ -82,6 -73,7 +82,7 @@@ extern void git_config(config_fn_t fn, 
  extern int config_with_options(config_fn_t fn, void *,
                               struct git_config_source *config_source,
                               const struct config_options *opts);
+ extern int git_parse_ssize_t(const char *, ssize_t *);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_parse_maybe_bool(const char *);
  extern int git_config_int(const char *, const char *);
@@@ -224,6 -216,16 +225,6 @@@ extern int repo_config_get_maybe_bool(s
  extern int repo_config_get_pathname(struct repository *repo,
                                    const char *key, const char **dest);
  
 -/*
 - * Note: This function exists solely to maintain backward compatibility with
 - * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
 - * NOT be used anywhere else.
 - *
 - * Runs the provided config function on the '.gitmodules' file found in the
 - * working directory.
 - */
 -extern void config_from_gitmodules(config_fn_t fn, void *data);
 -
  extern int git_config_get_value(const char *key, const char **value);
  extern const struct string_list *git_config_get_value_multi(const char *key);
  extern void git_config_clear(void);
@@@ -256,8 -258,4 +257,8 @@@ struct key_value_info 
  extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
  extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);
  
 +#define LOOKUP_CONFIG(mapping, var) \
 +      lookup_config(mapping, ARRAY_SIZE(mapping), var)
 +int lookup_config(const char **mapping, int nr_mapping, const char *var);
 +
  #endif /* CONFIG_H */
diff --combined help.c
index 3ebf0568dba1f20039b6e4d02c18c652078ad2cb,e469f5731c3983ae5a20056e41fec42be798e7ad..9c0b5a8de974727e98efc1ad28fd49655290ffd8
--- 1/help.c
--- 2/help.c
+++ b/help.c
@@@ -409,90 -409,6 +409,90 @@@ void list_common_guides_help(void
        putchar('\n');
  }
  
 +struct slot_expansion {
 +      const char *prefix;
 +      const char *placeholder;
 +      void (*fn)(struct string_list *list, const char *prefix);
 +      int found;
 +};
 +
 +void list_config_help(int for_human)
 +{
 +      struct slot_expansion slot_expansions[] = {
 +              { "advice", "*", list_config_advices },
 +              { "color.branch", "<slot>", list_config_color_branch_slots },
 +              { "color.decorate", "<slot>", list_config_color_decorate_slots },
 +              { "color.diff", "<slot>", list_config_color_diff_slots },
 +              { "color.grep", "<slot>", list_config_color_grep_slots },
 +              { "color.interactive", "<slot>", list_config_color_interactive_slots },
 +              { "color.status", "<slot>", list_config_color_status_slots },
 +              { "fsck", "<msg-id>", list_config_fsck_msg_ids },
 +              { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids },
 +              { NULL, NULL, NULL }
 +      };
 +      const char **p;
 +      struct slot_expansion *e;
 +      struct string_list keys = STRING_LIST_INIT_DUP;
 +      int i;
 +
 +      for (p = config_name_list; *p; p++) {
 +              const char *var = *p;
 +              struct strbuf sb = STRBUF_INIT;
 +
 +              for (e = slot_expansions; e->prefix; e++) {
 +
 +                      strbuf_reset(&sb);
 +                      strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder);
 +                      if (!strcasecmp(var, sb.buf)) {
 +                              e->fn(&keys, e->prefix);
 +                              e->found++;
 +                              break;
 +                      }
 +              }
 +              strbuf_release(&sb);
 +              if (!e->prefix)
 +                      string_list_append(&keys, var);
 +      }
 +
 +      for (e = slot_expansions; e->prefix; e++)
 +              if (!e->found)
 +                      BUG("slot_expansion %s.%s is not used",
 +                          e->prefix, e->placeholder);
 +
 +      string_list_sort(&keys);
 +      for (i = 0; i < keys.nr; i++) {
 +              const char *var = keys.items[i].string;
 +              const char *wildcard, *tag, *cut;
 +
 +              if (for_human) {
 +                      puts(var);
 +                      continue;
 +              }
 +
 +              wildcard = strchr(var, '*');
 +              tag = strchr(var, '<');
 +
 +              if (!wildcard && !tag) {
 +                      puts(var);
 +                      continue;
 +              }
 +
 +              if (wildcard && !tag)
 +                      cut = wildcard;
 +              else if (!wildcard && tag)
 +                      cut = tag;
 +              else
 +                      cut = wildcard < tag ? wildcard : tag;
 +
 +              /*
 +               * We may produce duplicates, but that's up to
 +               * git-completion.bash to handle
 +               */
 +              printf("%.*s\n", (int)(cut - var), var);
 +      }
 +      string_list_clear(&keys, 0);
 +}
 +
  void list_all_cmds_help(void)
  {
        print_cmd_by_category(main_categories);
@@@ -693,6 -609,7 +693,7 @@@ int cmd_version(int argc, const char **
                else
                        printf("no commit associated with this build\n");
                printf("sizeof-long: %d\n", (int)sizeof(long));
+               printf("sizeof-size_t: %d\n", (int)sizeof(size_t));
                /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
        }
        return 0;
diff --combined http-backend.c
index bd0442a805a16bf2313b65a54fdfd4bf937b13ad,e88d29f62b1f1f426b1ca75aafc126da96a8a1da..88c38c834ba479447be8eb64f3bc5331cdb9ed49
@@@ -279,12 -279,18 +279,18 @@@ static struct rpc_service *select_servi
        return svc;
  }
  
+ static void write_to_child(int out, const unsigned char *buf, ssize_t len, const char *prog_name)
+ {
+       if (write_in_full(out, buf, len) < 0)
+               die("unable to write to '%s'", prog_name);
+ }
  /*
   * This is basically strbuf_read(), except that if we
   * hit max_request_buffer we die (we'd rather reject a
   * maliciously large request than chew up infinite memory).
   */
- static ssize_t read_request(int fd, unsigned char **out)
+ static ssize_t read_request_eof(int fd, unsigned char **out)
  {
        size_t len = 0, alloc = 8192;
        unsigned char *buf = xmalloc(alloc);
        }
  }
  
- static void inflate_request(const char *prog_name, int out, int buffer_input)
+ static ssize_t read_request_fixed_len(int fd, ssize_t req_len, unsigned char **out)
+ {
+       unsigned char *buf = NULL;
+       ssize_t cnt = 0;
+       if (max_request_buffer < req_len) {
+               die("request was larger than our maximum size (%lu): "
+                   "%" PRIuMAX "; try setting GIT_HTTP_MAX_REQUEST_BUFFER",
+                   max_request_buffer, (uintmax_t)req_len);
+       }
+       buf = xmalloc(req_len);
+       cnt = read_in_full(fd, buf, req_len);
+       if (cnt < 0) {
+               free(buf);
+               return -1;
+       }
+       *out = buf;
+       return cnt;
+ }
+ static ssize_t get_content_length(void)
+ {
+       ssize_t val = -1;
+       const char *str = getenv("CONTENT_LENGTH");
+       if (str && !git_parse_ssize_t(str, &val))
+               die("failed to parse CONTENT_LENGTH: %s", str);
+       return val;
+ }
+ static ssize_t read_request(int fd, unsigned char **out, ssize_t req_len)
+ {
+       if (req_len < 0)
+               return read_request_eof(fd, out);
+       else
+               return read_request_fixed_len(fd, req_len, out);
+ }
+ static void inflate_request(const char *prog_name, int out, int buffer_input, ssize_t req_len)
  {
        git_zstream stream;
        unsigned char *full_request = NULL;
        unsigned char in_buf[8192];
        unsigned char out_buf[8192];
        unsigned long cnt = 0;
+       int req_len_defined = req_len >= 0;
+       size_t req_remaining_len = req_len;
  
        memset(&stream, 0, sizeof(stream));
        git_inflate_init_gzip_only(&stream);
                        if (full_request)
                                n = 0; /* nothing left to read */
                        else
-                               n = read_request(0, &full_request);
+                               n = read_request(0, &full_request, req_len);
                        stream.next_in = full_request;
                } else {
-                       n = xread(0, in_buf, sizeof(in_buf));
+                       ssize_t buffer_len;
+                       if (req_len_defined && req_remaining_len <= sizeof(in_buf))
+                               buffer_len = req_remaining_len;
+                       else
+                               buffer_len = sizeof(in_buf);
+                       n = xread(0, in_buf, buffer_len);
                        stream.next_in = in_buf;
+                       if (req_len_defined && n > 0)
+                               req_remaining_len -= n;
                }
  
                if (n <= 0)
                                die("zlib error inflating request, result %d", ret);
  
                        n = stream.total_out - cnt;
-                       if (write_in_full(out, out_buf, n) < 0)
-                               die("%s aborted reading request", prog_name);
-                       cnt += n;
+                       write_to_child(out, out_buf, stream.total_out - cnt, prog_name);
+                       cnt = stream.total_out;
  
                        if (ret == Z_STREAM_END)
                                goto done;
@@@ -376,18 -429,34 +429,34 @@@ done
        free(full_request);
  }
  
- static void copy_request(const char *prog_name, int out)
+ static void copy_request(const char *prog_name, int out, ssize_t req_len)
  {
        unsigned char *buf;
-       ssize_t n = read_request(0, &buf);
+       ssize_t n = read_request(0, &buf, req_len);
        if (n < 0)
                die_errno("error reading request body");
-       if (write_in_full(out, buf, n) < 0)
-               die("%s aborted reading request", prog_name);
+       write_to_child(out, buf, n, prog_name);
        close(out);
        free(buf);
  }
  
+ static void pipe_fixed_length(const char *prog_name, int out, size_t req_len)
+ {
+       unsigned char buf[8192];
+       size_t remaining_len = req_len;
+       while (remaining_len > 0) {
+               size_t chunk_length = remaining_len > sizeof(buf) ? sizeof(buf) : remaining_len;
+               ssize_t n = xread(0, buf, chunk_length);
+               if (n < 0)
+                       die_errno("Reading request failed");
+               write_to_child(out, buf, n, prog_name);
+               remaining_len -= n;
+       }
+       close(out);
+ }
  static void run_service(const char **argv, int buffer_input)
  {
        const char *encoding = getenv("HTTP_CONTENT_ENCODING");
        const char *host = getenv("REMOTE_ADDR");
        int gzipped_request = 0;
        struct child_process cld = CHILD_PROCESS_INIT;
+       ssize_t req_len = get_content_length();
  
        if (encoding && !strcmp(encoding, "gzip"))
                gzipped_request = 1;
                                 "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
  
        cld.argv = argv;
-       if (buffer_input || gzipped_request)
+       if (buffer_input || gzipped_request || req_len >= 0)
                cld.in = -1;
        cld.git_cmd = 1;
        if (start_command(&cld))
  
        close(1);
        if (gzipped_request)
-               inflate_request(argv[0], cld.in, buffer_input);
+               inflate_request(argv[0], cld.in, buffer_input, req_len);
        else if (buffer_input)
-               copy_request(argv[0], cld.in);
+               copy_request(argv[0], cld.in, req_len);
+       else if (req_len >= 0)
+               pipe_fixed_length(argv[0], cld.in, req_len);
        else
                close(0);
  
@@@ -436,13 -508,13 +508,13 @@@ static int show_text_ref(const char *na
  {
        const char *name_nons = strip_namespace(name);
        struct strbuf *buf = cb_data;
 -      struct object *o = parse_object(oid);
 +      struct object *o = parse_object(the_repository, oid);
        if (!o)
                return 0;
  
        strbuf_addf(buf, "%s\t%s\n", oid_to_hex(oid), name_nons);
        if (o->type == OBJ_TAG) {
 -              o = deref_tag(o, name, 0);
 +              o = deref_tag(the_repository, o, name, 0);
                if (!o)
                        return 0;
                strbuf_addf(buf, "%s\t%s^{}\n", oid_to_hex(&o->oid),