Merge branch 'sb/describe-blob'
authorJunio C Hamano <gitster@pobox.com>
Thu, 28 Dec 2017 22:08:50 +0000 (14:08 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 28 Dec 2017 22:08:50 +0000 (14:08 -0800)
"git describe" was taught to dig trees deeper to find a
<commit-ish>:<path> that refers to a given blob object.

* sb/describe-blob:
builtin/describe.c: describe a blob
builtin/describe.c: factor out describe_commit
builtin/describe.c: print debug statements earlier
builtin/describe.c: rename `oid` to avoid variable shadowing
revision.h: introduce blob/tree walking in order of the commits
list-objects.c: factor out traverse_trees_and_blobs
t6120: fix typo in test name

1  2 
Documentation/rev-list-options.txt
builtin/describe.c
list-objects.c
revision.c
revision.h
index 8d8b7f492a44aa0b0b1d9d48fa25dc5293144087,9066e0c7777afbbba3f2b58eb15e14c31e0b854d..22f5c9b43dd01c02710e44e36ee6a9dd2afc9b60
@@@ -686,6 -686,11 +686,11 @@@ ifdef::git-rev-list[
        all object IDs which I need to download if I have the commit
        object _bar_ but not _foo_''.
  
+ --in-commit-order::
+       Print tree and blob ids in order of the commits. The tree
+       and blob ids are printed after they are first referenced
+       by a commit.
  --objects-edge::
        Similar to `--objects`, but also print the IDs of excluded
        commits prefixed with a ``-'' character.  This is used by
  --unpacked::
        Only useful with `--objects`; print the object IDs that are not
        in packs.
 +
 +--filter=<filter-spec>::
 +      Only useful with one of the `--objects*`; omits objects (usually
 +      blobs) from the list of printed objects.  The '<filter-spec>'
 +      may be one of the following:
 ++
 +The form '--filter=blob:none' omits all blobs.
 ++
 +The form '--filter=blob:limit=<n>[kmg]' omits blobs larger than n bytes
 +or units.  n may be zero.  The suffixes k, m, and g can be used to name
 +units in KiB, MiB, or GiB.  For example, 'blob:limit=1k' is the same
 +as 'blob:limit=1024'.
 ++
 +The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout
 +specification contained in the blob (or blob-expression) '<blob-ish>'
 +to omit blobs that would not be not required for a sparse checkout on
 +the requested refs.
 ++
 +The form '--filter=sparse:path=<path>' similarly uses a sparse-checkout
 +specification contained in <path>.
 +
 +--no-filter::
 +      Turn off any previous `--filter=` argument.
 +
 +--filter-print-omitted::
 +      Only useful with `--filter=`; prints a list of the objects omitted
 +      by the filter.  Object IDs are prefixed with a ``~'' character.
 +
 +--missing=<missing-action>::
 +      A debug option to help with future "partial clone" development.
 +      This option specifies how missing objects are handled.
 ++
 +The form '--missing=error' requests that rev-list stop with an error if
 +a missing object is encountered.  This is the default action.
 ++
 +The form '--missing=allow-any' will allow object traversal to continue
 +if a missing object is encountered.  Missing objects will silently be
 +omitted from the results.
 ++
 +The form '--missing=print' is like 'allow-any', but will also print a
 +list of the missing objects.  Object IDs are prefixed with a ``?'' character.
  endif::git-rev-list[]
  
  --no-walk[=(sorted|unsorted)]::
diff --combined builtin/describe.c
index e14e162ef6ef829072f01f89ab2c797256a6861d,5b4bfaba3f76924f48593e043a8ca747d179009b..3b0b204b1e495626f3490d62f281291ee039ba42
@@@ -3,15 -3,17 +3,18 @@@
  #include "lockfile.h"
  #include "commit.h"
  #include "tag.h"
+ #include "blob.h"
  #include "refs.h"
  #include "builtin.h"
  #include "exec_cmd.h"
  #include "parse-options.h"
 +#include "revision.h"
  #include "diff.h"
  #include "hashmap.h"
  #include "argv-array.h"
  #include "run-command.h"
+ #include "revision.h"
+ #include "list-objects.h"
  
  #define MAX_TAGS      (FLAG_BITS - 1)
  
@@@ -181,7 -183,7 +184,7 @@@ static int get_name(const char *path, c
        }
  
        /* Is it annotated? */
 -      if (!peel_ref(path, peeled.hash)) {
 +      if (!peel_ref(path, &peeled)) {
                is_annotated = !!oidcmp(oid, &peeled);
        } else {
                oidcpy(&peeled, oid);
@@@ -256,7 -258,7 +259,7 @@@ static unsigned long finish_depth_compu
        return seen_commits;
  }
  
- static void display_name(struct commit_name *n)
+ static void append_name(struct commit_name *n, struct strbuf *dst)
  {
        if (n->prio == 2 && !n->tag) {
                n->tag = lookup_tag(&n->oid);
        }
  
        if (n->tag)
-               printf("%s", n->tag->tag);
+               strbuf_addstr(dst, n->tag->tag);
        else
-               printf("%s", n->path);
+               strbuf_addstr(dst, n->path);
  }
  
- static void show_suffix(int depth, const struct object_id *oid)
+ static void append_suffix(int depth, const struct object_id *oid, struct strbuf *dst)
  {
-       printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
+       strbuf_addf(dst, "-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
  }
  
- static void describe(const char *arg, int last_one)
+ static void describe_commit(struct object_id *oid, struct strbuf *dst)
  {
-       struct object_id oid;
        struct commit *cmit, *gave_up_on = NULL;
        struct commit_list *list;
        struct commit_name *n;
        unsigned long seen_commits = 0;
        unsigned int unannotated_cnt = 0;
  
-       if (get_oid(arg, &oid))
-               die(_("Not a valid object name %s"), arg);
-       cmit = lookup_commit_reference(&oid);
-       if (!cmit)
-               die(_("%s is not a valid '%s' object"), arg, commit_type);
+       cmit = lookup_commit_reference(oid);
  
        n = find_commit_name(&cmit->object.oid);
        if (n && (tags || all || n->prio == 2)) {
                /*
                 * Exact match to an existing ref.
                 */
-               display_name(n);
+               append_name(n, dst);
                if (longformat)
-                       show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid);
+                       append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst);
                if (suffix)
-                       printf("%s", suffix);
-               printf("\n");
+                       strbuf_addstr(dst, suffix);
                return;
        }
  
        if (!max_candidates)
                die(_("no tag exactly matches '%s'"), oid_to_hex(&cmit->object.oid));
        if (debug)
-               fprintf(stderr, _("searching to describe %s\n"), arg);
+               fprintf(stderr, _("No exact match on refs or tags, searching to describe\n"));
  
        if (!have_util) {
                struct hashmap_iter iter;
        }
  
        if (!match_cnt) {
-               struct object_id *oid = &cmit->object.oid;
+               struct object_id *cmit_oid = &cmit->object.oid;
                if (always) {
-                       printf("%s", find_unique_abbrev(oid->hash, abbrev));
+                       strbuf_addstr(dst, find_unique_abbrev(cmit_oid->hash, abbrev));
                        if (suffix)
-                               printf("%s", suffix);
-                       printf("\n");
+                               strbuf_addstr(dst, suffix);
                        return;
                }
                if (unannotated_cnt)
                        die(_("No annotated tags can describe '%s'.\n"
                            "However, there were unannotated tags: try --tags."),
-                           oid_to_hex(oid));
+                           oid_to_hex(cmit_oid));
                else
                        die(_("No tags can describe '%s'.\n"
                            "Try --always, or create some tags."),
-                           oid_to_hex(oid));
+                           oid_to_hex(cmit_oid));
        }
  
        QSORT(all_matches, match_cnt, compare_pt);
                }
        }
  
