about summary refs log tree commit
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2020-03-26 17:11:20 -0700
committerJunio C Hamano <gitster@pobox.com>2020-03-26 17:11:20 -0700
commit4e4baee3f44da26a5eaab27c76d597b04fef5259 (patch)
tree5d0ebb218dddd9fc17531ac75d140bf59eb056da
parentfa82be982dfc5b463a125991a2d381f1cd0ad9eb (diff)
parent0c0f8a7f28672fa323312fbba394e5a9949b2aad (diff)
downloadgit-4e4baee3f44da26a5eaab27c76d597b04fef5259.tar.gz
Provide more information (e.g. the object of the tree-ish in which
the blob being converted appears, in addition to its path, which
has already been given) to smudge/clean conversion filters.

* bc/filter-process:
  t0021: test filter metadata for additional cases
  builtin/reset: compute checkout metadata for reset
  builtin/rebase: compute checkout metadata for rebases
  builtin/clone: compute checkout metadata for clones
  builtin/checkout: compute checkout metadata for checkouts
  convert: provide additional metadata to filters
  convert: permit passing additional metadata to filter processes
  builtin/checkout: pass branch info down to checkout_worktree
-rw-r--r--apply.c2
-rw-r--r--archive.c13
-rw-r--r--archive.h1
-rw-r--r--builtin/cat-file.c5
-rw-r--r--builtin/checkout.c64
-rw-r--r--builtin/clone.c5
-rw-r--r--builtin/rebase.c1
-rw-r--r--builtin/reset.c16
-rw-r--r--cache.h1
-rw-r--r--convert.c66
-rw-r--r--convert.h29
-rw-r--r--diff.c5
-rw-r--r--entry.c7
-rw-r--r--merge-recursive.c2
-rw-r--r--merge.c1
-rw-r--r--sequencer.c1
-rwxr-xr-xt/t0021-conversion.sh198
-rw-r--r--t/t0021/rot13-filter.pl6
-rw-r--r--unpack-trees.c1
-rw-r--r--unpack-trees.h1
20 files changed, 350 insertions, 75 deletions
diff --git a/apply.c b/apply.c
index bdc008fae2..144c19aaca 100644
--- a/apply.c
+++ b/apply.c
@@ -4349,7 +4349,7 @@ static int try_create_file(struct apply_state *state, const char *path,
         if (fd < 0)
                 return 1;
 
-        if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf)) {
+        if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf, NULL)) {
                 size = nbuf.len;
                 buf  = nbuf.buf;
         }
