builtin/submodule--helper: factor out submodule updating
[gitweb.git] / diff-lib.c
index 2c9be60ed9b47c2e563f69e6c8b60195fd51a917..732f684a49c54cdbc3b9708e91c064b7845d7716 100644 (file)
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "refs.h"
+#include "submodule.h"
+#include "dir.h"
+#include "fsmonitor.h"
 
 /*
  * diff-files
  */
 
-int run_diff_files(struct rev_info *revs, int silent_on_removed)
+/*
+ * Has the work tree entity been removed?
+ *
+ * Return 1 if it was removed from the work tree, 0 if an entity to be
+ * compared with the cache entry ce still exists (the latter includes
+ * the case where a directory that is not a submodule repository
+ * exists for ce that is a submodule -- it is a submodule that is not
+ * checked out).  Return negative for an error.
+ */
+static int check_removed(const struct cache_entry *ce, struct stat *st)
+{
+       if (lstat(ce->name, st) < 0) {
+               if (!is_missing_file_error(errno))
+                       return -1;
+               return 1;
+       }
+       if (has_symlink_leading_path(ce->name, ce_namelen(ce)))
+               return 1;
+       if (S_ISDIR(st->st_mode)) {
+               struct object_id sub;
+
+               /*
+                * If ce is already a gitlink, we can have a plain
+                * directory (i.e. the submodule is not checked out),
+                * or a checked out submodule.  Either case this is not
+                * a case where something was removed from the work tree,
+                * so we will return 0.
+                *
+                * Otherwise, if the directory is not a submodule
+                * repository, that means ce which was a blob turned into
+                * a directory --- the blob was removed!
+                */
+               if (!S_ISGITLINK(ce->ce_mode) &&
+                   resolve_gitlink_ref(ce->name, "HEAD", &sub))
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * Has a file changed or has a submodule new commits or a dirty work tree?
+ *
+ * Return 1 when changes are detected, 0 otherwise. If the DIRTY_SUBMODULES
+ * option is set, the caller does not only want to know if a submodule is
+ * modified at all but wants to know all the conditions that are met (new
+ * commits, untracked content and/or modified content).
+ */
+static int match_stat_with_submodule(struct diff_options *diffopt,
+                                    const struct cache_entry *ce,
+                                    struct stat *st, unsigned ce_option,
+                                    unsigned *dirty_submodule)
+{
+       int changed = ce_match_stat(ce, st, ce_option);
+       if (S_ISGITLINK(ce->ce_mode)) {
+               struct diff_flags orig_flags = diffopt->flags;
+               if (!diffopt->flags.override_submodule_config)
+                       set_diffopt_flags_from_submodule_config(diffopt, ce->name);
+               if (diffopt->flags.ignore_submodules)
+                       changed = 0;
+               else if (!diffopt->flags.ignore_dirty_submodules &&
+                        (!changed || diffopt->flags.dirty_submodules))
+                       *dirty_submodule = is_submodule_modified(ce->name,
+                                                                diffopt->flags.ignore_untracked_in_submodules);
+               diffopt->flags = orig_flags;
+       }
+       return changed;
+}
+
+int run_diff_files(struct rev_info *revs, unsigned int option)
 {
        int entries, i;
        int diff_unmerged_stage = revs->max_count;
+       unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
+                             ? CE_MATCH_RACY_IS_DIRTY : 0);
+       uint64_t start = getnanotime();
+
+       diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
 
        if (diff_unmerged_stage < 0)
                diff_unmerged_stage = 2;
-       entries = read_cache();
-       if (entries < 0) {
-               perror("read_cache");
-               return -1;
-       }
+       entries = active_nr;
        for (i = 0; i < entries; i++) {
-               struct stat st;
                unsigned int oldmode, newmode;
                struct cache_entry *ce = active_cache[i];
                int changed;
+               unsigned dirty_submodule = 0;
+               const struct object_id *old_oid, *new_oid;
 
-               if (!ce_path_match(ce, revs->prune_data))
+               if (diff_can_quit_early(&revs->diffopt))
+                       break;
+
+               if (!ce_path_match(ce, &revs->prune_data, NULL))
                        continue;
 
                if (ce_stage(ce)) {
                        struct combine_diff_path *dpath;
+                       struct diff_filepair *pair;
+                       unsigned int wt_mode = 0;
                        int num_compare_stages = 0;
                        size_t path_len;
+                       struct stat st;
 
                        path_len = ce_namelen(ce);
 
-                       dpath = xmalloc (combine_diff_path_size (5, path_len));
+                       dpath = xmalloc(combine_diff_path_size(5, path_len));
                        dpath->path = (char *) &(dpath->parent[5]);
 
                        dpath->next = NULL;
-                       dpath->len = path_len;
                        memcpy(dpath->path, ce->name, path_len);
                        dpath->path[path_len] = '\0';
-                       dpath->mode = 0;
-                       hashclr(dpath->sha1);
+                       oidclr(&dpath->oid);
                        memset(&(dpath->parent[0]), 0,
-                                       sizeof(struct combine_diff_parent)*5);
+                              sizeof(struct combine_diff_parent)*5);
+
+                       changed = check_removed(ce, &st);
+                       if (!changed)
+                               wt_mode = ce_mode_from_stat(ce, st.st_mode);
+                       else {
+                               if (changed < 0) {
+                                       perror(ce->name);
+                                       continue;
+                               }
+                               wt_mode = 0;
+                       }
+                       dpath->mode = wt_mode;
 
                        while (i < entries) {
                                struct cache_entry *nce = active_cache[i];
@@ -64,11 +156,11 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                                 */
                                stage = ce_stage(nce);
                                if (2 <= stage) {
-                                       int mode = ntohl(nce->ce_mode);
+                                       int mode = nce->ce_mode;
                                        num_compare_stages++;
-                                       hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
-                                       dpath->parent[stage-2].mode =
-                                               canon_mode(mode);
+                                       oidcpy(&dpath->parent[stage - 2].oid,
+                                              &nce->oid);
+                                       dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
                                        dpath->parent[stage-2].status =
                                                DIFF_STATUS_MODIFIED;
                                }
@@ -90,46 +182,72 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                                free(dpath);
                                continue;
                        }
-                       free(dpath);
-                       dpath = NULL;
+                       FREE_AND_NULL(dpath);
 
                        /*
                         * Show the diff for the 'ce' if we found the one
                         * from the desired stage.
                         */
-                       diff_unmerge(&revs->diffopt, ce->name, 0, null_sha1);
+                       pair = diff_unmerge(&revs->diffopt, ce->name);
+                       if (wt_mode)
+                               pair->two->mode = wt_mode;
                        if (ce_stage(ce) != diff_unmerged_stage)
                                continue;
                }
 
-               if (lstat(ce->name, &st) < 0) {
-                       if (errno != ENOENT && errno != ENOTDIR) {
-                               perror(ce->name);
+               if (ce_uptodate(ce) || ce_skip_worktree(ce))
+                       continue;
+
+               /* If CE_VALID is set, don't look at workdir for file removal */
+               if (ce->ce_flags & CE_VALID) {
+                       changed = 0;
+                       newmode = ce->ce_mode;
+               } else {
+                       struct stat st;
+
+                       changed = check_removed(ce, &st);
+                       if (changed) {
+                               if (changed < 0) {
+                                       perror(ce->name);
+                                       continue;
+                               }
+                               diff_addremove(&revs->diffopt, '-', ce->ce_mode,
+                                              &ce->oid,
+                                              !is_null_oid(&ce->oid),
+                                              ce->name, 0);
                                continue;
-                       }
-                       if (silent_on_removed)
+                       } else if (revs->diffopt.ita_invisible_in_index &&
+                                  ce_intent_to_add(ce)) {
+                               diff_addremove(&revs->diffopt, '+', ce->ce_mode,
+                                              the_hash_algo->empty_tree, 0,
+                                              ce->name, 0);
                                continue;
-                       diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
-                                      ce->sha1, ce->name, NULL);
-                       continue;
+                       }
+
+                       changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
+                                                           ce_option, &dirty_submodule);
+                       newmode = ce_mode_from_stat(ce, st.st_mode);
                }
-               changed = ce_match_stat(ce, &st, 0);
-               if (!changed && !revs->diffopt.find_copies_harder)
-                       continue;
-               oldmode = ntohl(ce->ce_mode);
 
-               newmode = canon_mode(st.st_mode);
-               if (!trust_executable_bit &&
-                   S_ISREG(newmode) && S_ISREG(oldmode) &&
-                   ((newmode ^ oldmode) == 0111))
-                       newmode = oldmode;
+               if (!changed && !dirty_submodule) {
+                       ce_mark_uptodate(ce);
+                       mark_fsmonitor_valid(ce);
+                       if (!revs->diffopt.flags.find_copies_harder)
+                               continue;
+               }
+               oldmode = ce->ce_mode;
+               old_oid = &ce->oid;
+               new_oid = changed ? &null_oid : &ce->oid;
                diff_change(&revs->diffopt, oldmode, newmode,
-                           ce->sha1, (changed ? null_sha1 : ce->sha1),
-                           ce->name, NULL);
+                           old_oid, new_oid,
+                           !is_null_oid(old_oid),
+                           !is_null_oid(new_oid),
+                           ce->name, 0, dirty_submodule);
 
        }
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
+       trace_performance_since(start, "diff-files");
        return 0;
 }
 