-       display_name(all_matches[0].name);
+       append_name(all_matches[0].name, dst);
        if (abbrev)
-               show_suffix(all_matches[0].depth, &cmit->object.oid);
+               append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
        if (suffix)
-               printf("%s", suffix);
-       printf("\n");
+               strbuf_addstr(dst, suffix);
+ }
+ struct process_commit_data {
+       struct object_id current_commit;
+       struct object_id looking_for;
+       struct strbuf *dst;
+       struct rev_info *revs;
+ };
+ static void process_commit(struct commit *commit, void *data)
+ {
+       struct process_commit_data *pcd = data;
+       pcd->current_commit = commit->object.oid;
+ }
+ static void process_object(struct object *obj, const char *path, void *data)
+ {
+       struct process_commit_data *pcd = data;
+       if (!oidcmp(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
+               reset_revision_walk();
+               describe_commit(&pcd->current_commit, pcd->dst);
+               strbuf_addf(pcd->dst, ":%s", path);
+               free_commit_list(pcd->revs->commits);
+               pcd->revs->commits = NULL;
+       }
+ }
+ static void describe_blob(struct object_id oid, struct strbuf *dst)
+ {
+       struct rev_info revs;
+       struct argv_array args = ARGV_ARRAY_INIT;
+       struct process_commit_data pcd = { null_oid, oid, dst, &revs};
+       argv_array_pushl(&args, "internal: The first arg is not parsed",
+               "--objects", "--in-commit-order", "--reverse", "HEAD",
+               NULL);
+       init_revisions(&revs, NULL);
+       if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1)
+               BUG("setup_revisions could not handle all args?");
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       traverse_commit_list(&revs, process_commit, process_object, &pcd);
+       reset_revision_walk();
+ }
+ static void describe(const char *arg, int last_one)
+ {
+       struct object_id oid;
+       struct commit *cmit;
+       struct strbuf sb = STRBUF_INIT;
+       if (debug)
+               fprintf(stderr, _("describe %s\n"), arg);
+       if (get_oid(arg, &oid))
+               die(_("Not a valid object name %s"), arg);
+       cmit = lookup_commit_reference_gently(&oid, 1);
+       if (cmit)
+               describe_commit(&oid, &sb);
+       else if (lookup_blob(&oid))
+               describe_blob(oid, &sb);
+       else
+               die(_("%s is neither a commit nor blob"), arg);
+       puts(sb.buf);
  
        if (!last_one)
                clear_commit_marks(cmit, -1);
+       strbuf_release(&sb);
  }
  
  int cmd_describe(int argc, const char **argv, const char *prefix)
                        }
                } else if (dirty) {
                        static struct lock_file index_lock;
 -                      int fd;
 +                      struct rev_info revs;
 +                      struct argv_array args = ARGV_ARRAY_INIT;
 +                      int fd, result;
  
                        read_cache_preload(NULL);
                        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED,
                        if (0 <= fd)
                                update_index_if_able(&the_index, &index_lock);
  
 -                      if (!cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1,
 -                                          diff_index_args, prefix))
 +                      init_revisions(&revs, prefix);
 +                      argv_array_pushv(&args, diff_index_args);
 +                      if (setup_revisions(args.argc, args.argv, &revs, NULL) != 1)
 +                              BUG("malformed internal diff-index command line");
 +                      result = run_diff_index(&revs, 0);
 +
 +                      if (!diff_result_code(&revs.diffopt, result))
                                suffix = NULL;
                        else
                                suffix = dirty;