diff --git a/archive.c b/archive.c
index a8da0fcc4f..fb39706120 100644
--- a/archive.c
+++ b/archive.c
@@ -77,6 +77,11 @@ void *object_file_to_archive(const struct archiver_args *args,
 {
         void *buffer;
         const struct commit *commit = args->convert ? args->commit : NULL;
+        struct checkout_metadata meta;
+
+        init_checkout_metadata(&meta, args->refname,
+                               args->commit_oid ? args->commit_oid :
+                               (args->tree ? &args->tree->object.oid : NULL), oid);
 
         path += args->baselen;
         buffer = read_object_file(oid, type, sizep);
@@ -85,7 +90,7 @@ void *object_file_to_archive(const struct archiver_args *args,
                 size_t size = 0;
 
                 strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
-                convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf);
+                convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta);
                 if (commit)
                         format_subst(commit, buf.buf, buf.len, &buf);
                 buffer = strbuf_detach(&buf, &size);
@@ -385,16 +390,17 @@ static void parse_treeish_arg(const char **argv,
         struct tree *tree;
         const struct commit *commit;
         struct object_id oid;
+        char *ref = NULL;
 
         /* Remotes are only allowed to fetch actual refs */
         if (remote && !remote_allow_unreachable) {
-                char *ref = NULL;
                 const char *colon = strchrnul(name, ':');
                 int refnamelen = colon - name;
 
                 if (!dwim_ref(name, refnamelen, &oid, &ref))
                         die(_("no such ref: %.*s"), refnamelen, name);
-                free(ref);
+        } else {
+                dwim_ref(name, strlen(name), &oid, &ref);
         }
 
         if (get_oid(name, &oid))
@@ -427,6 +433,7 @@ static void parse_treeish_arg(const char **argv,
 
                 tree = parse_tree_indirect(&tree_oid);
         }
+        ar_args->refname = ref;
         ar_args->tree = tree;
         ar_args->commit_oid = commit_oid;
         ar_args->commit = commit;
diff --git a/archive.h b/archive.h
index e60e3dd31c..3bd96bf6bb 100644
--- a/archive.h
+++ b/archive.h
@@ -8,6 +8,7 @@ struct repository;
 
 struct archiver_args {
         struct repository *repo;
+        const char *refname;
         const char *base;
         size_t baselen;
         struct tree *tree;
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 272f9fc6d7..6ecc8ee6dc 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -42,7 +42,10 @@ static int filter_object(const char *path, unsigned mode,
                              oid_to_hex(oid), path);
         if ((type == OBJ_BLOB) && S_ISREG(mode)) {
                 struct strbuf strbuf = STRBUF_INIT;
-                if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) {
+                struct checkout_metadata meta;
+
+                init_checkout_metadata(&meta, NULL, NULL, oid);
+                if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) {
                         free(*buf);
                         *size = strbuf.len;
                         *buf = strbuf_detach(&strbuf, NULL);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d6773818b8..8bc94d392b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -88,6 +88,19 @@ struct checkout_opts {
         struct tree *source_tree;
 };
 
+struct branch_info {
+        const char *name; /* The short name used */
+        const char *path; /* The full name of a real branch */
+        struct commit *commit; /* The named commit */
+        char *refname; /* The full name of the ref being checked out. */
+        struct object_id oid; /* The object ID of the commit being checked out. */
+        /*
+         * if not null the branch is detached because it's already
+         * checked out in this checkout
+         */
+        char *checkout;
+};
+
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                               int changed)
 {
@@ -337,7 +350,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce,
         }
 }
 
-static int checkout_worktree(const struct checkout_opts *opts)
+static int checkout_worktree(const struct checkout_opts *opts,
+                             const struct branch_info *info)
 {
         struct checkout state = CHECKOUT_INIT;
         int nr_checkouts = 0, nr_unmerged = 0;
@@ -348,6 +362,10 @@ static int checkout_worktree(const struct checkout_opts *opts)
         state.refresh_cache = 1;
         state.istate = &the_index;
 
+        init_checkout_metadata(&state.meta, info->refname,
+                               info->commit ? &info->commit->object.oid : &info->oid,
+                               NULL);
+
         enable_delayed_checkout(&state);
         for (pos = 0; pos < active_nr; pos++) {
                 struct cache_entry *ce = active_cache[pos];
@@ -396,7 +414,7 @@ static int checkout_worktree(const struct checkout_opts *opts)
 }
 
 static int checkout_paths(const struct checkout_opts *opts,
-                          const char *revision)
+                          const struct branch_info *new_branch_info)
 {
         int pos;
         static char *ps_matched;
@@ -462,7 +480,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                 else
                         BUG("either flag must have been set, worktree=%d, index=%d",
                             opts->checkout_worktree, opts->checkout_index);
-                return run_add_interactive(revision, patch_mode, &opts->pathspec);
+                return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec);
         }
 
         repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -523,7 +541,7 @@ static int checkout_paths(const struct checkout_opts *opts,
 
         /* Now we are committed to check them out */
         if (opts->checkout_worktree)
-                errs |= checkout_worktree(opts);
+                errs |= checkout_worktree(opts, new_branch_info);
         else
                 remove_marked_cache_entries(&the_index, 1);
 
@@ -586,7 +604,8 @@ static void describe_detached_head(const char *msg, struct commit *commit)
 }
 
 static int reset_tree(struct tree *tree, const struct checkout_opts *o,
-                      int worktree, int *writeout_error)
+                      int worktree, int *writeout_error,
+                      struct branch_info *info)
 {
         struct unpack_trees_options opts;
         struct tree_desc tree_desc;
@@ -601,6 +620,11 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
         opts.verbose_update = o->show_progress;
         opts.src_index = &the_index;
         opts.dst_index = &the_index;
+        init_checkout_metadata(&opts.meta, info->refname,
+                               info->commit ? &info->commit->object.oid :
+                               is_null_oid(&info->oid) ? &tree->object.oid :
+                               &info->oid,
+                               NULL);
         parse_tree(tree);
         init_tree_desc(&tree_desc, tree->buffer, tree->size);
         switch (unpack_trees(1, &tree_desc, &opts)) {
@@ -620,21 +644,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
         }
 }
 
-struct branch_info {
-        const char *name; /* The short name used */
-        const char *path; /* The full name of a real branch */
-        struct commit *commit; /* The named commit */
-        /*
-         * if not null the branch is detached because it's already
-         * checked out in this checkout
-         */
-        char *checkout;
-};
-
 static void setup_branch_path(struct branch_info *branch)
 {
         struct strbuf buf = STRBUF_INIT;
 
+        /*
+         * If this is a ref, resolve it; otherwise, look up the OID for our
+         * expression.  Failure here is okay.
+         */
+        if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+                repo_get_oid_committish(the_repository, branch->name, &branch->oid);
+
         strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
         if (strcmp(buf.buf, branch->name))
                 branch->name = xstrdup(buf.buf);
@@ -663,7 +683,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
         } else
                 new_tree = get_commit_tree(new_branch_info->commit);
         if (opts->discard_changes) {
-                ret = reset_tree(new_tree, opts, 1, writeout_error);
+                ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
                 if (ret)
                         return ret;
         } else {
@@ -692,6 +712,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
                 topts.quiet = opts->merge && old_branch_info->commit;
                 topts.verbose_update = opts->show_progress;
                 topts.fn = twoway_merge;
+                init_checkout_metadata(&topts.meta, new_branch_info->refname,
+                                       new_branch_info->commit ?
+                                       &new_branch_info->commit->object.oid :
+                                       &new_branch_info->oid, NULL);
                 if (opts->overwrite_ignore) {
                         topts.dir = xcalloc(1, sizeof(*topts.dir));
                         topts.dir->flags |= DIR_SHOW_IGNORED;
@@ -762,7 +786,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 
                         ret = reset_tree(new_tree,
                                          opts, 1,
-                                         writeout_error);
+                                         writeout_error, new_branch_info);
                         if (ret)
                                 return ret;
                         o.ancestor = old_branch_info->name;
@@ -782,7 +806,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                                 exit(128);
                         ret = reset_tree(new_tree,
                                          opts, 0,
-                                         writeout_error);
+                                         writeout_error, new_branch_info);
                         strbuf_release(&o.obuf);
                         strbuf_release(&old_commit_shortname);
                         if (ret)
