Merge branch 'bp/post-index-change-hook'
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Apr 2019 07:41:10 +0000 (16:41 +0900)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Apr 2019 07:41:11 +0000 (16:41 +0900)
A new hook "post-index-change" is called when the on-disk index
file changes, which can help e.g. a virtualized working tree
implementation.

* bp/post-index-change-hook:
read-cache: add post-index-change hook

1  2 
Documentation/githooks.txt
builtin/reset.c
builtin/update-index.c
cache.h
read-cache.c
unpack-trees.c
index 5bf653c111d07e0958840e38187320a433b20d07,bfb0be36599d84c8296ed53ae2da4f591053c63a..786e778ab8223a0ee02a44c8c756652fcf147205
@@@ -99,10 -99,6 +99,10 @@@ All the `git commit` hooks are invoked 
  variable `GIT_EDITOR=:` if the command will not bring up an editor
  to modify the commit message.
  
 +The default 'pre-commit' hook, when enabled--and with the
 +`hooks.allownonascii` config option unset or set to false--prevents
 +the use of non-ASCII filenames.
 +
  prepare-commit-msg
  ~~~~~~~~~~~~~~~~~~
  
@@@ -496,6 -492,24 +496,24 @@@ This hook is invoked by `git-p4 submit`
  from standard input. Exiting with non-zero status from this script prevent
  `git-p4 submit` from launching. Run `git-p4 submit --help` for details.
  
+ post-index-change
+ ~~~~~~~~~~~~~~~~~
+ This hook is invoked when the index is written in read-cache.c
+ do_write_locked_index.
+ The first parameter passed to the hook is the indicator for the
+ working directory being updated.  "1" meaning working directory
+ was updated or "0" when the working directory was not updated.
+ The second parameter passed to the hook is the indicator for whether
+ or not the index was updated and the skip-worktree bit could have
+ changed.  "1" meaning skip-worktree bits could have been updated
+ and "0" meaning they were not.
+ Only one parameter should be set to "1" when the hook runs.  The hook
+ running passing "1", "1" should not be possible.
  GIT
  ---
  Part of the linkgit:git[1] suite
diff --combined builtin/reset.c
index 7882829a95d8294ea5bb5bcba3335e651233d79a,e173afcaacd541f5c9c68da2d27a937dab86c480..26ef9a7bd03ac8925e1cb350c49e5e327999ba52
@@@ -341,7 -341,6 +341,7 @@@ int cmd_reset(int argc, const char **ar
        if (patch_mode) {
                if (reset_type != NONE)
                        die(_("--patch is incompatible with --{hard,mixed,soft}"));
 +              trace2_cmd_mode("patch-interactive");
                return run_add_interactive(rev, "--patch=reset", &pathspec);
        }
  
        if (reset_type == NONE)
                reset_type = MIXED; /* by default */
  
 +      if (pathspec.nr)
 +              trace2_cmd_mode("path");
 +      else
 +              trace2_cmd_mode(reset_type_names[reset_type]);
 +
        if (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))
                setup_work_tree();
  
                        int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
                        if (read_from_tree(&pathspec, &oid, intent_to_add))
                                return 1;