diff --combined list-objects.c
index d9e83d05e15d36a4cba49de95ad35d7cdf54c199,4caa6fcb77c5601426792969ff0a1c02b3d18711..0966cdc9fa8dd448aa9955a2fb5324ae8ebbf97d
@@@ -7,21 -7,16 +7,21 @@@
  #include "tree-walk.h"
  #include "revision.h"
  #include "list-objects.h"
 +#include "list-objects-filter.h"
 +#include "list-objects-filter-options.h"
  
  static void process_blob(struct rev_info *revs,
                         struct blob *blob,
                         show_object_fn show,
                         struct strbuf *path,
                         const char *name,
 -                       void *cb_data)
 +                       void *cb_data,
 +                       filter_object_fn filter_fn,
 +                       void *filter_data)
  {
        struct object *obj = &blob->object;
        size_t pathlen;
 +      enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
  
        if (!revs->blob_objects)
                return;
                die("bad blob object");
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
 -      obj->flags |= SEEN;
  
        pathlen = path->len;
        strbuf_addstr(path, name);
 -      show(obj, path->buf, cb_data);
 +      if (filter_fn)
 +              r = filter_fn(LOFS_BLOB, obj,
 +                            path->buf, &path->buf[pathlen],
 +                            filter_data);
 +      if (r & LOFR_MARK_SEEN)
 +              obj->flags |= SEEN;
 +      if (r & LOFR_DO_SHOW)
 +              show(obj, path->buf, cb_data);
        strbuf_setlen(path, pathlen);
  }
  
