expose a helper function peel_to_type().
[gitweb.git] / diff-lib.c
index da5571302df6ed418874fd4d7423853a7de5b52c..03eaa7cef35dfaec0a8f61b2b96892e5604ed731 100644 (file)
@@ -9,6 +9,7 @@
 #include "revision.h"
 #include "cache-tree.h"
 #include "path-list.h"
+#include "unpack-trees.h"
 
 /*
  * diff-files
@@ -37,7 +38,7 @@ static int get_mode(const char *path, int *mode)
        if (!path || !strcmp(path, "/dev/null"))
                *mode = 0;
        else if (!strcmp(path, "-"))
-               *mode = ntohl(create_ce_mode(0666));
+               *mode = create_ce_mode(0666);
        else if (stat(path, &st))
                return error("Could not access '%s'", path);
        else
@@ -121,7 +122,7 @@ static int queue_diff(struct diff_options *o,
        } else {
                struct diff_filespec *d1, *d2;
 
-               if (o->reverse_diff) {
+               if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
                        unsigned tmp;
                        const char *tmp_c;
                        tmp = mode1; mode1 = mode2; mode2 = tmp;
@@ -173,9 +174,10 @@ static int is_in_index(const char *path)
 }
 
 static int handle_diff_files_args(struct rev_info *revs,
-               int argc, const char **argv, int *silent)
+                                 int argc, const char **argv,
+                                 unsigned int *options)
 {
-       *silent = 0;
+       *options = 0;
 
        /* revs->max_count == -2 means --no-index */
        while (1 < argc && argv[1][0] == '-') {
@@ -188,11 +190,11 @@ static int handle_diff_files_args(struct rev_info *revs,
                else if (!strcmp(argv[1], "-n") ||
                                !strcmp(argv[1], "--no-index")) {
                        revs->max_count = -2;
-                       revs->diffopt.exit_with_status = 1;
-                       revs->diffopt.no_index = 1;
+                       DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
+                       DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
                }
                else if (!strcmp(argv[1], "-q"))
-                       *silent = 1;
+                       *options |= DIFF_SILENT_ON_REMOVED;
                else
                        return error("invalid option: %s", argv[1]);
                argv++; argc--;
@@ -207,7 +209,7 @@ static int handle_diff_files_args(struct rev_info *revs,
                if (!is_in_index(revs->diffopt.paths[0]) ||
                                        !is_in_index(revs->diffopt.paths[1])) {
                        revs->max_count = -2;
-                       revs->diffopt.no_index = 1;
+                       DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
                }
        }
 
@@ -230,7 +232,7 @@ static int handle_diff_files_args(struct rev_info *revs,
 static int is_outside_repo(const char *path, int nongit, const char *prefix)
 {
        int i;
-       if (nongit || !strcmp(path, "-") || path[0] == '/')
+       if (nongit || !strcmp(path, "-") || is_absolute_path(path))
                return 1;
        if (prefixcmp(path, "../"))
                return 0;
@@ -258,7 +260,7 @@ int setup_diff_no_index(struct rev_info *revs,
                        break;
                } else if (i < argc - 3 && !strcmp(argv[i], "--no-index")) {
                        i = argc - 3;
-                       revs->diffopt.exit_with_status = 1;
+                       DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
                        break;
                }
        if (argc != i + 2 || (!is_outside_repo(argv[i + 1], nongit, prefix) &&
@@ -296,7 +298,7 @@ int setup_diff_no_index(struct rev_info *revs,
        else
                revs->diffopt.paths = argv + argc - 2;
        revs->diffopt.nr_paths = 2;
-       revs->diffopt.no_index = 1;
+       DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
        revs->max_count = -2;
        if (diff_setup_done(&revs->diffopt) < 0)
                die("diff_setup_done failed");
@@ -305,12 +307,12 @@ int setup_diff_no_index(struct rev_info *revs,
 
 int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
 {
-       int silent_on_removed;
+       unsigned int options;
 
-       if (handle_diff_files_args(revs, argc, argv, &silent_on_removed))
+       if (handle_diff_files_args(revs, argc, argv, &options))
                return -1;
 
-       if (revs->diffopt.no_index) {
+       if (DIFF_OPT_TST(&revs->diffopt, NO_INDEX)) {
                if (revs->diffopt.nr_paths != 2)
                        return error("need two files/directories with --no-index");
                if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
@@ -329,13 +331,16 @@ int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
                perror("read_cache");
                return -1;
        }
-       return run_diff_files(revs, silent_on_removed);
+       return run_diff_files(revs, options);
 }
 
-int run_diff_files(struct rev_info *revs, int silent_on_removed)
+int run_diff_files(struct rev_info *revs, unsigned int option)
 {
        int entries, i;
        int diff_unmerged_stage = revs->max_count;
+       int silent_on_removed = option & DIFF_SILENT_ON_REMOVED;
+       unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
+                             ? CE_MATCH_RACY_IS_DIRTY : 0);
 
        if (diff_unmerged_stage < 0)
                diff_unmerged_stage = 2;
@@ -346,7 +351,8 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                struct cache_entry *ce = active_cache[i];
                int changed;
 
-               if (revs->diffopt.quiet && revs->diffopt.has_changes)
+               if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
+                       DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
                        break;
 
                if (!ce_path_match(ce, revs->prune_data))
@@ -379,7 +385,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                                        continue;
                        }
                        else
-                               dpath->mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+                               dpath->mode = ce_mode_from_stat(ce, st.st_mode);
 
                        while (i < entries) {
                                struct cache_entry *nce = active_cache[i];
@@ -393,10 +399,10 @@ 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 = ntohl(ce_mode_from_stat(nce, mode));
+                                       dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
                                        dpath->parent[stage-2].status =
                                                DIFF_STATUS_MODIFIED;
                                }
@@ -430,6 +436,8 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                                continue;
                }
 
+               if (ce_uptodate(ce))
+                       continue;
                if (lstat(ce->name, &st) < 0) {
                        if (errno != ENOENT && errno != ENOTDIR) {
                                perror(ce->name);
@@ -437,15 +445,15 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                        }
                        if (silent_on_removed)
                                continue;
-                       diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
+                       diff_addremove(&revs->diffopt, '-', ce->ce_mode,
                                       ce->sha1, ce->name, NULL);
                        continue;
                }
-               changed = ce_match_stat(ce, &st, 0);
-               if (!changed && !revs->diffopt.find_copies_harder)
+               changed = ce_match_stat(ce, &st, ce_option);
+               if (!changed && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
                        continue;
-               oldmode = ntohl(ce->ce_mode);
-               newmode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+               oldmode = ce->ce_mode;
+               newmode = ce_mode_from_stat(ce, st.st_mode);
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
                            ce->name, NULL);
@@ -466,7 +474,7 @@ static void diff_index_show_file(struct rev_info *revs,
                                 struct cache_entry *ce,
                                 unsigned char *sha1, unsigned int mode)
 {
-       diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
+       diff_addremove(&revs->diffopt, prefix[0], mode,
                       sha1, ce->name, NULL);
 }
 
@@ -545,14 +553,14 @@ static int show_modified(struct rev_info *revs,
                p->len = pathlen;
                memcpy(p->path, new->name, pathlen);
                p->path[pathlen] = 0;
-               p->mode = ntohl(mode);
+               p->mode = mode;
                hashclr(p->sha1);
                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);
+               p->parent[0].mode = new->ce_mode;
                hashcpy(p->parent[0].sha1, new->sha1);
                p->parent[1].status = DIFF_STATUS_MODIFIED;
-               p->parent[1].mode = ntohl(old->ce_mode);
+               p->parent[1].mode = old->ce_mode;
                hashcpy(p->parent[1].sha1, old->sha1);
                show_combined_diff(p, 2, revs->dense_combined_merges, revs);
                free(p);
@@ -561,121 +569,157 @@ static int show_modified(struct rev_info *revs,
 
        oldmode = old->ce_mode;
        if (mode == oldmode && !hashcmp(sha1, old->sha1) &&
-           !revs->diffopt.find_copies_harder)
+           !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
                return 0;
 
-       mode = ntohl(mode);
-       oldmode = ntohl(oldmode);
-
        diff_change(&revs->diffopt, oldmode, mode,
                    old->sha1, sha1, old->name, NULL);
        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 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.
+ */
+static void mark_merge_entries(void)
 {
-       while (entries) {
-               struct cache_entry *ce = *ac;
-               int same = (entries > 1) && ce_same_name(ce, ac[1]);
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= CE_STAGEMASK;
+       }
+}
 
-               if (revs->diffopt.quiet && revs->diffopt.has_changes)
-                       break;
+/*
+ * 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,
+       struct cache_entry *idx,
+       struct cache_entry *tree,
+       int idx_pos, int idx_nr)
+{
+       struct rev_info *revs = o->unpack_data;
+       int match_missing, cached;
+
+       /*
+        * 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".
+        */
+       cached = o->index_only;
+       match_missing = !revs->ignore_merges;
+
+       if (cached && idx && ce_stage(idx)) {
+               if (tree)
+                       diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1);
+               return;
+       }
+
+       /*
+        * 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->sha1, tree->ce_mode);
+               return;
+       }
+
+       /* Show difference between old and new */
+       show_modified(revs, tree, idx, 1, cached, match_missing);
+}
+
+/*
+ * Count how many index entries go with the first one
+ */
+static inline int count_skip(const struct cache_entry *src, int pos)
+{
+       int skip = 1;
 
-               if (!ce_path_match(ce, pathspec))
-                       goto skip_entry;
+       /* We can only have multiple entries if the first one is not stage-0 */
+       if (ce_stage(src)) {
+               struct cache_entry **p = active_cache + pos;
+               int namelen = ce_namelen(src);
 
-               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);
+               for (;;) {
+                       const struct cache_entry *ce;
+                       pos++;
+                       if (pos >= active_nr)
                                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);
+                       ce = *++p;
+                       if (ce_namelen(ce) != namelen)
                                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))
+                       if (memcmp(ce->name, src->name, namelen))
                                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;
-
-               default:
-                       die("impossible cache entry stage");
+                       skip++;
                }
-
-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]));
        }