@@ -1710,7 +1734,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 
         UNLEAK(opts);
         if (opts->patch_mode || opts->pathspec.nr)
-                return checkout_paths(opts, new_branch_info.name);
+                return checkout_paths(opts, &new_branch_info);
         else
                 return checkout_branch(opts, &new_branch_info);
 }
diff --git a/builtin/clone.c b/builtin/clone.c
index 46573b9b7c..d8b1f413aa 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -784,11 +784,11 @@ static int checkout(int submodule_progress)
         if (!strcmp(head, "HEAD")) {
                 if (advice_detached_head)
                         detach_advice(oid_to_hex(&oid));
+                FREE_AND_NULL(head);
         } else {
                 if (!starts_with(head, "refs/heads/"))
                         die(_("HEAD not found below refs/heads!"));
         }
-        free(head);
 
         /* We need to be in the new work tree for the checkout */
         setup_work_tree();
@@ -803,6 +803,7 @@ static int checkout(int submodule_progress)
         opts.verbose_update = (option_verbosity >= 0);
         opts.src_index = &the_index;
         opts.dst_index = &the_index;
+        init_checkout_metadata(&opts.meta, head, &oid, NULL);
 
         tree = parse_tree_indirect(&oid);
         parse_tree(tree);
@@ -810,6 +811,8 @@ static int checkout(int submodule_progress)
         if (unpack_trees(1, &t, &opts) < 0)
                 die(_("unable to checkout working tree"));
 
+        free(head);
+
         if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                 die(_("unable to write new index file"));
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index bff53d5d16..27a07d4e78 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -868,6 +868,7 @@ static int reset_head(struct object_id *oid, const char *action,
         unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
         unpack_tree_opts.update = 1;
         unpack_tree_opts.merge = 1;
+        init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
         if (!detach_head)
                 unpack_tree_opts.reset = 1;
 
diff --git a/builtin/reset.c b/builtin/reset.c
index 18228c312e..4c634111bd 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -46,7 +46,7 @@ static inline int is_merge(void)
         return !access(git_path_merge_head(the_repository), F_OK);
 }
 
-static int reset_index(const struct object_id *oid, int reset_type, int quiet)
+static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet)
 {
         int i, nr = 0;
         struct tree_desc desc[2];
@@ -60,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
         opts.dst_index = &the_index;
         opts.fn = oneway_merge;
         opts.merge = 1;
+        init_checkout_metadata(&opts.meta, ref, oid, NULL);
         if (!quiet)
                 opts.verbose_update = 1;
         switch (reset_type) {
@@ -418,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                 }
                         }
                 } else {
-                        int err = reset_index(&oid, reset_type, quiet);
+                        struct object_id dummy;
+                        char *ref = NULL;
+                        int err;
+
+                        dwim_ref(rev, strlen(rev), &dummy, &ref);
+                        if (ref && !starts_with(ref, "refs/"))
+                                ref = NULL;
+
+                        err = reset_index(ref, &oid, reset_type, quiet);
                         if (reset_type == KEEP && !err)
-                                err = reset_index(&oid, MIXED, quiet);
+                                err = reset_index(ref, &oid, MIXED, quiet);
                         if (err)
                                 die(_("Could not reset index file to revision '%s'."), rev);
+                        free(ref);
                 }
 
                 if (write_locked_index(&the_index, &lock, COMMIT_LOCK))