@@@ -80,9 -69,7 +80,9 @@@ static void process_tree(struct rev_inf
                         show_object_fn show,
                         struct strbuf *base,
                         const char *name,
 -                       void *cb_data)
 +                       void *cb_data,
 +                       filter_object_fn filter_fn,
 +                       void *filter_data)
  {
        struct object *obj = &tree->object;
        struct tree_desc desc;
@@@ -90,7 -77,6 +90,7 @@@
        enum interesting match = revs->diffopt.pathspec.nr == 0 ?
                all_entries_interesting: entry_not_interesting;
        int baselen = base->len;
 +      enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
  
        if (!revs->tree_objects)
                return;
                die("bad tree object %s", oid_to_hex(&obj->oid));
        }
  
 -      obj->flags |= SEEN;
        strbuf_addstr(base, name);
 -      show(obj, base->buf, cb_data);
 +      if (filter_fn)
 +              r = filter_fn(LOFS_BEGIN_TREE, obj,
 +                            base->buf, &base->buf[baselen],
 +                            filter_data);
 +      if (r & LOFR_MARK_SEEN)
 +              obj->flags |= SEEN;
 +      if (r & LOFR_DO_SHOW)
 +              show(obj, base->buf, cb_data);
        if (base->len)
                strbuf_addch(base, '/');
  
                        process_tree(revs,
                                     lookup_tree(entry.oid),
                                     show, base, entry.path,
 -                                   cb_data);
 +                                   cb_data, filter_fn, filter_data);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.oid->hash,
                                        show, base, entry.path,
                        process_blob(revs,
                                     lookup_blob(entry.oid),
                                     show, base, entry.path,
 -                                   cb_data);
 +                                   cb_data, filter_fn, filter_data);
        }
 +
 +      if (filter_fn) {
 +              r = filter_fn(LOFS_END_TREE, obj,
 +                            base->buf, &base->buf[baselen],
 +                            filter_data);
 +              if (r & LOFR_MARK_SEEN)
 +                      obj->flags |= SEEN;
 +              if (r & LOFR_DO_SHOW)
 +                      show(obj, base->buf, cb_data);
 +      }
 +
        strbuf_setlen(base, baselen);
        free_tree_buffer(tree);
  }
@@@ -214,27 -183,15 +214,17 @@@ static void add_pending_tree(struct rev
        add_pending_object(revs, &tree->object, "");
  }
  
- static void do_traverse(struct rev_info *revs,
-                       show_commit_fn show_commit,
-                       show_object_fn show_object,
-                       void *show_data,
-                       filter_object_fn filter_fn,
-                       void *filter_data)
+ static void traverse_trees_and_blobs(struct rev_info *revs,
+                                    struct strbuf *base,
+                                    show_object_fn show_object,
 -                                   void *data)
++                                   void *show_data,
++                                   filter_object_fn filter_fn,
++                                   void *filter_data)
  {
        int i;
-       struct commit *commit;
-       struct strbuf base;
  
-       strbuf_init(&base, PATH_MAX);
-       while ((commit = get_revision(revs)) != NULL) {
-               /*
-                * an uninteresting boundary commit may not have its tree
-                * parsed yet, but we are not going to show them anyway
-                */
-               if (commit->tree)
-                       add_pending_tree(revs, commit->tree);
-               show_commit(commit, show_data);
-       }
+       assert(base->len == 0);
        for (i = 0; i < revs->pending.nr; i++) {
                struct object_array_entry *pending = revs->pending.objects + i;
                struct object *obj = pending->item;
                        continue;
                if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
 -                      show_object(obj, name, data);
 +                      show_object(obj, name, show_data);
                        continue;
                }
                if (!path)
                        path = "";
                if (obj->type == OBJ_TREE) {
                        process_tree(revs, (struct tree *)obj, show_object,
-                                    &base, path, show_data,
 -                                   base, path, data);
++                                   base, path, show_data,
 +                                   filter_fn, filter_data);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
                        process_blob(revs, (struct blob *)obj, show_object,
-                                    &base, path, show_data,
 -                                   base, path, data);
++                                   base, path, show_data,
 +                                   filter_fn, filter_data);
                        continue;
                }
                die("unknown pending object %s (%s)",
                    oid_to_hex(&obj->oid), name);
        }
        object_array_clear(&revs->pending);
-       strbuf_release(&base);
+ }
 -void traverse_commit_list(struct rev_info *revs,
 -                        show_commit_fn show_commit,
 -                        show_object_fn show_object,
 -                        void *data)