-       return 0;
+       return skip;
 }
 
 /*
- * 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 return how many index entries we want to skip.
+ *
+ * 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(struct cache_entry **src,
+       struct unpack_trees_options *o,
+       int index_pos)
 {
-       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);
-       }
+       int skip = 0;
+       struct cache_entry *idx = src[0];
+       struct cache_entry *tree = src[1];
+       struct rev_info *revs = o->unpack_data;
+
+       if (index_pos >= 0)
+               skip = count_skip(idx, index_pos);
+
+       /*
+        * 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))
+               do_oneway_diff(o, idx, tree, index_pos, skip);
+
+       return skip;
 }
 
 int run_diff_index(struct rev_info *revs, int cached)
 {
-       int ret;
        struct object *ent;
        struct tree *tree;
        const char *tree_name;
-       int match_missing = 0;
-
-       /*
-        * Backward compatibility wart - "diff-index -m" does
-        * not mean "do not ignore merges", but totally different.
-        */
-       if (!revs->ignore_merges)
-               match_missing = 1;
+       struct unpack_trees_options opts;
+       struct tree_desc t;
 
        mark_merge_entries();
 
@@ -684,13 +728,20 @@ int run_diff_index(struct rev_info *revs, int cached)
        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);
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.index_only = cached;
+       opts.merge = 1;
+       opts.fn = oneway_diff;
+       opts.unpack_data = revs;
+
+       init_tree_desc(&t, tree->buffer, tree->size);
+       unpack_trees(1, &t, &opts);
+
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
-       return ret;
+       return 0;
 }
 
 int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
