Merge branch 'ab/hooks'
authorJunio C Hamano <gitster@pobox.com>
Tue, 17 May 2016 21:38:17 +0000 (14:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 17 May 2016 21:38:17 +0000 (14:38 -0700)
A new configuration variable core.hooksPath allows customizing
where the hook directory is.

* ab/hooks:
hooks: allow customizing where the hook directory is
githooks.txt: minor improvements to the grammar & phrasing
githooks.txt: amend dangerous advice about 'update' hook ACL
githooks.txt: improve the intro section

1  2 
Documentation/config.txt
cache.h
config.c
environment.c
run-command.c
diff --combined Documentation/config.txt
index ece0acdbab468d2ba968abcf2940211dda15493f,7f8c119b2fe329227268ab841690268cad8bfa8e..6e97e1e05e4912b5d0b80dc64f3fd683c41e7614
@@@ -618,6 -618,23 +618,23 @@@ core.attributesFile:
        $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME is either not
        set or empty, $HOME/.config/git/attributes is used instead.
  
+ core.hooksPath::
+       By default Git will look for your hooks in the
+       '$GIT_DIR/hooks' directory. Set this to different path,
+       e.g. '/etc/git/hooks', and Git will try to find your hooks in
+       that directory, e.g. '/etc/git/hooks/pre-receive' instead of
+       in '$GIT_DIR/hooks/pre-receive'.
+ +
+ The path can be either absolute or relative. A relative path is
+ taken as relative to the directory where the hooks are run (see
+ the "DESCRIPTION" section of linkgit:githooks[5]).
+ +
+ This configuration variable is useful in cases where you'd like to
+ centrally configure your Git hooks instead of configuring them on a
+ per-repository basis, or as a more flexible and centralized
+ alternative to having an `init.templateDir` where you've changed
+ default hooks.
  core.editor::
        Commands such as `commit` and `tag` that lets you edit
        messages by launching an editor uses the value of this
@@@ -1113,9 -1130,8 +1130,9 @@@ commit.template:
  credential.helper::
        Specify an external helper to be called when a username or
        password credential is needed; the helper may consult external
 -      storage to avoid prompting the user for the credentials. See
 -      linkgit:gitcredentials[7] for details.
 +      storage to avoid prompting the user for the credentials. Note
 +      that multiple helpers may be defined. See linkgit:gitcredentials[7]
 +      for details.
  
  credential.useHttpPath::
        When acquiring credentials, consider the "path" component of an http
@@@ -1655,12 -1671,6 +1672,12 @@@ http.emptyAuth:
        a username in the URL, as libcurl normally requires a username for
        authentication.
  
 +http.extraHeader::
 +      Pass an additional HTTP header when communicating with a server.  If
 +      more than one such entry exists, all of them are added as extra
 +      headers.  To allow overriding the settings inherited from the system
 +      config, an empty value will reset the extra headers to the empty list.
 +
  http.cookieFile::
        File containing previously stored cookie lines which should be used
        in the Git http session, if they match the server. The file format
@@@ -1893,14 -1903,6 +1910,14 @@@ interactive.singleKey:
        setting is silently ignored if portable keystroke input
        is not available; requires the Perl module Term::ReadKey.
  
 +interactive.diffFilter::
 +      When an interactive command (such as `git add --patch`) shows
 +      a colorized diff, git will pipe the diff through the shell
 +      command defined by this configuration variable. The command may
 +      mark up the diff further for human consumption, provided that it
 +      retains a one-to-one correspondence with the lines in the
 +      original diff. Defaults to disabled (no filtering).
 +
  log.abbrevCommit::
        If true, makes linkgit:git-log[1], linkgit:git-show[1], and
        linkgit:git-whatchanged[1] assume `--abbrev-commit`. You may
@@@ -2162,11 -2164,8 +2179,11 @@@ pack.packSizeLimit:
        The maximum size of a pack.  This setting only affects
        packing to a file when repacking, i.e. the git:// protocol
        is unaffected.  It can be overridden by the `--max-pack-size`
 -      option of linkgit:git-repack[1]. The minimum size allowed is
 -      limited to 1 MiB. The default is unlimited.
 +      option of linkgit:git-repack[1].  Reaching this limit results
 +      in the creation of multiple packfiles; which in turn prevents
 +      bitmaps from being created.
 +      The minimum size allowed is limited to 1 MiB.
 +      The default is unlimited.
        Common unit suffixes of 'k', 'm', or 'g' are
        supported.
  
@@@ -2566,9 -2565,8 +2583,9 @@@ repack.writeBitmaps:
        objects to disk (e.g., when `git repack -a` is run).  This
        index can speed up the "counting objects" phase of subsequent
        packs created for clones and fetches, at the cost of some disk
 -      space and extra time spent on the initial repack.  Defaults to
 -      false.
 +      space and extra time spent on the initial repack.  This has
 +      no effect if multiple packfiles are created.
 +      Defaults to false.
  
  rerere.autoUpdate::
        When set to true, `git-rerere` updates the index with the
@@@ -2748,17 -2746,6 +2765,17 @@@ submodule.<name>.ignore:
        "--ignore-submodules" option. The 'git submodule' commands are not
        affected by this setting.
  
 +submodule.fetchJobs::
 +      Specifies how many submodules are fetched/cloned at the same time.
 +      A positive integer allows up to that number of submodules fetched
 +      in parallel. A value of 0 will give some reasonable default.
 +      If unset, it defaults to 1.
 +
 +tag.forceSignAnnotated::
 +      A boolean to specify whether annotated tags created should be GPG signed.
 +      If `--annotate` is specified on the command line, it takes
 +      precedence over this option.
 +
  tag.sort::
        This variable controls the sort ordering of tags when displayed by
        linkgit:git-tag[1]. Without the "--sort=<value>" option provided, the
diff --combined cache.h
index 160f8e32e31a4ebd832f96fcdc55a6063aa30dab,a7529b0233f5ce0647999e106ea9dbc58396039d..eddf3e8b0c3234957e63a67308fef71582c7750a
+++ b/cache.h
@@@ -651,9 -651,11 +651,10 @@@ extern int prefer_symlink_refs
  extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
  extern int warn_on_object_refname_ambiguity;
 -extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
  extern const char *git_attributes_file;
+ extern const char *git_hooks_path;
  extern int zlib_compression_level;
  extern int core_compression_level;
  extern int core_compression_seen;
@@@ -663,9 -665,6 +664,9 @@@ extern size_t delta_base_cache_limit
  extern unsigned long big_file_threshold;
  extern unsigned long pack_size_limit_cfg;
  
 +void set_shared_repository(int value);
 +int get_shared_repository(void);
 +
  /*
   * Do replace refs need to be checked this run?  This variable is
   * initialized to true unless --no-replace-object is used or
@@@ -747,39 -746,9 +748,39 @@@ extern int grafts_replace_parents
   */
  #define GIT_REPO_VERSION 0
  #define GIT_REPO_VERSION_READ 1
 -extern int repository_format_version;
  extern int repository_format_precious_objects;
 -extern int check_repository_format(void);
 +
 +struct repository_format {
 +      int version;
 +      int precious_objects;
 +      int is_bare;
 +      char *work_tree;
 +      struct string_list unknown_extensions;
 +};
 +
 +/*
 + * Read the repository format characteristics from the config file "path" into
 + * "format" struct. Returns the numeric version. On error, -1 is returned,
 + * format->version is set to -1, and all other fields in the struct are
 + * undefined.
 + */
 +int read_repository_format(struct repository_format *format, const char *path);
 +
 +/*
 + * Verify that the repository described by repository_format is something we
 + * can read. If it is, return 0. Otherwise, return -1, and "err" will describe
 + * any errors encountered.
 + */
 +int verify_repository_format(const struct repository_format *format,
 +                           struct strbuf *err);
 +
 +/*
 + * Check the repository format version in the path found in get_git_dir(),
 + * and die if it is a version we don't understand. Generally one would
 + * set_git_dir() before calling this, and use it only for "are we in a valid
 + * repo?".
 + */
 +extern void check_repository_format(void);
  
  #define MTIME_CHANGED 0x0001
  #define CTIME_CHANGED 0x0002
@@@ -958,6 -927,8 +959,6 @@@ static inline int is_empty_blob_sha1(co
  
  int git_mkstemp(char *path, size_t n, const char *template);
  
 -int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
 -
  /* set default permissions by passing mode arguments to open(2) */
  int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
  int git_mkstemp_mode(char *pattern, int mode);
@@@ -1154,8 -1125,6 +1155,8 @@@ extern int get_sha1_blob(const char *st
  extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
  extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc);
  
 +extern int get_oid(const char *str, struct object_id *oid);
 +
  typedef int each_abbrev_fn(const unsigned char *sha1, void *);
  extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
  
@@@ -1558,6 -1527,7 +1559,6 @@@ extern void git_config(config_fn_t fn, 
  extern int git_config_with_options(config_fn_t fn, void *,
                                   struct git_config_source *config_source,
                                   int respect_includes);
 -extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  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 *);
@@@ -1581,6 -1551,7 +1582,6 @@@ extern void git_config_set_multivar_in_
  extern int git_config_rename_section(const char *, const char *);
  extern int git_config_rename_section_in_file(const char *, const char *, const char *);
  extern const char *git_etc_gitconfig(void);
 -extern int check_repository_format_version(const char *var, const char *value, void *cb);
  extern int git_env_bool(const char *, int);
  extern unsigned long git_env_ulong(const char *, unsigned long);
  extern int git_config_system(void);
@@@ -1766,8 -1737,8 +1767,8 @@@ int add_files_to_cache(const char *pref
  extern int diff_auto_refresh_index;
  
  /* match-trees.c */
 -void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
 -void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *);
 +void shift_tree(const struct object_id *, const struct object_id *, struct object_id *, int);
 +void shift_tree_by(const struct object_id *, const struct object_id *, struct object_id *, const char *);
  
  /*
   * whitespace rules.
@@@ -1801,7 -1772,7 +1802,7 @@@ int split_cmdline(char *cmdline, const 
  /* Takes a negative value returned by split_cmdline */
  const char *split_cmdline_strerror(int cmdline_errno);
  
 -/* git.c */
 +/* setup.c */
  struct startup_info {
        int have_repository;
        const char *prefix;
diff --combined config.c
index 262d8d74896819ec90fc017e5e12a68c1b48b2f4,9ef5fde785317c6292a5bb1b23f6b8dc819aafb1..13fb5087772f389f1724a47a077eb3237367fbf5
+++ b/config.c
@@@ -108,7 -108,7 +108,7 @@@ static int handle_path_include(const ch
  
        expanded = expand_user_path(path);
        if (!expanded)
 -              return error("Could not expand include path '%s'", path);
 +              return error("could not expand include path '%s'", path);
        path = expanded;
  
        /*
@@@ -162,7 -162,7 +162,7 @@@ void git_config_push_parameter(const ch
  {
        struct strbuf env = STRBUF_INIT;
        const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
 -      if (old) {
 +      if (old && *old) {
                strbuf_addstr(&env, old);
                strbuf_addch(&env, ' ');
        }
@@@ -717,6 -717,9 +717,9 @@@ static int git_default_core_config(cons
        if (!strcmp(var, "core.attributesfile"))
                return git_config_pathname(&git_attributes_file, var, value);
  
+       if (!strcmp(var, "core.hookspath"))
+               return git_config_pathname(&git_hooks_path, var, value);
        if (!strcmp(var, "core.bare")) {
                is_bare_repository_cfg = git_config_bool(var, value);
                return 0;
@@@ -950,7 -953,7 +953,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;
        }
  
@@@ -976,7 -979,7 +979,7 @@@ 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);
 +                      error("malformed value for %s: %s", var, value);
                        return error("Must be one of nothing, matching, simple, "
                                     "upstream or current.");
                }
@@@ -1188,12 -1191,11 +1191,12 @@@ int git_config_system(void
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
  }
  
 -int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 +static int do_git_config_sequence(config_fn_t fn, void *data)
  {
        int ret = 0, found = 0;
        char *xdg_config = xdg_config_home("config");
        char *user_config = expand_user_path("~/.gitconfig");
 +      char *repo_config = git_pathdup("config");
  
        if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
  
        free(xdg_config);
        free(user_config);
 +      free(repo_config);
        return ret == 0 ? found : ret;
  }
  
@@@ -1237,6 -1238,8 +1240,6 @@@ int git_config_with_options(config_fn_
                            struct git_config_source *config_source,
                            int respect_includes)
  {
 -      char *repo_config = NULL;
 -      int ret;
        struct config_include_data inc = CONFIG_INCLUDE_INIT;
  
        if (respect_includes) {
        else if (config_source && config_source->blob)
                return git_config_from_blob_ref(fn, config_source->blob, data);
  
 -      repo_config = git_pathdup("config");
 -      ret = git_config_early(fn, data, repo_config);
 -      if (repo_config)
 -              free(repo_config);
 -      return ret;
 +      return do_git_config_sequence(fn, data);
  }
  
  static void git_config_raw(config_fn_t fn, void *data)
@@@ -1309,11 -1316,14 +1312,11 @@@ static struct config_set_element *confi
        struct config_set_element k;
        struct config_set_element *found_entry;
        char *normalized_key;
 -      int ret;
        /*
         * `key` may come from the user, so normalize it before using it
         * for querying entries from the hashmap.
         */
 -      ret = git_config_parse_key(key, &normalized_key, NULL);
 -
 -      if (ret)
 +      if (git_config_parse_key(key, &normalized_key, NULL))
                return NULL;
  
        hashmap_entry_init(&k, strhash(normalized_key));
@@@ -2214,13 -2224,9 +2217,13 @@@ void git_config_set_multivar_in_file(co
                                     const char *key, const char *value,
                                     const char *value_regex, int multi_replace)
  {
 -      if (git_config_set_multivar_in_file_gently(config_filename, key, value,
 -                                                 value_regex, multi_replace) < 0)
 -              die(_("Could not set '%s' to '%s'"), key, value);
 +      if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
 +                                                  value_regex, multi_replace))
 +              return;
 +      if (value)
 +              die(_("could not set '%s' to '%s'"), key, value);
 +      else
 +              die(_("could not unset '%s'"), key);
  }
  
  int git_config_set_multivar_gently(const char *key, const char *value,
@@@ -2401,7 -2407,7 +2404,7 @@@ int git_config_rename_section(const cha
  #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,
diff --combined environment.c
index 57acb2fe2aee79a30c4c956dcececd6a7122aeb6,a14d1150238255e1de0bc4fe287627b91d0de620..2857e3f3662ef3b3ce6e062d0413e995179598ba
@@@ -25,12 -25,15 +25,13 @@@ int log_all_ref_updates = -1; /* unspec
  int warn_ambiguous_refs = 1;
  int warn_on_object_refname_ambiguity = 1;
  int ref_paranoia = -1;
 -int repository_format_version;
  int repository_format_precious_objects;
  const char *git_commit_encoding;
  const char *git_log_output_encoding;
 -int shared_repository = PERM_UMASK;
  const char *apply_default_whitespace;
  const char *apply_default_ignorewhitespace;
  const char *git_attributes_file;
+ const char *git_hooks_path;
  int zlib_compression_level = Z_BEST_SPEED;
  int core_compression_level;
  int core_compression_seen;
@@@ -62,6 -65,7 +63,6 @@@ int grafts_replace_parents = 1
  int core_apply_sparse_checkout;
  int merge_log_config = -1;
  int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
 -struct startup_info *startup_info;
  unsigned long pack_size_limit_cfg;
  
  #ifndef PROTECT_HFS_DEFAULT
@@@ -322,24 -326,3 +323,24 @@@ const char *get_commit_output_encoding(
  {
        return git_commit_encoding ? git_commit_encoding : "UTF-8";
  }
 +
 +static int the_shared_repository = PERM_UMASK;
 +static int need_shared_repository_from_config = 1;
 +
 +void set_shared_repository(int value)
 +{
 +      the_shared_repository = value;
 +      need_shared_repository_from_config = 0;
 +}
 +
 +int get_shared_repository(void)
 +{
 +      if (need_shared_repository_from_config) {
 +              const char *var = "core.sharedrepository";
 +              const char *value;
 +              if (!git_config_get_value(var, &value))
 +                      the_shared_repository = git_config_perm(var, value);
 +              need_shared_repository_from_config = 0;
 +      }
 +      return the_shared_repository;
 +}
diff --combined run-command.c
index e4593cd99b13fdbfec02939526ac151270aa20f5,aa85cd5c3ccd343df4727217089ec3c6055735cc..f5c57a5fc77d53d5fba7a494113bb999455fac60
@@@ -590,16 -590,6 +590,16 @@@ static void *run_thread(void *data
        struct async *async = data;
        intptr_t ret;
  
 +      if (async->isolate_sigpipe) {
 +              sigset_t mask;
 +              sigemptyset(&mask);
 +              sigaddset(&mask, SIGPIPE);
 +              if (pthread_sigmask(SIG_BLOCK, &mask, NULL) < 0) {
 +                      ret = error("unable to block SIGPIPE in async thread");
 +                      return (void *)ret;
 +              }
 +      }
 +
        pthread_setspecific(async_key, async);
        ret = async->proc(async->proc_in, async->proc_out, async->data);
        return (void *)ret;
@@@ -825,7 -815,10 +825,10 @@@ const char *find_hook(const char *name
        static struct strbuf path = STRBUF_INIT;
  
        strbuf_reset(&path);
-       strbuf_git_path(&path, "hooks/%s", name);
+       if (git_hooks_path)
+               strbuf_addf(&path, "%s/%s", git_hooks_path, name);
+       else
+               strbuf_git_path(&path, "hooks/%s", name);
        if (access(path.buf, X_OK) < 0)
                return NULL;
        return path.buf;
@@@ -912,7 -905,7 +915,7 @@@ struct parallel_processes 
        struct strbuf buffered_output; /* of finished children */
  };
  
 -static int default_start_failure(struct strbuf *err,
 +static int default_start_failure(struct strbuf *out,
                                 void *pp_cb,
                                 void *pp_task_cb)
  {
  }
  
  static int default_task_finished(int result,
 -                               struct strbuf *err,
 +                               struct strbuf *out,
                                 void *pp_cb,
                                 void *pp_task_cb)
  {
@@@ -1004,7 -997,7 +1007,7 @@@ static void pp_cleanup(struct parallel_
         * When get_next_task added messages to the buffer in its last
         * iteration, the buffered output is non empty.
         */
 -      fputs(pp->buffered_output.buf, stderr);
 +      strbuf_write(&pp->buffered_output, stderr);
        strbuf_release(&pp->buffered_output);
  
        sigchain_pop_common();
@@@ -1089,7 -1082,7 +1092,7 @@@ static void pp_output(struct parallel_p
        int i = pp->output_owner;
        if (pp->children[i].state == GIT_CP_WORKING &&
            pp->children[i].err.len) {
 -              fputs(pp->children[i].err.buf, stderr);
 +              strbuf_write(&pp->children[i].err, stderr);
                strbuf_reset(&pp->children[i].err);
        }
  }
@@@ -1127,11 -1120,11 +1130,11 @@@ static int pp_collect_finished(struct p
                        strbuf_addbuf(&pp->buffered_output, &pp->children[i].err);
                        strbuf_reset(&pp->children[i].err);
                } else {
 -                      fputs(pp->children[i].err.buf, stderr);
 +                      strbuf_write(&pp->children[i].err, stderr);
                        strbuf_reset(&pp->children[i].err);
  
                        /* Output all other finished child processes */
 -                      fputs(pp->buffered_output.buf, stderr);
 +                      strbuf_write(&pp->buffered_output, stderr);
                        strbuf_reset(&pp->buffered_output);
  
                        /*