@@ -140,235 +258,312 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
 /* A file entry went away or appeared */
 static void diff_index_show_file(struct rev_info *revs,
                                 const char *prefix,
-                                struct cache_entry *ce,
-                                unsigned char *sha1, unsigned int mode)
+                                const struct cache_entry *ce,
+                                const struct object_id *oid, int oid_valid,
+                                unsigned int mode,
+                                unsigned dirty_submodule)
 {
-       diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
-                      sha1, ce->name, NULL);
+       diff_addremove(&revs->diffopt, prefix[0], mode,
+                      oid, oid_valid, ce->name, dirty_submodule);
 }
 
-static int get_stat_data(struct cache_entry *ce,
-                        unsigned char **sha1p,
+static int get_stat_data(const struct cache_entry *ce,
+                        const struct object_id **oidp,
                         unsigned int *modep,
-                        int cached, int match_missing)
+                        int cached, int match_missing,
+                        unsigned *dirty_submodule, struct diff_options *diffopt)
 {
-       unsigned char *sha1 = ce->sha1;
+       const struct object_id *oid = &ce->oid;
        unsigned int mode = ce->ce_mode;
 
-       if (!cached) {
-               static unsigned char no_sha1[20];
+       if (!cached && !ce_uptodate(ce)) {
                int changed;
                struct stat st;
-               if (lstat(ce->name, &st) < 0) {
-                       if (errno == ENOENT && match_missing) {
-                               *sha1p = sha1;
+               changed = check_removed(ce, &st);
+               if (changed < 0)
+                       return -1;
+               else if (changed) {
+                       if (match_missing) {
+                               *oidp = oid;
                                *modep = mode;
                                return 0;
                        }
                        return -1;
                }
-               changed = ce_match_stat(ce, &st, 0);
+               changed = match_stat_with_submodule(diffopt, ce, &st,
+                                                   0, dirty_submodule);
                if (changed) {
-                       mode = create_ce_mode(st.st_mode);
-                       if (!trust_executable_bit && S_ISREG(st.st_mode))
-                               mode = ce->ce_mode;
-                       sha1 = no_sha1;
+                       mode = ce_mode_from_stat(ce, st.st_mode);
+                       oid = &null_oid;
                }
        }
 
-       *sha1p = sha1;
+       *oidp = oid;
        *modep = mode;
        return 0;
 }
 
 static void show_new_file(struct rev_info *revs,
-                         struct cache_entry *new,
+                         const struct cache_entry *new_file,
                          int cached, int match_missing)
 {
-       unsigned char *sha1;
+       const struct object_id *oid;
        unsigned int mode;
+       unsigned dirty_submodule = 0;
 
-       /* New file in the index: it might actually be different in
-        * the working copy.
+       /*
+        * New file in the index: it might actually be different in
+        * the working tree.
         */
-       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
+       if (get_stat_data(new_file, &oid, &mode, cached, match_missing,
+           &dirty_submodule, &revs->diffopt) < 0)
                return;
 
-       diff_index_show_file(revs, "+", new, sha1, mode);
+       diff_index_show_file(revs, "+", new_file, oid, !is_null_oid(oid), mode, dirty_submodule);
 }
 
 static int show_modified(struct rev_info *revs,
-                        struct cache_entry *old,
-                        struct cache_entry *new,
+                        const struct cache_entry *old_entry,
+                        const struct cache_entry *new_entry,
                         int report_missing,
                         int cached, int match_missing)
 {
        unsigned int mode, oldmode;
-       unsigned char *sha1;
+       const struct object_id *oid;
+       unsigned dirty_submodule = 0;
 
-       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+       if (get_stat_data(new_entry, &oid, &mode, cached, match_missing,
+                         &dirty_submodule, &revs->diffopt) < 0) {
                if (report_missing)
-                       diff_index_show_file(revs, "-", old,
-                                            old->sha1, old->ce_mode);
+                       diff_index_show_file(revs, "-", old_entry,
+                                            &old_entry->oid, 1, old_entry->ce_mode,
+                                            0);
                return -1;
        }
 
        if (revs->combine_merges && !cached &&
-           (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) {
+           (oidcmp(oid, &old_entry->oid) || oidcmp(&old_entry->oid, &new_entry->oid))) {
                struct combine_diff_path *p;
-               int pathlen = ce_namelen(new);
+               int pathlen = ce_namelen(new_entry);
 
                p = xmalloc(combine_diff_path_size(2, pathlen));
                p->path = (char *) &p->parent[2];
                p->next = NULL;
-               p->len = pathlen;
-               memcpy(p->path, new->name, pathlen);
+               memcpy(p->path, new_entry->name, pathlen);
                p->path[pathlen] = 0;
-               p->mode = ntohl(mode);
-               hashclr(p->sha1);
+               p->mode = mode;
+               oidclr(&p->oid);
                memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
                p->parent[0].status = DIFF_STATUS_MODIFIED;
-               p->parent[0].mode = ntohl(new->ce_mode);
-               hashcpy(p->parent[0].sha1, new->sha1);
+               p->parent[0].mode = new_entry->ce_mode;
+               oidcpy(&p->parent[0].oid, &new_entry->oid);
                p->parent[1].status = DIFF_STATUS_MODIFIED;
-               p->parent[1].mode = ntohl(old->ce_mode);
-               hashcpy(p->parent[1].sha1, old->sha1);
+               p->parent[1].mode = old_entry->ce_mode;
+               oidcpy(&p->parent[1].oid, &old_entry->oid);
                show_combined_diff(p, 2, revs->dense_combined_merges, revs);
                free(p);
                return 0;
        }
 
-       oldmode = old->ce_mode;
-       if (mode == oldmode && !hashcmp(sha1, old->sha1) &&
-           !revs->diffopt.find_copies_harder)
+       oldmode = old_entry->ce_mode;
+       if (mode == oldmode && !oidcmp(oid, &old_entry->oid) && !dirty_submodule &&
+           !revs->diffopt.flags.find_copies_harder)
                return 0;
 
-       mode = ntohl(mode);
-       oldmode = ntohl(oldmode);
-
        diff_change(&revs->diffopt, oldmode, mode,
-                   old->sha1, sha1, old->name, NULL);
+                   &old_entry->oid, oid, 1, !is_null_oid(oid),
+                   old_entry->name, 0, dirty_submodule);
        return 0;
 }
 
-static int diff_cache(struct rev_info *revs,
-                     struct cache_entry **ac, int entries,
-                     const char **pathspec,
-                     int cached, int match_missing)
+/*
+ * This gets a mix of an existing index and a tree, one pathname entry
+ * at a time. The index entry may be a single stage-0 one, but it could
+ * also be multiple unmerged entries (in which case idx_pos/idx_nr will
+ * give you the position and number of entries in the index).
+ */
+static void do_oneway_diff(struct unpack_trees_options *o,
+                          const struct cache_entry *idx,
+                          const struct cache_entry *tree)
 {
-       while (entries) {
-               struct cache_entry *ce = *ac;
-               int same = (entries > 1) && ce_same_name(ce, ac[1]);
-
-               if (!ce_path_match(ce, pathspec))
-                       goto skip_entry;
-
-               switch (ce_stage(ce)) {
-               case 0:
-                       /* No stage 1 entry? That means it's a new file */
-                       if (!same) {
-                               show_new_file(revs, ce, cached, match_missing);
-                               break;
-                       }
-                       /* Show difference between old and new */
-                       show_modified(revs,ac[1], ce, 1,
-                                     cached, match_missing);
-                       break;
-               case 1:
-                       /* No stage 3 (merge) entry?
-                        * That means it's been deleted.
-                        */
-                       if (!same) {
-                               diff_index_show_file(revs, "-", ce,
-                                                    ce->sha1, ce->ce_mode);
-                               break;
-                       }
-                       /* We come here with ce pointing at stage 1
-                        * (original tree) and ac[1] pointing at stage
-                        * 3 (unmerged).  show-modified with
-                        * report-missing set to false does not say the
-                        * file is deleted but reports true if work
-                        * tree does not have it, in which case we
-                        * fall through to report the unmerged state.
-                        * Otherwise, we show the differences between
-                        * the original tree and the work tree.
-                        */
-                       if (!cached &&
-                           !show_modified(revs, ce, ac[1], 0,
-                                          cached, match_missing))
-                               break;
-                       diff_unmerge(&revs->diffopt, ce->name,
-                                    ntohl(ce->ce_mode), ce->sha1);
-                       break;
-               case 3:
-                       diff_unmerge(&revs->diffopt, ce->name,
-                                    0, null_sha1);
-                       break;
+       struct rev_info *revs = o->unpack_data;
+       int match_missing, cached;
 
-               default:
-                       die("impossible cache entry stage");
-               }
+       /*
+        * i-t-a entries do not actually exist in the index (if we're
+        * looking at its content)
+        */
+       if (o->index_only &&
+           revs->diffopt.ita_invisible_in_index &&
+           idx && ce_intent_to_add(idx)) {
+               idx = NULL;
+               if (!tree)
+                       return; /* nothing to diff.. */
+       }
 
-skip_entry:
-               /*
-                * Ignore all the different stages for this file,
-                * we've handled the relevant cases now.
-                */
-               do {
-                       ac++;
-                       entries--;
-               } while (entries && ce_same_name(ce, ac[0]));
+       /* if the entry is not checked out, don't examine work tree */
+       cached = o->index_only ||
+               (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
+       /*
+        * Backward compatibility wart - "diff-index -m" does
+        * not mean "do not ignore merges", but "match_missing".
+        *
+        * But with the revision flag parsing, that's found in
+        * "!revs->ignore_merges".
+        */
+       match_missing = !revs->ignore_merges;
+
+       if (cached && idx && ce_stage(idx)) {
+               struct diff_filepair *pair;
+               pair = diff_unmerge(&revs->diffopt, idx->name);
+               if (tree)
+                       fill_filespec(pair->one, &tree->oid, 1,
+                                     tree->ce_mode);
+               return;
        }
-       return 0;
+
+       /*
+        * Something added to the tree?
+        */
+       if (!tree) {
+               show_new_file(revs, idx, cached, match_missing);
+               return;
+       }
+
+       /*
+        * Something removed from the tree?
+        */
+       if (!idx) {
+               diff_index_show_file(revs, "-", tree, &tree->oid, 1,
+                                    tree->ce_mode, 0);
+               return;
+       }
+
+       /* Show difference between old and new */
+       show_modified(revs, tree, idx, 1, cached, match_missing);
 }
 
 /*
- * This turns all merge entries into "stage 3". That guarantees that
- * when we read in the new tree (into "stage 1"), we won't lose sight
- * of the fact that we had unmerged entries.
+ * The unpack_trees() interface is designed for merging, so
+ * the different source entries are designed primarily for
+ * the source trees, with the old index being really mainly
+ * used for being replaced by the result.
+ *
+ * For diffing, the index is more important, and we only have a
+ * single tree.
+ *
+ * We're supposed to advance o->pos to skip what we have already processed.
+ *
+ * This wrapper makes it all more readable, and takes care of all
+ * the fairly complex unpack_trees() semantic requirements, including
+ * the skipping, the path matching, the type conflict cases etc.
  */
-static void mark_merge_entries(void)
+static int oneway_diff(const struct cache_entry * const *src,
+                      struct unpack_trees_options *o)
 {
-       int i;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-               ce->ce_flags |= htons(CE_STAGEMASK);
+       const struct cache_entry *idx = src[0];
+       const struct cache_entry *tree = src[1];
+       struct rev_info *revs = o->unpack_data;
+
+       /*
+        * Unpack-trees generates a DF/conflict entry if
+        * there was a directory in the index and a tree
+        * in the tree. From a diff standpoint, that's a
+        * delete of the tree and a create of the file.
+        */
+       if (tree == o->df_conflict_entry)
+               tree = NULL;
+
+       if (ce_path_match(idx ? idx : tree, &revs->prune_data, NULL)) {
+               do_oneway_diff(o, idx, tree);
+               if (diff_can_quit_early(&revs->diffopt)) {
+                       o->exiting_early = 1;
+                       return -1;
+               }
        }
+
+       return 0;
 }
 
-int run_diff_index(struct rev_info *revs, int cached)
+static int diff_cache(struct rev_info *revs,
+                     const struct object_id *tree_oid,
+                     const char *tree_name,
+                     int cached)
 {
-       int ret;
-       struct object *ent;
        struct tree *tree;
-       const char *tree_name;
-       int match_missing = 0;
+       struct tree_desc t;
+       struct unpack_trees_options opts;
 
-       /* 
-        * Backward compatibility wart - "diff-index -m" does
-        * not mean "do not ignore merges", but totally different.
-        */
-       if (!revs->ignore_merges)
-               match_missing = 1;
+       tree = parse_tree_indirect(tree_oid);
+       if (!tree)
+               return error("bad tree object %s",
+                            tree_name ? tree_name : oid_to_hex(tree_oid));
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.index_only = cached;
+       opts.diff_index_cached = (cached &&
+                                 !revs->diffopt.flags.find_copies_harder);
+       opts.merge = 1;
+       opts.fn = oneway_diff;
+       opts.unpack_data = revs;
+       opts.src_index = &the_index;
+       opts.dst_index = NULL;
+       opts.pathspec = &revs->diffopt.pathspec;
+       opts.pathspec->recursive = 1;
+
+       init_tree_desc(&t, tree->buffer, tree->size);
+       return unpack_trees(1, &t, &opts);
+}
 
-       if (read_cache() < 0) {
-               perror("read_cache");
-               return -1;
-       }
-       mark_merge_entries();
+int run_diff_index(struct rev_info *revs, int cached)
+{
+       struct object_array_entry *ent;
+       uint64_t start = getnanotime();
 
-       ent = revs->pending.objects[0].item;
-       tree_name = revs->pending.objects[0].name;
-       tree = parse_tree_indirect(ent->sha1);
-       if (!tree)
-               return error("bad tree object %s", tree_name);
-       if (read_tree(tree, 1, revs->prune_data))
-               return error("unable to read tree object %s", tree_name);
-       ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
-                        cached, match_missing);
+       if (revs->pending.nr != 1)
+               BUG("run_diff_index must be passed exactly one tree");
+
+       ent = revs->pending.objects;
+       if (diff_cache(revs, &ent->item->oid, ent->name, cached))
+               exit(128);
+
+       diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
+       diffcore_fix_diff_index(&revs->diffopt);
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
-       return ret;
+       trace_performance_since(start, "diff-index");
+       return 0;
+}
+
+int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt)
+{
+       struct rev_info revs;
+
+       init_revisions(&revs, NULL);
+       copy_pathspec(&revs.prune_data, &opt->pathspec);
+       revs.diffopt = *opt;
+
+       if (diff_cache(&revs, tree_oid, NULL, 1))
+               exit(128);
+       return 0;
+}
+
+int index_differs_from(const char *def, const struct diff_flags *flags,
+                      int ita_invisible_in_index)
+{
+       struct rev_info rev;
+       struct setup_revision_opt opt;
+
+       init_revisions(&rev, NULL);
+       memset(&opt, 0, sizeof(opt));
+       opt.def = def;
+       setup_revisions(0, NULL, &rev, &opt);
+       rev.diffopt.flags.quick = 1;
+       rev.diffopt.flags.exit_with_status = 1;
+       if (flags)
+               diff_flags_or(&rev.diffopt.flags, flags);
+       rev.diffopt.ita_invisible_in_index = ita_invisible_in_index;
+       run_diff_index(&rev, 1);
+       object_array_clear(&rev.pending);
+       return (rev.diffopt.flags.has_changes != 0);
 }