diff --git a/cache.h b/cache.h
index d35ef97a73..c77b95870a 100644
--- a/cache.h
+++ b/cache.h
@@ -1698,6 +1698,7 @@ struct checkout {
         const char *base_dir;
         int base_dir_len;
         struct delayed_checkout *delayed_checkout;
+        struct checkout_metadata meta;
         unsigned force:1,
                  quiet:1,
                  not_new:1,
diff --git a/convert.c b/convert.c
index 5ead3ce678..5aa87d45e3 100644
--- a/convert.c
+++ b/convert.c
@@ -797,6 +797,7 @@ static void handle_filter_error(const struct strbuf *filter_status,
 static int apply_multi_file_filter(const char *path, const char *src, size_t len,
                                    int fd, struct strbuf *dst, const char *cmd,
                                    const unsigned int wanted_capability,
+                                   const struct checkout_metadata *meta,
                                    struct delayed_checkout *dco)
 {
         int err;
@@ -855,6 +856,24 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
         if (err)
                 goto done;
 
+        if (meta && meta->refname) {
+                err = packet_write_fmt_gently(process->in, "ref=%s\n", meta->refname);
+                if (err)
+                        goto done;
+        }
+
+        if (meta && !is_null_oid(&meta->treeish)) {
+                err = packet_write_fmt_gently(process->in, "treeish=%s\n", oid_to_hex(&meta->treeish));
+                if (err)
+                        goto done;
+        }
+
+        if (meta && !is_null_oid(&meta->blob)) {
+                err = packet_write_fmt_gently(process->in, "blob=%s\n", oid_to_hex(&meta->blob));
+                if (err)
+                        goto done;
+        }
+
         if ((entry->supported_capabilities & CAP_DELAY) &&
             dco && dco->state == CE_CAN_DELAY) {
                 can_delay = 1;
@@ -971,6 +990,7 @@ static struct convert_driver {
 static int apply_filter(const char *path, const char *src, size_t len,
                         int fd, struct strbuf *dst, struct convert_driver *drv,
                         const unsigned int wanted_capability,
+                        const struct checkout_metadata *meta,
                         struct delayed_checkout *dco)
 {
         const char *cmd = NULL;
@@ -990,7 +1010,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
                 return apply_single_file_filter(path, src, len, fd, dst, cmd);
         else if (drv->process && *drv->process)
                 return apply_multi_file_filter(path, src, len, fd, dst,
-                        drv->process, wanted_capability, dco);
+                        drv->process, wanted_capability, meta, dco);
 
         return 0;
 }
@@ -1368,7 +1388,7 @@ int would_convert_to_git_filter_fd(const struct index_state *istate, const char
         if (!ca.drv->required)
                 return 0;
 
-        return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL);
+        return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL, NULL);
 }
 
 const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
@@ -1406,7 +1426,7 @@ int convert_to_git(const struct index_state *istate,
 
         convert_attrs(istate, &ca, path);
 
-        ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL);
+        ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL, NULL);
         if (!ret && ca.drv && ca.drv->required)
                 die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
 
@@ -1441,7 +1461,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,
         assert(ca.drv);
         assert(ca.drv->clean || ca.drv->process);
 
-        if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL))
+        if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL, NULL))
                 die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
 
         encode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);
@@ -1452,7 +1472,9 @@ void convert_to_git_filter_fd(const struct index_state *istate,
 static int convert_to_working_tree_internal(const struct index_state *istate,
                                             const char *path, const char *src,
                                             size_t len, struct strbuf *dst,
-                                            int normalizing, struct delayed_checkout *dco)
+                                            int normalizing,
+                                            const struct checkout_metadata *meta,
+                                            struct delayed_checkout *dco)
 {
         int ret = 0, ret_filter = 0;
         struct conv_attrs ca;
@@ -1484,7 +1506,7 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
         }
 
         ret_filter = apply_filter(
-                path, src, len, -1, dst, ca.drv, CAP_SMUDGE, dco);
+                path, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);
         if (!ret_filter && ca.drv && ca.drv->required)
                 die(_("%s: smudge filter %s failed"), path, ca.drv->name);
 