+                       the_index.updated_skipworktree = 1;
                        if (!quiet && get_git_work_tree()) {
                                uint64_t t_begin, t_delta_in_ms;
  
diff --combined builtin/update-index.c
index 1b6c42f748fd52ece4cf5f109f92a50bc2a87be3,cf731640fa032efbc2723c6dd62de70c9b78e0ee..73fe04e1c2e45a46af6ea561be28e8831498b6d4
@@@ -848,16 -848,14 +848,16 @@@ static int parse_new_style_cacheinfo(co
        return 0;
  }
  
 -static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
 -                              const struct option *opt, int unset)
 +static enum parse_opt_result cacheinfo_callback(
 +      struct parse_opt_ctx_t *ctx, const struct option *opt,
 +      const char *arg, int unset)
  {
        struct object_id oid;
        unsigned int mode;
        const char *path;
  
        BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(arg);
  
        if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) {
                if (add_cacheinfo(mode, &oid, path, 0))
        return 0;
  }
  
 -static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
 -                            const struct option *opt, int unset)
 +static enum parse_opt_result stdin_cacheinfo_callback(
 +      struct parse_opt_ctx_t *ctx, const struct option *opt,
 +      const char *arg, int unset)
  {
        int *nul_term_line = opt->value;
  
        BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(arg);
  
        if (ctx->argc != 1)
                return error("option '%s' must be the last argument", opt->long_name);
        return 0;
  }
  
 -static int stdin_callback(struct parse_opt_ctx_t *ctx,
 -                              const struct option *opt, int unset)
 +static enum parse_opt_result stdin_callback(
 +      struct parse_opt_ctx_t *ctx, const struct option *opt,
 +      const char *arg, int unset)
  {
        int *read_from_stdin = opt->value;
  
        BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(arg);
  
        if (ctx->argc != 1)
                return error("option '%s' must be the last argument", opt->long_name);
        return 0;
  }
  
 -static int unresolve_callback(struct parse_opt_ctx_t *ctx,
 -                              const struct option *opt, int unset)
 +static enum parse_opt_result unresolve_callback(
 +      struct parse_opt_ctx_t *ctx, const struct option *opt,
 +      const char *arg, int unset)
  {
        int *has_errors = opt->value;
        const char *prefix = startup_info->prefix;
  
        BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(arg);
  
        /* consume remaining arguments. */
        *has_errors = do_unresolve(ctx->argc, ctx->argv,
        return 0;
  }
  
 -static int reupdate_callback(struct parse_opt_ctx_t *ctx,
 -                              const struct option *opt, int unset)
 +static enum parse_opt_result reupdate_callback(
 +      struct parse_opt_ctx_t *ctx, const struct option *opt,
 +      const char *arg, int unset)
  {
        int *has_errors = opt->value;
        const char *prefix = startup_info->prefix;
  
        BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(arg);
  
        /* consume remaining arguments. */
        setup_work_tree();
@@@ -996,8 -986,7 +996,8 @@@ int cmd_update_index(int argc, const ch
                        N_("add the specified entry to the index"),
                        PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
                        PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
 -                      (parse_opt_cb *) cacheinfo_callback},
 +                      NULL, 0,
 +                      cacheinfo_callback},
                {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x",
                        N_("override the executable bit of the listed files"),
                        PARSE_OPT_NONEG,
                {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
                        N_("read list of paths to be updated from standard input"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 -                      (parse_opt_cb *) stdin_callback},
 +                      NULL, 0, stdin_callback},
                {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL,
                        N_("add entries from standard input to the index"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 -                      (parse_opt_cb *) stdin_cacheinfo_callback},
 +                      NULL, 0, stdin_cacheinfo_callback},
                {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
                        N_("repopulate stages #2 and #3 for the listed paths"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 -                      (parse_opt_cb *) unresolve_callback},
 +                      NULL, 0, unresolve_callback},
                {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
                        N_("only update entries that differ from HEAD"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 -                      (parse_opt_cb *) reupdate_callback},
 +                      NULL, 0, reupdate_callback},
                OPT_BIT(0, "ignore-missing", &refresh_args.flags,
                        N_("ignore files missing from worktree"),
                        REFRESH_IGNORE_MISSING),
        if (entries < 0)
                die("cache corrupted");
  
+       the_index.updated_skipworktree = 1;
        /*
         * Custom copy of parse_options() because we want to handle
         * filename arguments as they come.
diff --combined cache.h
index e3c2ab962822e784b934a2342584ca42a16a9439,46eb862d3e844137b8d2d25bbf049597cacf8f6d..e928fe9d3bd90b3ac2ca69bd31d281784287d536
+++ b/cache.h
@@@ -9,7 -9,6 +9,7 @@@
  #include "gettext.h"
  #include "convert.h"
  #include "trace.h"
 +#include "trace2.h"
  #include "string-list.h"
  #include "pack-revindex.h"
  #include "hash.h"
@@@ -339,7 -338,9 +339,9 @@@ struct index_state 
        struct cache_time timestamp;
        unsigned name_hash_initialized : 1,
                 initialized : 1,
-                drop_cache_tree : 1;
+                drop_cache_tree : 1,
+                updated_workdir : 1,
+                updated_skipworktree : 1;
        struct hashmap name_hash;
        struct hashmap dir_hash;
        struct object_id oid;
@@@ -759,7 -760,7 +761,7 @@@ extern void rename_index_entry_at(struc
  /* Remove entry, return true if there are more entries to go. */
  extern int remove_index_entry_at(struct index_state *, int pos);
  
 -extern void remove_marked_cache_entries(struct index_state *istate);
 +extern void remove_marked_cache_entries(struct index_state *istate, int invalidate);
  extern int remove_file_from_index(struct index_state *, const char *path);
  #define ADD_CACHE_VERBOSE 1
  #define ADD_CACHE_PRETEND 2
@@@ -962,10 -963,6 +964,10 @@@ extern char *repository_format_partial_
  extern const char *core_partial_clone_filter_default;
  extern int repository_format_worktree_config;
  
 +/*
 + * You _have_ to initialize a `struct repository_format` using
 + * `= REPOSITORY_FORMAT_INIT` before calling `read_repository_format()`.
 + */
  struct repository_format {
        int version;
        int precious_objects;
        struct string_list unknown_extensions;
  };
  
 +/*
 + * Always use this to initialize a `struct repository_format`
 + * to a well-defined, default state before calling
 + * `read_repository()`.
 + */
 +#define REPOSITORY_FORMAT_INIT \
 +{ \
 +      .version = -1, \
 +      .is_bare = -1, \
 +      .hash_algo = GIT_HASH_SHA1, \
 +      .unknown_extensions = STRING_LIST_INIT_DUP, \
 +}
 +
  /*
   * 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.
 + * "format" struct. Returns the numeric version. On error, or if no version is
 + * found in the configuration, -1 is returned, format->version is set to -1,
 + * and all other fields in the struct are set to the default configuration
 + * (REPOSITORY_FORMAT_INIT). Always initialize the struct using
 + * REPOSITORY_FORMAT_INIT before calling this function.
   */
  int read_repository_format(struct repository_format *format, const char *path);
  
 +/*
 + * Free the memory held onto by `format`, but not the struct itself.
 + * (No need to use this after `read_repository_format()` fails.)
 + */
 +void clear_repository_format(struct repository_format *format);
 +
  /*
   * 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
@@@ -1379,7 -1355,6 +1381,7 @@@ enum get_oid_result 
  };
  
  extern int get_oid(const char *str, struct object_id *oid);
 +extern int get_oidf(struct object_id *oid, const char *fmt, ...);
  extern int get_oid_commit(const char *str, struct object_id *oid);
  extern int get_oid_committish(const char *str, struct object_id *oid);
  extern int get_oid_tree(const char *str, struct object_id *oid);
@@@ -1533,19 -1508,10 +1535,19 @@@ int date_overflows(timestamp_t date)
  #define IDENT_STRICT         1
  #define IDENT_NO_DATE        2
  #define IDENT_NO_NAME        4
 +
 +enum want_ident {
 +      WANT_BLANK_IDENT,
 +      WANT_AUTHOR_IDENT,
 +      WANT_COMMITTER_IDENT
 +};
 +
  extern const char *git_author_info(int);
  extern const char *git_committer_info(int);
 -extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
 -extern const char *fmt_name(const char *name, const char *email);
 +extern const char *fmt_ident(const char *name, const char *email,
 +              enum want_ident whose_ident,
 +              const char *date_str, int);
 +extern const char *fmt_name(enum want_ident);
  extern const char *ident_default_name(void);
  extern const char *ident_default_email(void);
  extern const char *git_editor(void);
@@@ -1553,10 -1519,6 +1555,10 @@@ extern const char *git_sequence_editor(
  extern const char *git_pager(int stdout_is_tty);
  extern int is_terminal_dumb(void);
  extern int git_ident_config(const char *, const char *, void *);
 +/*
 + * Prepare an ident to fall back on if the user didn't configure it.
 + */
 +void prepare_fallback_ident(const char *name, const char *email);
  extern void reset_ident_date(void);
  
  struct ident_split {
@@@ -1609,11 -1571,6 +1611,11 @@@ struct checkout 
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);
  extern void enable_delayed_checkout(struct checkout *state);
  extern int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
 +/*
 + * Unlink the last component and schedule the leading directories for
 + * removal, such that empty directories get removed.
 + */
 +extern void unlink_entry(const struct cache_entry *ce);
  
  struct cache_def {
        struct strbuf path;
diff --combined read-cache.c
index 4dc6de1b55b0a6047e49f188b28c431f0167d7ef,862bdf383dba781908b32efe57745d4a763e7297..c62eae651df112b94a8a17ae80e399687a570572
@@@ -17,6 -17,7 +17,7 @@@
  #include "commit.h"
  #include "blob.h"
  #include "resolve-undo.h"
+ #include "run-command.h"
  #include "strbuf.h"
  #include "varint.h"
  #include "split-index.h"
@@@ -588,19 -589,13 +589,19 @@@ int remove_index_entry_at(struct index_
   * CE_REMOVE is set in ce_flags.  This is much more effective than
   * calling remove_index_entry_at() for each entry to be removed.
   */
 -void remove_marked_cache_entries(struct index_state *istate)
 +void remove_marked_cache_entries(struct index_state *istate, int invalidate)
  {
        struct cache_entry **ce_array = istate->cache;
        unsigned int i, j;
  
        for (i = j = 0; i < istate->cache_nr; i++) {
                if (ce_array[i]->ce_flags & CE_REMOVE) {
 +                      if (invalidate) {
 +                              cache_tree_invalidate_path(istate,
 +                                                         ce_array[i]->name);
 +                              untracked_cache_remove_from_index(istate,
 +                                                                ce_array[i]->name);
 +                      }
                        remove_name_hash(istate, ce_array[i]);
                        save_or_free_index_entry(istate, ce_array[i]);
                }
@@@ -2226,16 -2221,6 +2227,16 @@@ int do_read_index(struct index_state *i
                load_index_extensions(&p);
        }
        munmap((void *)mmap, mmap_size);
 +
 +      /*
 +       * TODO trace2: replace "the_repository" with the actual repo instance
 +       * that is associated with the given "istate".
 +       */
 +      trace2_data_intmax("index", the_repository, "read/version",
 +                         istate->version);
 +      trace2_data_intmax("index", the_repository, "read/cache_nr",
 +                         istate->cache_nr);
 +
        return istate->cache_nr;
  
  unmap:
@@@ -2267,17 -2252,9 +2268,17 @@@ int read_index_from(struct index_state 
        if (istate->initialized)
                return istate->cache_nr;
  
 +      /*
 +       * TODO trace2: replace "the_repository" with the actual repo instance
 +       * that is associated with the given "istate".
 +       */
 +      trace2_region_enter_printf("index", "do_read_index", the_repository,
 +                                 "%s", path);
        trace_performance_enter();
        ret = do_read_index(istate, path, 0);
        trace_performance_leave("read cache %s", path);
 +      trace2_region_leave_printf("index", "do_read_index", the_repository,
 +                                 "%s", path);
  
        split_index = istate->split_index;
        if (!split_index || is_null_oid(&split_index->base_oid)) {
  
        base_oid_hex = oid_to_hex(&split_index->base_oid);
        base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
 +      trace2_region_enter_printf("index", "shared/do_read_index",
 +                                 the_repository, "%s", base_path);
        ret = do_read_index(split_index->base, base_path, 1);
 +      trace2_region_leave_printf("index", "shared/do_read_index",
 +                                 the_repository, "%s", base_path);
        if (!oideq(&split_index->base_oid, &split_index->base->oid))
                die(_("broken index, expect %s in %s, got %s"),
                    base_oid_hex, base_path,
@@@ -2922,8 -2895,7 +2923,8 @@@ static int do_write_index(struct index_
                        return -1;
        }
  
 -      if (!strip_extensions && istate->split_index) {
 +      if (!strip_extensions && istate->split_index &&
 +          !is_null_oid(&istate->split_index->base_oid)) {
                struct strbuf sb = STRBUF_INIT;
  
                err = write_link_extension(&sb, istate) < 0 ||
        istate->timestamp.sec = (unsigned int)st.st_mtime;
        istate->timestamp.nsec = ST_MTIME_NSEC(st);
        trace_performance_since(start, "write index, changed mask = %x", istate->cache_changed);
 +
 +      /*
 +       * TODO trace2: replace "the_repository" with the actual repo instance
 +       * that is associated with the given "istate".
 +       */
 +      trace2_data_intmax("index", the_repository, "write/version",
 +                         istate->version);
 +      trace2_data_intmax("index", the_repository, "write/cache_nr",
 +                         istate->cache_nr);
 +
        return 0;
  }
  
@@@ -3034,23 -2996,21 +3035,32 @@@ static int commit_locked_index(struct l
  static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
                                 unsigned flags)
  {
 -      int ret = do_write_index(istate, lock->tempfile, 0);
 +      int ret;
 +
 +      /*
 +       * TODO trace2: replace "the_repository" with the actual repo instance
 +       * that is associated with the given "istate".
 +       */
 +      trace2_region_enter_printf("index", "do_write_index", the_repository,
 +                                 "%s", lock->tempfile->filename.buf);
 +      ret = do_write_index(istate, lock->tempfile, 0);
 +      trace2_region_leave_printf("index", "do_write_index", the_repository,
 +                                 "%s", lock->tempfile->filename.buf);
 +
        if (ret)
                return ret;
        if (flags & COMMIT_LOCK)
-               return commit_locked_index(lock);
-       return close_lock_file_gently(lock);
+               ret = commit_locked_index(lock);
+       else
+               ret = close_lock_file_gently(lock);
+       run_hook_le(NULL, "post-index-change",
+                       istate->updated_workdir ? "1" : "0",
+                       istate->updated_skipworktree ? "1" : "0", NULL);
+       istate->updated_workdir = 0;
+       istate->updated_skipworktree = 0;
+       return ret;
  }
  
  static int write_split_index(struct index_state *istate,
@@@ -3130,13 -3090,7 +3140,13 @@@ static int write_shared_index(struct in
        int ret;
  
        move_cache_to_base_index(istate);
 +
 +      trace2_region_enter_printf("index", "shared/do_write_index",
 +                                 the_repository, "%s", (*temp)->filename.buf);
        ret = do_write_index(si->base, *temp, 1);
 +      trace2_region_enter_printf("index", "shared/do_write_index",
 +                                 the_repository, "%s", (*temp)->filename.buf);
 +
        if (ret)
                return ret;
        ret = adjust_shared_perm(get_tempfile_path(*temp));
@@@ -3245,7 -3199,7 +3255,7 @@@ int write_locked_index(struct index_sta
        ret = write_split_index(istate, lock, flags);
  
        /* Freshen the shared index only if the split-index was written */
 -      if (!ret && !new_shared_index) {
 +      if (!ret && !new_shared_index && !is_null_oid(&si->base_oid)) {
                const char *shared_index = git_path("sharedindex.%s",
                                                    oid_to_hex(&si->base_oid));
                freshen_shared_index(shared_index, 1);
diff --combined unpack-trees.c
index 1ccd343cad92dfad3e66deb304811eaf7af9b4fc,8665a4a7c0baa68ff60a81467a412445546f8bef..c5ec30f25f40377b525094c8eba224b9eb131c73
@@@ -299,6 -299,25 +299,6 @@@ static void load_gitmodules_file(struc
        }
  }
  
 -/*
 - * Unlink the last component and schedule the leading directories for
 - * removal, such that empty directories get removed.
 - */
 -static void unlink_entry(const struct cache_entry *ce)
 -{
 -      const struct submodule *sub = submodule_from_ce(ce);
 -      if (sub) {
 -              /* state.force is set at the caller. */
 -              submodule_move_head(ce->name, "HEAD", NULL,
 -                                  SUBMODULE_MOVE_HEAD_FORCE);
 -      }
 -      if (!check_leading_path(ce->name, ce_namelen(ce)))
 -              return;
 -      if (remove_or_warn(ce->ce_mode, ce->name))
 -              return;
 -      schedule_dir_for_removal(ce->name, ce_namelen(ce));
 -}
 -
  static struct progress *get_progress(struct unpack_trees_options *o)
  {
        unsigned cnt = 0, total = 0;
@@@ -391,7 -410,7 +391,7 @@@ static int check_updates(struct unpack_
                                unlink_entry(ce);
                }
        }
 -      remove_marked_cache_entries(index);
 +      remove_marked_cache_entries(index, 0);
        remove_scheduled_dirs();
  
        if (should_update_submodules() && o->update && !o->dry_run)
@@@ -1618,6 -1637,8 +1618,8 @@@ int unpack_trees(unsigned len, struct t
                                                  WRITE_TREE_SILENT |
                                                  WRITE_TREE_REPAIR);
                }
+               o->result.updated_workdir = 1;
                discard_index(o->dst_index);
                *o->dst_index = o->result;
        } else {
@@@ -2386,7 -2407,7 +2388,7 @@@ int oneway_merge(const struct cache_ent
                if (o->update && S_ISGITLINK(old->ce_mode) &&
                    should_update_submodules() && !verify_uptodate(old, o))
                        update |= CE_UPDATE;
 -              add_entry(o, old, update, 0);
 +              add_entry(o, old, update, CE_STAGEMASK);
                return 0;
        }
        return merged_entry(a, old, o);