++static void do_traverse(struct rev_info *revs,
++                      show_commit_fn show_commit,
++                      show_object_fn show_object,
++                      void *show_data,
++                      filter_object_fn filter_fn,
++                      void *filter_data)
+ {
+       struct commit *commit;
+       struct strbuf csp; /* callee's scratch pad */
+       strbuf_init(&csp, PATH_MAX);
+       while ((commit = get_revision(revs)) != NULL) {
+               /*
+                * an uninteresting boundary commit may not have its tree
+                * parsed yet, but we are not going to show them anyway
+                */
+               if (commit->tree)
+                       add_pending_tree(revs, commit->tree);
 -              show_commit(commit, data);
++              show_commit(commit, show_data);
+               if (revs->tree_blobs_in_commit_order)
+                       /*
+                        * NEEDSWORK: Adding the tree and then flushing it here
+                        * needs a reallocation for each commit. Can we pass the
+                        * tree directory without allocation churn?
+                        */
 -                      traverse_trees_and_blobs(revs, &csp, show_object, data);
++                      traverse_trees_and_blobs(revs, &csp,
++                                               show_object, show_data,
++                                               filter_fn, filter_data);
+       }
 -      traverse_trees_and_blobs(revs, &csp, show_object, data);
 -
++      traverse_trees_and_blobs(revs, &csp,
++                               show_object, show_data,
++                               filter_fn, filter_data);
+       strbuf_release(&csp);
  }
 +
 +void traverse_commit_list(struct rev_info *revs,
 +                        show_commit_fn show_commit,
 +                        show_object_fn show_object,
 +                        void *show_data)
 +{
 +      do_traverse(revs, show_commit, show_object, show_data, NULL, NULL);
 +}
 +
 +void traverse_commit_list_filtered(
 +      struct list_objects_filter_options *filter_options,
 +      struct rev_info *revs,
 +      show_commit_fn show_commit,
 +      show_object_fn show_object,
 +      void *show_data,
 +      struct oidset *omitted)
 +{
 +      filter_object_fn filter_fn = NULL;
 +      filter_free_fn filter_free_fn = NULL;
 +      void *filter_data = NULL;
 +
 +      filter_data = list_objects_filter__init(omitted, filter_options,
 +                                              &filter_fn, &filter_free_fn);
 +      do_traverse(revs, show_commit, show_object, show_data,
 +                  filter_fn, filter_data);
 +      if (filter_data && filter_free_fn)
 +              filter_free_fn(filter_data);
 +}
diff --combined revision.c
index f6a3da5cd969765ada5ef536f08fb0cf4846cd39,9329d4ebbf1e0fd18dfe9d508db41cbc3b97ae3f..72f2b4572ee798f9e17d6f4fbc0d12cfcd2fe221
@@@ -395,16 -395,8 +395,16 @@@ static struct commit *one_relevant_pare
   * if the whole diff is removal of old data, and otherwise
   * REV_TREE_DIFFERENT (of course if the trees are the same we
   * want REV_TREE_SAME).
 - * That means that once we get to REV_TREE_DIFFERENT, we do not
 - * have to look any further.
 + *
 + * The only time we care about the distinction is when
 + * remove_empty_trees is in effect, in which case we care only about
 + * whether the whole change is REV_TREE_NEW, or if there's another type
 + * of change. Which means we can stop the diff early in either of these
 + * cases:
 + *
 + *   1. We're not using remove_empty_trees at all.
 + *
 + *   2. We saw anything except REV_TREE_NEW.
   */
  static int tree_difference = REV_TREE_SAME;
  