@@ -700,6 +751,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        int i;
        struct cache_entry **dst;
        struct cache_entry *last = NULL;
+       struct unpack_trees_options opts;
+       struct tree_desc t;
 
        /*
         * This is used by git-blame to run diff-cache internally;
@@ -716,8 +769,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
                        cache_tree_invalidate_path(active_cache_tree,
                                                   ce->name);
                        last = ce;
-                       ce->ce_mode = 0;
-                       ce->ce_flags &= ~htons(CE_STAGEMASK);
+                       ce->ce_flags |= CE_REMOVE;
                }
                *dst++ = ce;
        }
@@ -728,8 +780,15 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        tree = parse_tree_indirect(tree_sha1);
        if (!tree)
                die("bad tree object %s", sha1_to_hex(tree_sha1));
-       if (read_tree(tree, 1, opt->paths))
-               return error("unable to read tree %s", sha1_to_hex(tree_sha1));
-       return diff_cache(&revs, active_cache, active_nr, revs.prune_data,
-                         1, 0);
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.index_only = 1;
+       opts.merge = 1;
+       opts.fn = oneway_diff;
+       opts.unpack_data = &revs;
+
+       init_tree_desc(&t, tree->buffer, tree->size);
+       unpack_trees(1, &t, &opts);
+       return 0;
 }