@@ -1494,22 +1516,24 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
 int async_convert_to_working_tree(const struct index_state *istate,
                                   const char *path, const char *src,
                                   size_t len, struct strbuf *dst,
+                                  const struct checkout_metadata *meta,
                                   void *dco)
 {
-        return convert_to_working_tree_internal(istate, path, src, len, dst, 0, dco);
+        return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);
 }
 
 int convert_to_working_tree(const struct index_state *istate,
                             const char *path, const char *src,
-                            size_t len, struct strbuf *dst)
+                            size_t len, struct strbuf *dst,
+                            const struct checkout_metadata *meta)
 {
-        return convert_to_working_tree_internal(istate, path, src, len, dst, 0, NULL);
+        return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);
 }
 
 int renormalize_buffer(const struct index_state *istate, const char *path,
                        const char *src, size_t len, struct strbuf *dst)
 {
-        int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL);
+        int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);
         if (ret) {
                 src = dst->buf;
                 len = dst->len;
@@ -1982,3 +2006,25 @@ int stream_filter(struct stream_filter *filter,
 {
         return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
 }
+
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+                            const struct object_id *treeish,
+                            const struct object_id *blob)
+{
+        memset(meta, 0, sizeof(*meta));
+        if (refname)
+                meta->refname = refname;
+        if (treeish)
+                oidcpy(&meta->treeish, treeish);
+        if (blob)
+                oidcpy(&meta->blob, blob);
+}
+
+void clone_checkout_metadata(struct checkout_metadata *dst,
+                             const struct checkout_metadata *src,
+                             const struct object_id *blob)
+{
+        memcpy(dst, src, sizeof(*dst));
+        if (blob)
+                oidcpy(&dst->blob, blob);
+}
diff --git a/convert.h b/convert.h
index 3710969d43..e29d1026a6 100644
--- a/convert.h
+++ b/convert.h
@@ -4,10 +4,10 @@
 #ifndef CONVERT_H
 #define CONVERT_H
 
+#include "hash.h"
 #include "string-list.h"
 
 struct index_state;
-struct object_id;
 struct strbuf;
 
 #define CONV_EOL_RNDTRP_DIE   (1<<0) /* Die if CRLF to LF to CRLF is different */
@@ -57,6 +57,12 @@ struct delayed_checkout {
         struct string_list paths;
 };
 
+struct checkout_metadata {
+        const char *refname;
+        struct object_id treeish;
+        struct object_id blob;
+};
+
 extern enum eol core_eol;
 extern char *check_roundtrip_encoding;
 const char *get_cached_convert_stats_ascii(const struct index_state *istate,
@@ -71,10 +77,12 @@ int convert_to_git(const struct index_state *istate,
                    struct strbuf *dst, int conv_flags);
 int convert_to_working_tree(const struct index_state *istate,
                             const char *path, const char *src,
-                            size_t len, struct strbuf *dst);
+                            size_t len, struct strbuf *dst,
+                            const struct checkout_metadata *meta);
 int async_convert_to_working_tree(const struct index_state *istate,
                                   const char *path, const char *src,
                                   size_t len, struct strbuf *dst,
+                                  const struct checkout_metadata *meta,
                                   void *dco);
 int async_query_available_blobs(const char *cmd,
                                 struct string_list *available_paths);
@@ -95,6 +103,23 @@ int would_convert_to_git_filter_fd(const struct index_state *istate,
                                    const char *path);
 
 /*
+ * Initialize the checkout metadata with the given values.  Any argument may be
+ * NULL if it is not applicable.  The treeish should be a commit if that is
+ * available, and a tree otherwise.
+ *
+ * The refname is not copied and must be valid for the lifetime of the struct.
+ * THe object IDs are copied.
+ */
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+                            const struct object_id *treeish,
+                            const struct object_id *blob);
+
+/* Copy the metadata from src to dst, updating the blob. */
+void clone_checkout_metadata(struct checkout_metadata *dst,
+                             const struct checkout_metadata *src,
+                             const struct object_id *blob);
+
+/*
  * Reset the internal list of attributes used by convert_to_git and
  * convert_to_working_tree.
  */