@@@ -415,11 -407,10 +415,11 @@@ static void file_add_remove(struct diff
                    const char *fullpath, unsigned dirty_submodule)
  {
        int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
 +      struct rev_info *revs = options->change_fn_data;
  
        tree_difference |= diff;
 -      if (tree_difference == REV_TREE_DIFFERENT)
 -              DIFF_OPT_SET(options, HAS_CHANGES);
 +      if (!revs->remove_empty_trees || tree_difference != REV_TREE_NEW)
 +              options->flags.has_changes = 1;
  }
  
  static void file_change(struct diff_options *options,
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
  {
        tree_difference = REV_TREE_DIFFERENT;
 -      DIFF_OPT_SET(options, HAS_CHANGES);
 +      options->flags.has_changes = 1;
  }
  
  static int rev_compare_tree(struct rev_info *revs,
        }
  
        tree_difference = REV_TREE_SAME;
 -      DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
 +      revs->pruning.flags.has_changes = 0;
        if (diff_tree_oid(&t1->object.oid, &t2->object.oid, "",
                           &revs->pruning) < 0)
                return REV_TREE_DIFFERENT;
@@@ -480,7 -471,7 +480,7 @@@ static int rev_same_tree_as_empty(struc
                return 0;
  
        tree_difference = REV_TREE_SAME;
 -      DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
 +      revs->pruning.flags.has_changes = 0;
        retval = diff_tree_oid(NULL, &t1->object.oid, "", &revs->pruning);
  
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
@@@ -1412,11 -1403,10 +1412,11 @@@ void init_revisions(struct rev_info *re
        revs->abbrev = DEFAULT_ABBREV;
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
 -      DIFF_OPT_SET(&revs->pruning, RECURSIVE);
 -      DIFF_OPT_SET(&revs->pruning, QUICK);
 +      revs->pruning.flags.recursive = 1;
 +      revs->pruning.flags.quick = 1;
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
 +      revs->pruning.change_fn_data = revs;
        revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
        revs->dense = 1;
        revs->prefix = prefix;
@@@ -1832,7 -1822,7 +1832,7 @@@ static int handle_revision_opt(struct r
                revs->simplify_by_decoration = 1;
                revs->limited = 1;
                revs->prune = 1;
 -              load_ref_decorations(DECORATE_SHORT_REFS);
 +              load_ref_decorations(NULL, DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
                revs->sort_order = REV_SORT_BY_COMMIT_DATE;
                revs->topo_order = 1;
                revs->dense = 0;
        } else if (!strcmp(arg, "--show-all")) {
                revs->show_all = 1;
+       } else if (!strcmp(arg, "--in-commit-order")) {
+               revs->tree_blobs_in_commit_order = 1;
        } else if (!strcmp(arg, "--remove-empty")) {
                revs->remove_empty_trees = 1;
        } else if (!strcmp(arg, "--merges")) {
                die("--unpacked=<packfile> no longer supported.");
        } else if (!strcmp(arg, "-r")) {
                revs->diff = 1;
 -              DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
 +              revs->diffopt.flags.recursive = 1;
        } else if (!strcmp(arg, "-t")) {
                revs->diff = 1;
 -              DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
 -              DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);
 +              revs->diffopt.flags.recursive = 1;
 +              revs->diffopt.flags.tree_in_recursive = 1;
        } else if (!strcmp(arg, "-m")) {
                revs->ignore_merges = 0;
        } else if (!strcmp(arg, "-c")) {
                revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_ERE;
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
                revs->grep_filter.ignore_case = 1;
 -              DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
 +              revs->diffopt.flags.pickaxe_ignore_case = 1;
        } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
                revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_FIXED;
        } else if (!strcmp(arg, "--perl-regexp") || !strcmp(arg, "-P")) {
@@@ -2409,7 -2401,7 +2411,7 @@@ int setup_revisions(int argc, const cha
        /* Pickaxe, diff-filter and rename following need diffs */
        if (revs->diffopt.pickaxe ||
            revs->diffopt.filter ||
 -          DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
 +          revs->diffopt.flags.follow_renames)
                revs->diff = 1;
  
        if (revs->topo_order)
        if (revs->prune_data.nr) {
                copy_pathspec(&revs->pruning.pathspec, &revs->prune_data);
                /* Can't prune commits with rename following: the paths change.. */
 -              if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
 +              if (!revs->diffopt.flags.follow_renames)
                        revs->prune = 1;
                if (!revs->full_diff)
                        copy_pathspec(&revs->diffopt.pathspec,
diff --combined revision.h
index 747bce8d8a184003b406d0d3b93d75371c1af5fe,86985d68aa5d69bf1c0ee01fb6498f151fe31c0f..19dc9bdddba20dfa6ef1bdaaee0e82db3bdf8bd8
@@@ -4,7 -4,7 +4,7 @@@
  #include "parse-options.h"
  #include "grep.h"
  #include "notes.h"
 -#include "commit.h"
 +#include "pretty.h"
  #include "diff.h"
  
  /* Remember to update object flag allocation in object.h */
@@@ -121,7 -121,8 +121,8 @@@ struct rev_info 
                        bisect:1,
                        ancestry_path:1,
                        first_parent_only:1,
-                       line_level_traverse:1;
+                       line_level_traverse:1,
+                       tree_blobs_in_commit_order:1;
  
        /* Diff flags */
        unsigned int    diff:1,