diff --git a/diff.c b/diff.c
index f2cfbf2214..1010d806f5 100644
--- a/diff.c
+++ b/diff.c
@@ -4062,6 +4062,9 @@ static void prep_temp_blob(struct index_state *istate,
         struct strbuf tempfile = STRBUF_INIT;
         char *path_dup = xstrdup(path);
         const char *base = basename(path_dup);
+        struct checkout_metadata meta;
+
+        init_checkout_metadata(&meta, NULL, NULL, oid);
 
         /* Generate "XXXXXX_basename.ext" */
         strbuf_addstr(&tempfile, "XXXXXX_");
@@ -4071,7 +4074,7 @@ static void prep_temp_blob(struct index_state *istate,
         if (!temp->tempfile)
                 die_errno("unable to create temp-file");
         if (convert_to_working_tree(istate, path,
-                        (const char *)blob, (size_t)size, &buf)) {
+                        (const char *)blob, (size_t)size, &buf, &meta)) {
                 blob = buf.buf;
                 size = buf.len;
         }
diff --git a/entry.c b/entry.c
index 53380bb614..00b4903366 100644
--- a/entry.c
+++ b/entry.c
@@ -264,6 +264,9 @@ static int write_entry(struct cache_entry *ce,
         size_t newsize = 0;
         struct stat st;
         const struct submodule *sub;
+        struct checkout_metadata meta;
+
+        clone_checkout_metadata(&meta, &state->meta, &ce->oid);
 
         if (ce_mode_s_ifmt == S_IFREG) {
                 struct stream_filter *filter = get_stream_filter(state->istate, ce->name,
@@ -315,13 +318,13 @@ static int write_entry(struct cache_entry *ce,
                  */
                 if (dco && dco->state != CE_NO_DELAY) {
                         ret = async_convert_to_working_tree(state->istate, ce->name, new_blob,
-                                                            size, &buf, dco);
+                                                            size, &buf, &meta, dco);
                         if (ret && string_list_has_string(&dco->paths, ce->name)) {
                                 free(new_blob);
                                 goto delayed;
                         }
                 } else
-                        ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf);
+                        ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);
 
                 if (ret) {
                         free(new_blob);
diff --git a/merge-recursive.c b/merge-recursive.c
index 7a4e6f20fa..d92e2acf1e 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -958,7 +958,7 @@ static int update_file_flags(struct merge_options *opt,
                 if (S_ISREG(contents->mode)) {
                         struct strbuf strbuf = STRBUF_INIT;
                         if (convert_to_working_tree(opt->repo->index,
-                                                    path, buf, size, &strbuf)) {
+                                                    path, buf, size, &strbuf, NULL)) {
                                 free(buf);
                                 size = strbuf.len;
                                 buf = strbuf_detach(&strbuf, NULL);
diff --git a/merge.c b/merge.c
index 7c1d756c3f..aa36de2f64 100644
--- a/merge.c
+++ b/merge.c
@@ -94,6 +94,7 @@ int checkout_fast_forward(struct repository *r,
         opts.verbose_update = 1;
         opts.merge = 1;
         opts.fn = twoway_merge;
+        init_checkout_metadata(&opts.meta, NULL, remote, NULL);
         setup_unpack_trees_porcelain(&opts, "merge");
 
         if (unpack_trees(nr_trees, t, &opts)) {
diff --git a/sequencer.c b/sequencer.c
index c37515ee31..6fd2674632 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3305,6 +3305,7 @@ static int do_reset(struct repository *r,
         unpack_tree_opts.fn = oneway_merge;
         unpack_tree_opts.merge = 1;
         unpack_tree_opts.update = 1;
+        init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
 
         if (repo_read_index_unmerged(r)) {
                 rollback_lock_file(&lock);
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index dc664da551..4bfffa9c31 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -364,6 +364,10 @@ test_expect_success PERL 'required process filter should filter data' '
                 S=$(file_size test.r) &&
                 S2=$(file_size test2.r) &&
                 S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+                M=$(git hash-object test.r) &&
+                M2=$(git hash-object test2.r) &&
+                M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+                EMPTY=$(git hash-object /dev/null) &&
 
                 filter_git add . &&
                 cat >expected.log <<-EOF &&
@@ -378,14 +382,16 @@ test_expect_success PERL 'required process filter should filter data' '
                 test_cmp_count expected.log debug.log &&
 
                 git commit -m "test commit 2" &&
+                MASTER=$(git rev-parse --verify master) &&
+                META="ref=refs/heads/master treeish=$MASTER" &&
                 rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" &&
 
                 filter_git checkout --quiet --no-progress . &&
                 cat >expected.log <<-EOF &&
                         START
                         init handshake complete
-                        IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
-                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
+                        IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
                         STOP
                 EOF
                 test_cmp_exclude_clean expected.log debug.log &&
@@ -406,10 +412,10 @@ test_expect_success PERL 'required process filter should filter data' '
                 cat >expected.log <<-EOF &&
                         START
                         init handshake complete
-                        IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                        IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
-                        IN: smudge test4-empty.r 0 [OK] -- OUT: 0  [OK]
-                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
+                        IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
                         STOP
                 EOF
                 test_cmp_exclude_clean expected.log debug.log &&
@@ -420,6 +426,117 @@ test_expect_success PERL 'required process filter should filter data' '
         )
 '
 
+test_expect_success PERL 'required process filter should filter data for various subcommands' '
+        test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
+        test_config_global filter.protocol.required true &&
+        (
+                cd repo &&
+
+                S=$(file_size test.r) &&
+                S2=$(file_size test2.r) &&
+                S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+                M=$(git hash-object test.r) &&
+                M2=$(git hash-object test2.r) &&
+                M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+                EMPTY=$(git hash-object /dev/null) &&
+
+                MASTER=$(git rev-parse --verify master) &&
+
+                cp "$TEST_ROOT/test.o" test5.r &&
+                git add test5.r &&
+                git commit -m "test commit 3" &&
+                git checkout empty-branch &&
+                filter_git rebase --onto empty-branch master^^ master &&
+                MASTER2=$(git rev-parse --verify master) &&
+                META="ref=refs/heads/master treeish=$MASTER2" &&
+                cat >expected.log <<-EOF &&
+                        START
+                        init handshake complete
+                        IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                        IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                        STOP
+                EOF
+                test_cmp_exclude_clean expected.log debug.log &&
+
+                git reset --hard empty-branch &&
+                filter_git reset --hard $MASTER &&
+                META="treeish=$MASTER" &&
+                cat >expected.log <<-EOF &&
+                        START
+                        init handshake complete
+                        IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                        STOP
+                EOF
+                test_cmp_exclude_clean expected.log debug.log &&
+
+                git branch old-master $MASTER &&
+                git reset --hard empty-branch &&
+                filter_git reset --hard old-master &&
+                META="ref=refs/heads/old-master treeish=$MASTER" &&
+                cat >expected.log <<-EOF &&
+                        START
+                        init handshake complete
+                        IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                        STOP
+                EOF
+                test_cmp_exclude_clean expected.log debug.log &&
+
+                git checkout -b merge empty-branch &&
+                git branch -f master $MASTER2 &&
+                filter_git merge master &&
+                META="treeish=$MASTER2" &&
+                cat >expected.log <<-EOF &&
+                        START
+                        init handshake complete
+                        IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                        IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                        STOP
+                EOF
+                test_cmp_exclude_clean expected.log debug.log &&
+
+                filter_git archive master >/dev/null &&
+                META="ref=refs/heads/master treeish=$MASTER2" &&
+                cat >expected.log <<-EOF &&
+                        START
+                        init handshake complete
+                        IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                        IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                        STOP
+                EOF
+                test_cmp_exclude_clean expected.log debug.log &&
+
+                TREE="$(git rev-parse $MASTER2^{tree})" &&
+                filter_git archive $TREE >/dev/null &&
+                META="treeish=$TREE" &&
+                cat >expected.log <<-EOF &&
+                        START
+                        init handshake complete
+                        IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                        IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                        STOP
+                EOF
+                test_cmp_exclude_clean expected.log debug.log
+        )
+'
+
 test_expect_success PERL 'required process filter takes precedence' '
         test_config_global filter.protocol.clean false &&
         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" &&
@@ -519,17 +636,22 @@ test_expect_success PERL 'required process filter should process multiple packet
                 EOF
                 test_cmp_count expected.log debug.log &&
 
-                rm -f *.file &&
+                M1="blob=$(git hash-object 1pkt_1__.file)" &&
+                M2="blob=$(git hash-object 2pkt_1+1.file)" &&
+                M3="blob=$(git hash-object 2pkt_2-1.file)" &&
+                M4="blob=$(git hash-object 2pkt_2__.file)" &&
+                M5="blob=$(git hash-object 3pkt_2+1.file)" &&
+                rm -f *.file debug.log &&
 
                 filter_git checkout --quiet --no-progress -- *.file &&
                 cat >expected.log <<-EOF &&
                         START
                         init handshake complete
-                        IN: smudge 1pkt_1__.file $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
-                        IN: smudge 2pkt_1+1.file $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
-                        IN: smudge 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
-                        IN: smudge 2pkt_2__.file $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
-                        IN: smudge 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
+                        IN: smudge 1pkt_1__.file $M1 $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
+                        IN: smudge 2pkt_1+1.file $M2 $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
+                        IN: smudge 2pkt_2-1.file $M3 $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
+                        IN: smudge 2pkt_2__.file $M4 $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
+                        IN: smudge 3pkt_2+1.file $M5 $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
                         STOP
                 EOF
                 test_cmp_exclude_clean expected.log debug.log &&
@@ -578,6 +700,10 @@ test_expect_success PERL 'process filter should restart after unexpected write f
                 S=$(file_size test.r) &&
                 S2=$(file_size test2.r) &&
                 SF=$(file_size smudge-write-fail.r) &&
+                M=$(git hash-object test.r) &&
+                M2=$(git hash-object test2.r) &&
+                MF=$(git hash-object smudge-write-fail.r) &&
+                rm -f debug.log &&
 
                 git add . &&
                 rm -f *.r &&
@@ -591,11 +717,11 @@ test_expect_success PERL 'process filter should restart after unexpected write f
                 cat >expected.log <<-EOF &&
                         START
                         init handshake complete
-                        IN: smudge smudge-write-fail.r $SF [OK] -- [WRITE FAIL]
+                        IN: smudge smudge-write-fail.r blob=$MF $SF [OK] -- [WRITE FAIL]
                         START
                         init handshake complete
-                        IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                        IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
                         STOP
                 EOF
                 test_cmp_exclude_clean expected.log debug.log &&
@@ -629,6 +755,10 @@ test_expect_success PERL 'process filter should not be restarted if it signals a
                 S=$(file_size test.r) &&
                 S2=$(file_size test2.r) &&
                 SE=$(file_size error.r) &&
+                M=$(git hash-object test.r) &&
+                M2=$(git hash-object test2.r) &&
+                ME=$(git hash-object error.r) &&
+                rm -f debug.log &&
 
                 git add . &&
                 rm -f *.r &&
@@ -637,9 +767,9 @@ test_expect_success PERL 'process filter should not be restarted if it signals a
                 cat >expected.log <<-EOF &&
                         START
                         init handshake complete
-                        IN: smudge error.r $SE [OK] -- [ERROR]
-                        IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                        IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
+                        IN: smudge error.r blob=$ME $SE [OK] -- [ERROR]
+                        IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
+                        IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
                         STOP
                 EOF
                 test_cmp_exclude_clean expected.log debug.log &&
@@ -665,18 +795,21 @@ test_expect_success PERL 'process filter abort stops processing of all further f
                 echo "error this blob and all future blobs" >abort.o &&
                 cp abort.o abort.r &&
 
+                M="blob=$(git hash-object abort.r)" &&
+                rm -f debug.log &&
                 SA=$(file_size abort.r) &&
 
                 git add . &&
                 rm -f *.r &&
 
+
                 # Note: This test assumes that Git filters files in alphabetical
                 # order ("abort.r" before "test.r").
                 filter_git checkout --quiet --no-progress . &&
                 cat >expected.log <<-EOF &&
                         START
                         init handshake complete
-                        IN: smudge abort.r $SA [OK] -- [ABORT]
+                        IN: smudge abort.r $M $SA [OK] -- [ABORT]
                         STOP
                 EOF
                 test_cmp_exclude_clean expected.log debug.log &&
@@ -727,27 +860,29 @@ test_expect_success PERL 'delayed checkout in process filter' '
         ) &&
 
         S=$(file_size "$TEST_ROOT/test.o") &&
+        PM="ref=refs/heads/master treeish=$(git -C repo rev-parse --verify master) " &&
+        M="${PM}blob=$(git -C repo rev-parse --verify master:test.a)" &&
         cat >a.exp <<-EOF &&
                 START
                 init handshake complete
-                IN: smudge test.a $S [OK] -- OUT: $S . [OK]
-                IN: smudge test-delay10.a $S [OK] -- [DELAYED]
-                IN: smudge test-delay11.a $S [OK] -- [DELAYED]
-                IN: smudge test-delay20.a $S [OK] -- [DELAYED]
+                IN: smudge test.a $M $S [OK] -- OUT: $S . [OK]
+                IN: smudge test-delay10.a $M $S [OK] -- [DELAYED]
+                IN: smudge test-delay11.a $M $S [OK] -- [DELAYED]
+                IN: smudge test-delay20.a $M $S [OK] -- [DELAYED]
                 IN: list_available_blobs test-delay10.a test-delay11.a [OK]
-                IN: smudge test-delay10.a 0 [OK] -- OUT: $S . [OK]
-                IN: smudge test-delay11.a 0 [OK] -- OUT: $S . [OK]
+                IN: smudge test-delay10.a $M 0 [OK] -- OUT: $S . [OK]
+                IN: smudge test-delay11.a $M 0 [OK] -- OUT: $S . [OK]
                 IN: list_available_blobs test-delay20.a [OK]
-                IN: smudge test-delay20.a 0 [OK] -- OUT: $S . [OK]
+                IN: smudge test-delay20.a $M 0 [OK] -- OUT: $S . [OK]
                 IN: list_available_blobs [OK]
                 STOP
         EOF
         cat >b.exp <<-EOF &&
                 START
                 init handshake complete
-                IN: smudge test-delay10.b $S [OK] -- [DELAYED]
+                IN: smudge test-delay10.b $M $S [OK] -- [DELAYED]
                 IN: list_available_blobs test-delay10.b [OK]
-                IN: smudge test-delay10.b 0 [OK] -- OUT: $S . [OK]
+                IN: smudge test-delay10.b $M 0 [OK] -- OUT: $S . [OK]
                 IN: list_available_blobs [OK]
                 STOP
         EOF
@@ -767,8 +902,11 @@ test_expect_success PERL 'delayed checkout in process filter' '
 
                 rm *.a *.b &&
                 filter_git checkout . &&
-                test_cmp_count ../a.exp a.log &&
-                test_cmp_count ../b.exp b.log &&
+                # We are not checking out a ref here, so filter out ref metadata.
+                sed -e "s!$PM!!" ../a.exp >a.exp.filtered &&
+                sed -e "s!$PM!!" ../b.exp >b.exp.filtered &&
+                test_cmp_count a.exp.filtered a.log &&
+                test_cmp_count b.exp.filtered b.log &&
 
                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a &&
                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a &&
diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl
index 470107248e..cd32a82da5 100644
--- a/t/t0021/rot13-filter.pl
+++ b/t/t0021/rot13-filter.pl
@@ -135,7 +135,13 @@ while (1) {
                                 if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) {
                                         $DELAY{$pathname}{"requested"} = 1;
                                 }
+                        } elsif ($buffer =~ /^(ref|treeish|blob)=/) {
+                                print $debug " $buffer";
                         } else {
+                                # In general, filters need to be graceful about
+                                # new metadata, since it's documented that we
+                                # can pass any key-value pairs, but for tests,
+                                # let's be a little stricter.
                                 die "Unknown message '$buffer'";
                         }
 
diff --git a/unpack-trees.c b/unpack-trees.c
index 1dbcb8c1c3..f618a644ef 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -371,6 +371,7 @@ static int check_updates(struct unpack_trees_options *o)
         state.quiet = 1;
         state.refresh_cache = 1;
         state.istate = index;
+        clone_checkout_metadata(&state.meta, &o->meta, NULL);
 
         if (!o->update || o->dry_run) {
                 remove_marked_cache_entries(index, 0);
diff --git a/unpack-trees.h b/unpack-trees.h
index ae1557fb80..ad41b45a71 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -85,6 +85,7 @@ struct unpack_trees_options {
         struct index_state result;
 
         struct pattern_list *pl; /* for internal use */
+        struct checkout_metadata meta;
 };
 
 int unpack_trees(unsigned n, struct tree_desc *t,