Merge branch 'lt/rev-list-interactive'
authorJunio C Hamano <gitster@pobox.com>
Mon, 19 Nov 2007 00:03:24 +0000 (16:03 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 19 Nov 2007 00:03:24 +0000 (16:03 -0800)
* lt/rev-list-interactive:
Fix parent rewriting in --early-output
revision walker: mini clean-up
Enhance --early-output format
Add "--early-output" log flag for interactive GUI use
Simplify topo-sort logic

1  2 
builtin-log.c
builtin-rev-list.c
commit.c
revision.c
diff --combined builtin-log.c
index 197f6eec03df49d4fac044377afd8d0c50e90c81,54ddaad0eccedd74be349d208fecb9a87b143047..e1f1cf67143721933010065130adaba8723b272b
@@@ -55,13 -55,13 +55,13 @@@ static void cmd_log_init(int argc, cons
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
        rev->verbose_header = 1;
 -      rev->diffopt.recursive = 1;
 +      DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
        rev->show_root_diff = default_show_root;
        rev->subject_prefix = fmt_patch_subject_prefix;
        argc = setup_revisions(argc, argv, rev, "HEAD");
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
                rev->always_show_header = 0;
 -      if (rev->diffopt.follow_renames) {
 +      if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
                rev->always_show_header = 0;
                if (rev->diffopt.nr_paths != 1)
                        usage("git logs can only follow renames on one pathname at a time");
        }
  }
  
+ /*
+  * This gives a rough estimate for how many commits we
+  * will print out in the list.
+  */
+ static int estimate_commit_count(struct rev_info *rev, struct commit_list *list)
+ {
+       int n = 0;
+       while (list) {
+               struct commit *commit = list->item;
+               unsigned int flags = commit->object.flags;
+               list = list->next;
+               if (!(flags & (TREESAME | UNINTERESTING)))
+                       n++;
+       }
+       return n;
+ }
+ static void show_early_header(struct rev_info *rev, const char *stage, int nr)
+ {
+       if (rev->shown_one) {
+               rev->shown_one = 0;
+               if (rev->commit_format != CMIT_FMT_ONELINE)
+                       putchar(rev->diffopt.line_termination);
+       }
+       printf("Final output: %d %s\n", nr, stage);
+ }
+ struct itimerval early_output_timer;
+ static void log_show_early(struct rev_info *revs, struct commit_list *list)
+ {
+       int i = revs->early_output;
+       int show_header = 1;
+       sort_in_topological_order(&list, revs->lifo);
+       while (list && i) {
+               struct commit *commit = list->item;
+               switch (simplify_commit(revs, commit)) {
+               case commit_show:
+                       if (show_header) {
+                               int n = estimate_commit_count(revs, list);
+                               show_early_header(revs, "incomplete", n);
+                               show_header = 0;
+                       }
+                       log_tree_commit(revs, commit);
+                       i--;
+                       break;
+               case commit_ignore:
+                       break;
+               case commit_error:
+                       return;
+               }
+               list = list->next;
+       }
+       /* Did we already get enough commits for the early output? */
+       if (!i)
+               return;
+       /*
+        * ..if no, then repeat it twice a second until we
+        * do.
+        *
+        * NOTE! We don't use "it_interval", because if the
+        * reader isn't listening, we want our output to be
+        * throttled by the writing, and not have the timer
+        * trigger every second even if we're blocked on a
+        * reader!
+        */
+       early_output_timer.it_value.tv_sec = 0;
+       early_output_timer.it_value.tv_usec = 500000;
+       setitimer(ITIMER_REAL, &early_output_timer, NULL);
+ }
+ static void early_output(int signal)
+ {
+       show_early_output = log_show_early;
+ }
+ static void setup_early_output(struct rev_info *rev)
+ {
+       struct sigaction sa;
+       /*
+        * Set up the signal handler, minimally intrusively:
+        * we only set a single volatile integer word (not
+        * using sigatomic_t - trying to avoid unnecessary
+        * system dependencies and headers), and using
+        * SA_RESTART.
+        */
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = early_output;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGALRM, &sa, NULL);
+       /*
+        * If we can get the whole output in less than a
+        * tenth of a second, don't even bother doing the
+        * early-output thing..
+        *
+        * This is a one-time-only trigger.
+        */
+       early_output_timer.it_value.tv_sec = 0;
+       early_output_timer.it_value.tv_usec = 100000;
+       setitimer(ITIMER_REAL, &early_output_timer, NULL);
+ }
+ static void finish_early_output(struct rev_info *rev)
+ {
+       int n = estimate_commit_count(rev, rev->commits);
+       signal(SIGALRM, SIG_IGN);
+       show_early_header(rev, "done", n);
+ }
  static int cmd_log_walk(struct rev_info *rev)
  {
        struct commit *commit;
  
+       if (rev->early_output)
+               setup_early_output(rev);
        prepare_revision_walk(rev);
+       if (rev->early_output)
+               finish_early_output(rev);
        while ((commit = get_revision(rev)) != NULL) {
                log_tree_commit(rev, commit);
                if (!rev->reflog_info) {
@@@ -185,9 -308,11 +308,9 @@@ int cmd_show(int argc, const char **arg
                        struct tag *t = (struct tag *)o;
  
                        printf("%stag %s%s\n\n",
 -                                      diff_get_color(rev.diffopt.color_diff,
 -                                              DIFF_COMMIT),
 +                                      diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
                                        t->tag,
 -                                      diff_get_color(rev.diffopt.color_diff,
 -                                              DIFF_RESET));
 +                                      diff_get_color_opt(&rev.diffopt, DIFF_RESET));
                        ret = show_object(o->sha1, 1);
                        objects[i].item = (struct object *)t->tagged;
                        i--;
                }
                case OBJ_TREE:
                        printf("%stree %s%s\n\n",
 -                                      diff_get_color(rev.diffopt.color_diff,
 -                                              DIFF_COMMIT),
 +                                      diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
                                        name,
 -                                      diff_get_color(rev.diffopt.color_diff,
 -                                              DIFF_RESET));
 +                                      diff_get_color_opt(&rev.diffopt, DIFF_RESET));
                        read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
                                        show_tree_object);
                        break;
@@@ -269,8 -396,6 +392,8 @@@ static int istitlechar(char c
  static char *extra_headers = NULL;
  static int extra_headers_size = 0;
  static const char *fmt_patch_suffix = ".patch";
 +static int numbered = 0;
 +static int auto_number = 0;
  
  static int git_format_config(const char *var, const char *value)
  {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                return 0;
        }
 +      if (!strcmp(var, "format.numbered")) {
 +              if (!strcasecmp(value, "auto")) {
 +                      auto_number = 1;
 +                      return 0;
 +              }
 +
 +              numbered = git_config_bool(var, value);
 +              return 0;
 +      }
  
        return git_log_config(var, value);
  }
@@@ -473,6 -589,7 +596,6 @@@ int cmd_format_patch(int argc, const ch
        struct rev_info rev;
        int nr = 0, total, i, j;
        int use_stdout = 0;
 -      int numbered = 0;
        int start_number = -1;
        int keep_subject = 0;
        int numbered_files = 0;         /* _just_ numbers */
        rev.combine_merges = 0;
        rev.ignore_merges = 1;
        rev.diffopt.msg_sep = "";
 -      rev.diffopt.recursive = 1;
 +      DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
  
        rev.subject_prefix = fmt_patch_subject_prefix;
        rev.extra_headers = extra_headers;
                else if (!strcmp(argv[i], "-n") ||
                                !strcmp(argv[i], "--numbered"))
                        numbered = 1;
 +              else if (!strcmp(argv[i], "-N") ||
 +                              !strcmp(argv[i], "--no-numbered")) {
 +                      numbered = 0;
 +                      auto_number = 0;
 +              }
                else if (!prefixcmp(argv[i], "--start-number="))
                        start_number = strtol(argv[i] + 15, NULL, 10);
                else if (!strcmp(argv[i], "--numbered-files"))
        if (!rev.diffopt.output_format)
                rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
  
 -      if (!rev.diffopt.text)
 -              rev.diffopt.binary = 1;
 +      if (!DIFF_OPT_TST(&rev.diffopt, TEXT))
 +              DIFF_OPT_SET(&rev.diffopt, BINARY);
  
        if (!output_directory && !use_stdout)
                output_directory = prefix;
                list[nr - 1] = commit;
        }
        total = nr;
 +      if (!keep_subject && auto_number && total > 1)
 +              numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
        rev.add_signoff = add_signoff;
@@@ -760,7 -870,7 +883,7 @@@ int cmd_cherry(int argc, const char **a
        revs.diff = 1;
        revs.combine_merges = 0;
        revs.ignore_merges = 1;
 -      revs.diffopt.recursive = 1;
 +      DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
  
        if (add_pending_commit(head, &revs, 0))
                die("Unknown commit %s", head);
diff --combined builtin-rev-list.c
index f5149e59b7c04d943f3b90f706a66d185f8a396c,0258ec43f96cfd20c06125a905c08227729c7249..1cb5f67119a37b8490c76f4846372ba28a316fbf
@@@ -26,7 -26,6 +26,7 @@@ static const char rev_list_usage[] 
  "    --remove-empty\n"
  "    --all\n"
  "    --stdin\n"
 +"    --quiet\n"
  "  ordering output:\n"
  "    --topo-order\n"
  "    --date-order\n"
@@@ -51,7 -50,6 +51,7 @@@ static int show_timestamp
  static int hdr_termination;
  static const char *header_prefix;
  
 +static void finish_commit(struct commit *commit);
  static void show_commit(struct commit *commit)
  {
        if (show_timestamp)
                strbuf_release(&buf);
        }
        maybe_flush_or_die(stdout, "stdout");
 +      finish_commit(commit);
 +}
 +
 +static void finish_commit(struct commit *commit)
 +{
        if (commit->parents) {
                free_commit_list(commit->parents);
                commit->parents = NULL;
        commit->buffer = NULL;
  }
  
 +static void finish_object(struct object_array_entry *p)
 +{
 +      if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
 +              die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
 +}
 +
  static void show_object(struct object_array_entry *p)
  {
        /* An object with name "foo\n0000000..." can be used to
         */
        const char *ep = strchr(p->name, '\n');
  
 -      if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
 -              die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
 -
 +      finish_object(p);
        if (ep) {
                printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
                       (int) (ep - p->name),
@@@ -153,7 -142,7 +153,7 @@@ static int count_distance(struct commit
  
                if (commit->object.flags & (UNINTERESTING | COUNTED))
                        break;
-               if (!revs.prune_fn || (commit->object.flags & TREECHANGE))
+               if (!(commit->object.flags & TREESAME))
                        nr++;
                commit->object.flags |= COUNTED;
                p = commit->parents;
@@@ -209,7 -198,7 +209,7 @@@ static inline int halfway(struct commit
        /*
         * Don't short-cut something we are not going to return!
         */
-       if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
+       if (p->item->object.flags & TREESAME)
                return 0;
        if (DEBUG_BISECT)
                return 0;
@@@ -245,7 -234,7 +245,7 @@@ static void show_list(const char *debug
                char *ep, *sp;
  
                fprintf(stderr, "%c%c%c ",
-                       (flags & TREECHANGE) ? 'T' : ' ',
+                       (flags & TREESAME) ? ' ' : 'T',
                        (flags & UNINTERESTING) ? 'U' : ' ',
                        (flags & COUNTED) ? 'C' : ' ');
                if (commit->util)
@@@ -279,7 -268,7 +279,7 @@@ static struct commit_list *best_bisecti
                int distance;
                unsigned flags = p->item->object.flags;
  
-               if (revs.prune_fn && !(flags & TREECHANGE))
+               if (flags & TREESAME)
                        continue;
                distance = weight(p);
                if (nr - distance < distance)
@@@ -319,7 -308,7 +319,7 @@@ static struct commit_list *best_bisecti
                int distance;
                unsigned flags = p->item->object.flags;
  
-               if (revs.prune_fn && !(flags & TREECHANGE))
+               if (flags & TREESAME)
                        continue;
                distance = weight(p);
                if (nr - distance < distance)
@@@ -373,7 -362,7 +373,7 @@@ static struct commit_list *do_find_bise
                p->item->util = &weights[n++];
                switch (count_interesting_parents(commit)) {
                case 0:
-                       if (!revs.prune_fn || (flags & TREECHANGE)) {
+                       if (!(flags & TREESAME)) {
                                weight_set(p, 1);
                                counted++;
                                show_list("bisection 2 count one",
                         * add one for p itself if p is to be counted,
                         * otherwise inherit it from q directly.
                         */
-                       if (!revs.prune_fn || (flags & TREECHANGE)) {
+                       if (!(flags & TREESAME)) {
                                weight_set(p, weight(q)+1);
                                counted++;
                                show_list("bisection 2 count one",
@@@ -493,7 -482,7 +493,7 @@@ static struct commit_list *find_bisecti
                        continue;
                p->next = last;
                last = p;
-               if (!revs.prune_fn || (flags & TREECHANGE))
+               if (!(flags & TREESAME))
                        nr++;
                on_list++;
        }
@@@ -538,7 -527,6 +538,7 @@@ int cmd_rev_list(int argc, const char *
        int read_from_stdin = 0;
        int bisect_show_vars = 0;
        int bisect_find_all = 0;
 +      int quiet = 0;
  
        git_config(git_default_config);
        init_revisions(&revs, prefix);
                        read_revisions_from_stdin(&revs);
                        continue;
                }
 +              if (!strcmp(arg, "--quiet")) {
 +                      quiet = 1;
 +                      continue;
 +              }
                usage(rev_list_usage);
  
        }
                }
        }
  
 -      traverse_commit_list(&revs, show_commit, show_object);
 +      traverse_commit_list(&revs,
 +              quiet ? finish_commit : show_commit,
 +              quiet ? finish_object : show_object);
  
        return 0;
  }
diff --combined commit.c
index b5092658724e2172abdf54a1af6d91bda0e176da,ab4eb8bdd101d9a06dd408a7903c2c620d109dbc..f074811edc8c5ba41351f50c48a6cda7614e8f8e
+++ b/commit.c
@@@ -3,29 -3,54 +3,13 @@@
  #include "commit.h"
  #include "pkt-line.h"
  #include "utf8.h"
 -#include "interpolate.h"
  #include "diff.h"
  #include "revision.h"
  
  int save_commit_buffer = 1;
  
- struct sort_node
- {
-       /*
-        * the number of children of the associated commit
-        * that also occur in the list being sorted.
-        */
-       unsigned int indegree;
-       /*
-        * reference to original list item that we will re-use
-        * on output.
-        */
-       struct commit_list * list_item;
- };
  const char *commit_type = "commit";
  
 -static struct cmt_fmt_map {
 -      const char *n;
 -      size_t cmp_len;
 -      enum cmit_fmt v;
 -} cmt_fmts[] = {
 -      { "raw",        1,      CMIT_FMT_RAW },
 -      { "medium",     1,      CMIT_FMT_MEDIUM },
 -      { "short",      1,      CMIT_FMT_SHORT },
 -      { "email",      1,      CMIT_FMT_EMAIL },
 -      { "full",       5,      CMIT_FMT_FULL },
 -      { "fuller",     5,      CMIT_FMT_FULLER },
 -      { "oneline",    1,      CMIT_FMT_ONELINE },
 -      { "format:",    7,      CMIT_FMT_USERFORMAT},
 -};
 -
 -static char *user_format;
 -
 -enum cmit_fmt get_commit_format(const char *arg)
 -{
 -      int i;
 -
 -      if (!arg || !*arg)
 -              return CMIT_FMT_DEFAULT;
 -      if (*arg == '=')
 -              arg++;
 -      if (!prefixcmp(arg, "format:")) {
 -              if (user_format)
 -                      free(user_format);
 -              user_format = xstrdup(arg + 7);
 -              return CMIT_FMT_USERFORMAT;
 -      }
 -      for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
 -              if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
 -                  !strncmp(arg, cmt_fmts[i].n, strlen(arg)))
 -                      return cmt_fmts[i].v;
 -      }
 -
 -      die("invalid --pretty format: %s", arg);
 -}
 -
  static struct commit *check_commit(struct object *obj,
                                   const unsigned char *sha1,
                                   int quiet)
@@@ -419,6 -444,683 +403,6 @@@ void clear_commit_marks(struct commit *
        }
  }
  
 -/*
 - * Generic support for pretty-printing the header
 - */
 -static int get_one_line(const char *msg)
 -{
 -      int ret = 0;
 -
 -      for (;;) {
 -              char c = *msg++;
 -              if (!c)
 -                      break;
 -              ret++;
 -              if (c == '\n')
 -                      break;
 -      }
 -      return ret;
 -}
 -
 -/* High bit set, or ISO-2022-INT */
 -int non_ascii(int ch)
 -{
 -      ch = (ch & 0xff);
 -      return ((ch & 0x80) || (ch == 0x1b));
 -}
 -
 -static int is_rfc2047_special(char ch)
 -{
 -      return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
 -}
 -
 -static void add_rfc2047(struct strbuf *sb, const char *line, int len,
 -                     const char *encoding)
 -{
 -      int i, last;
 -
 -      for (i = 0; i < len; i++) {
 -              int ch = line[i];
 -              if (non_ascii(ch))
 -                      goto needquote;
 -              if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
 -                      goto needquote;
 -      }
 -      strbuf_add(sb, line, len);
 -      return;
 -
 -needquote:
 -      strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
 -      strbuf_addf(sb, "=?%s?q?", encoding);
 -      for (i = last = 0; i < len; i++) {
 -              unsigned ch = line[i] & 0xFF;
 -              /*
 -               * We encode ' ' using '=20' even though rfc2047
 -               * allows using '_' for readability.  Unfortunately,
 -               * many programs do not understand this and just
 -               * leave the underscore in place.
 -               */
 -              if (is_rfc2047_special(ch) || ch == ' ') {
 -                      strbuf_add(sb, line + last, i - last);
 -                      strbuf_addf(sb, "=%02X", ch);
 -                      last = i + 1;
 -              }
 -      }
 -      strbuf_add(sb, line + last, len - last);
 -      strbuf_addstr(sb, "?=");
 -}
 -
 -static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
 -                       const char *line, enum date_mode dmode,
 -                       const char *encoding)
 -{
 -      char *date;
 -      int namelen;
 -      unsigned long time;
 -      int tz;
 -      const char *filler = "    ";
 -
 -      if (fmt == CMIT_FMT_ONELINE)
 -              return;
 -      date = strchr(line, '>');
 -      if (!date)
 -              return;
 -      namelen = ++date - line;
 -      time = strtoul(date, &date, 10);
 -      tz = strtol(date, NULL, 10);
 -
 -      if (fmt == CMIT_FMT_EMAIL) {
 -              char *name_tail = strchr(line, '<');
 -              int display_name_length;
 -              if (!name_tail)
 -                      return;
 -              while (line < name_tail && isspace(name_tail[-1]))
 -                      name_tail--;
 -              display_name_length = name_tail - line;
 -              filler = "";
 -              strbuf_addstr(sb, "From: ");
 -              add_rfc2047(sb, line, display_name_length, encoding);
 -              strbuf_add(sb, name_tail, namelen - display_name_length);
 -              strbuf_addch(sb, '\n');
 -      } else {
 -              strbuf_addf(sb, "%s: %.*s%.*s\n", what,
 -                            (fmt == CMIT_FMT_FULLER) ? 4 : 0,
 -                            filler, namelen, line);
 -      }
 -      switch (fmt) {
 -      case CMIT_FMT_MEDIUM:
 -              strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, dmode));
 -              break;
 -      case CMIT_FMT_EMAIL:
 -              strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
 -              break;
 -      case CMIT_FMT_FULLER:
 -              strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode));
 -              break;
 -      default:
 -              /* notin' */
 -              break;
 -      }
 -}
 -
 -static int is_empty_line(const char *line, int *len_p)
 -{
 -      int len = *len_p;
 -      while (len && isspace(line[len-1]))
 -              len--;
 -      *len_p = len;
 -      return !len;
 -}
 -
 -static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
 -                      const struct commit *commit, int abbrev)
 -{
 -      struct commit_list *parent = commit->parents;
 -
 -      if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
 -          !parent || !parent->next)
 -              return;
 -
 -      strbuf_addstr(sb, "Merge:");
 -
 -      while (parent) {
 -              struct commit *p = parent->item;
 -              const char *hex = NULL;
 -              const char *dots;
 -              if (abbrev)
 -                      hex = find_unique_abbrev(p->object.sha1, abbrev);
 -              if (!hex)
 -                      hex = sha1_to_hex(p->object.sha1);
 -              dots = (abbrev && strlen(hex) != 40) ?  "..." : "";
 -              parent = parent->next;
 -
 -              strbuf_addf(sb, " %s%s", hex, dots);
 -      }
 -      strbuf_addch(sb, '\n');
 -}
 -
 -static char *get_header(const struct commit *commit, const char *key)
 -{
 -      int key_len = strlen(key);
 -      const char *line = commit->buffer;
 -
 -      for (;;) {
 -              const char *eol = strchr(line, '\n'), *next;
 -
 -              if (line == eol)
 -                      return NULL;
 -              if (!eol) {
 -                      eol = line + strlen(line);
 -                      next = NULL;
 -              } else
 -                      next = eol + 1;
 -              if (eol - line > key_len &&
 -                  !strncmp(line, key, key_len) &&
 -                  line[key_len] == ' ') {
 -                      return xmemdupz(line + key_len + 1, eol - line - key_len - 1);
 -              }
 -              line = next;
 -      }
 -}
 -
 -static char *replace_encoding_header(char *buf, const char *encoding)
 -{
 -      struct strbuf tmp;
 -      size_t start, len;
 -      char *cp = buf;
 -
 -      /* guess if there is an encoding header before a \n\n */
 -      while (strncmp(cp, "encoding ", strlen("encoding "))) {
 -              cp = strchr(cp, '\n');
 -              if (!cp || *++cp == '\n')
 -                      return buf;
 -      }
 -      start = cp - buf;
 -      cp = strchr(cp, '\n');
 -      if (!cp)
 -              return buf; /* should not happen but be defensive */
 -      len = cp + 1 - (buf + start);
 -
 -      strbuf_init(&tmp, 0);
 -      strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
 -      if (is_encoding_utf8(encoding)) {
 -              /* we have re-coded to UTF-8; drop the header */
 -              strbuf_remove(&tmp, start, len);
 -      } else {
 -              /* just replaces XXXX in 'encoding XXXX\n' */
 -              strbuf_splice(&tmp, start + strlen("encoding "),
 -                                        len - strlen("encoding \n"),
 -                                        encoding, strlen(encoding));
 -      }
 -      return strbuf_detach(&tmp, NULL);
 -}
 -
 -static char *logmsg_reencode(const struct commit *commit,
 -                           const char *output_encoding)
 -{
 -      static const char *utf8 = "utf-8";
 -      const char *use_encoding;
 -      char *encoding;
 -      char *out;
 -
 -      if (!*output_encoding)
 -              return NULL;
 -      encoding = get_header(commit, "encoding");
 -      use_encoding = encoding ? encoding : utf8;
 -      if (!strcmp(use_encoding, output_encoding))
 -              if (encoding) /* we'll strip encoding header later */
 -                      out = xstrdup(commit->buffer);
 -              else
 -                      return NULL; /* nothing to do */
 -      else
 -              out = reencode_string(commit->buffer,
 -                                    output_encoding, use_encoding);
 -      if (out)
 -              out = replace_encoding_header(out, output_encoding);
 -
 -      free(encoding);
 -      return out;
 -}
 -
 -static void fill_person(struct interp *table, const char *msg, int len)
 -{
 -      int start, end, tz = 0;
 -      unsigned long date;
 -      char *ep;
 -
 -      /* parse name */
 -      for (end = 0; end < len && msg[end] != '<'; end++)
 -              ; /* do nothing */
 -      start = end + 1;
 -      while (end > 0 && isspace(msg[end - 1]))
 -              end--;
 -      table[0].value = xmemdupz(msg, end);
 -
 -      if (start >= len)
 -              return;
 -
 -      /* parse email */
 -      for (end = start + 1; end < len && msg[end] != '>'; end++)
 -              ; /* do nothing */
 -
 -      if (end >= len)
 -              return;
 -
 -      table[1].value = xmemdupz(msg + start, end - start);
 -
 -      /* parse date */
 -      for (start = end + 1; start < len && isspace(msg[start]); start++)
 -              ; /* do nothing */
 -      if (start >= len)
 -              return;
 -      date = strtoul(msg + start, &ep, 10);
 -      if (msg + start == ep)
 -              return;
 -
 -      table[5].value = xmemdupz(msg + start, ep - (msg + start));
 -
 -      /* parse tz */
 -      for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
 -              ; /* do nothing */
 -      if (start + 1 < len) {
 -              tz = strtoul(msg + start + 1, NULL, 10);
 -              if (msg[start] == '-')
 -                      tz = -tz;
 -      }
 -
 -      interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
 -      interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
 -      interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
 -      interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
 -}
 -
 -void format_commit_message(const struct commit *commit,
 -                           const void *format, struct strbuf *sb)
 -{
 -      struct interp table[] = {
 -              { "%H" },       /* commit hash */
 -              { "%h" },       /* abbreviated commit hash */
 -              { "%T" },       /* tree hash */
 -              { "%t" },       /* abbreviated tree hash */
 -              { "%P" },       /* parent hashes */
 -              { "%p" },       /* abbreviated parent hashes */
 -              { "%an" },      /* author name */
 -              { "%ae" },      /* author email */
 -              { "%ad" },      /* author date */
 -              { "%aD" },      /* author date, RFC2822 style */
 -              { "%ar" },      /* author date, relative */
 -              { "%at" },      /* author date, UNIX timestamp */
 -              { "%ai" },      /* author date, ISO 8601 */
 -              { "%cn" },      /* committer name */
 -              { "%ce" },      /* committer email */
 -              { "%cd" },      /* committer date */
 -              { "%cD" },      /* committer date, RFC2822 style */
 -              { "%cr" },      /* committer date, relative */
 -              { "%ct" },      /* committer date, UNIX timestamp */
 -              { "%ci" },      /* committer date, ISO 8601 */
 -              { "%e" },       /* encoding */
 -              { "%s" },       /* subject */
 -              { "%b" },       /* body */
 -              { "%Cred" },    /* red */
 -              { "%Cgreen" },  /* green */
 -              { "%Cblue" },   /* blue */
 -              { "%Creset" },  /* reset color */
 -              { "%n" },       /* newline */
 -              { "%m" },       /* left/right/bottom */
 -      };
 -      enum interp_index {
 -              IHASH = 0, IHASH_ABBREV,
 -              ITREE, ITREE_ABBREV,
 -              IPARENTS, IPARENTS_ABBREV,
 -              IAUTHOR_NAME, IAUTHOR_EMAIL,
 -              IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
 -              IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
 -              ICOMMITTER_NAME, ICOMMITTER_EMAIL,
 -              ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
 -              ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
 -              ICOMMITTER_ISO8601,
 -              IENCODING,
 -              ISUBJECT,
 -              IBODY,
 -              IRED, IGREEN, IBLUE, IRESET_COLOR,
 -              INEWLINE,
 -              ILEFT_RIGHT,
 -      };
 -      struct commit_list *p;
 -      char parents[1024];
 -      unsigned long len;
 -      int i;
 -      enum { HEADER, SUBJECT, BODY } state;
 -      const char *msg = commit->buffer;
 -
 -      if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
 -              die("invalid interp table!");
 -
 -      /* these are independent of the commit */
 -      interp_set_entry(table, IRED, "\033[31m");
 -      interp_set_entry(table, IGREEN, "\033[32m");
 -      interp_set_entry(table, IBLUE, "\033[34m");
 -      interp_set_entry(table, IRESET_COLOR, "\033[m");
 -      interp_set_entry(table, INEWLINE, "\n");
 -
 -      /* these depend on the commit */
 -      if (!commit->object.parsed)
 -              parse_object(commit->object.sha1);
 -      interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
 -      interp_set_entry(table, IHASH_ABBREV,
 -                      find_unique_abbrev(commit->object.sha1,
 -                              DEFAULT_ABBREV));
 -      interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
 -      interp_set_entry(table, ITREE_ABBREV,
 -                      find_unique_abbrev(commit->tree->object.sha1,
 -                              DEFAULT_ABBREV));
 -      interp_set_entry(table, ILEFT_RIGHT,
 -                       (commit->object.flags & BOUNDARY)
 -                       ? "-"
 -                       : (commit->object.flags & SYMMETRIC_LEFT)
 -                       ? "<"
 -                       : ">");
 -
 -      parents[1] = 0;
 -      for (i = 0, p = commit->parents;
 -                      p && i < sizeof(parents) - 1;
 -                      p = p->next)
 -              i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
 -                      sha1_to_hex(p->item->object.sha1));
 -      interp_set_entry(table, IPARENTS, parents + 1);
 -
 -      parents[1] = 0;
 -      for (i = 0, p = commit->parents;
 -                      p && i < sizeof(parents) - 1;
 -                      p = p->next)
 -              i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
 -                      find_unique_abbrev(p->item->object.sha1,
 -                              DEFAULT_ABBREV));
 -      interp_set_entry(table, IPARENTS_ABBREV, parents + 1);
 -
 -      for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
 -              int eol;
 -              for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
 -                      ; /* do nothing */
 -
 -              if (state == SUBJECT) {
 -                      table[ISUBJECT].value = xmemdupz(msg + i, eol - i);
 -                      i = eol;
 -              }
 -              if (i == eol) {
 -                      state++;
 -                      /* strip empty lines */
 -                      while (msg[eol + 1] == '\n')
 -                              eol++;
 -              } else if (!prefixcmp(msg + i, "author "))
 -                      fill_person(table + IAUTHOR_NAME,
 -                                      msg + i + 7, eol - i - 7);
 -              else if (!prefixcmp(msg + i, "committer "))
 -                      fill_person(table + ICOMMITTER_NAME,
 -                                      msg + i + 10, eol - i - 10);
 -              else if (!prefixcmp(msg + i, "encoding "))
 -                      table[IENCODING].value =
 -                              xmemdupz(msg + i + 9, eol - i - 9);
 -              i = eol;
 -      }
 -      if (msg[i])
 -              table[IBODY].value = xstrdup(msg + i);
 -
 -      len = interpolate(sb->buf + sb->len, strbuf_avail(sb),
 -                              format, table, ARRAY_SIZE(table));
 -      if (len > strbuf_avail(sb)) {
 -              strbuf_grow(sb, len);
 -              interpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,
 -                                      format, table, ARRAY_SIZE(table));
 -      }
 -      strbuf_setlen(sb, sb->len + len);
 -      interp_clear_table(table, ARRAY_SIZE(table));
 -}
 -
 -static void pp_header(enum cmit_fmt fmt,
 -                    int abbrev,
 -                    enum date_mode dmode,
 -                    const char *encoding,
 -                    const struct commit *commit,
 -                    const char **msg_p,
 -                    struct strbuf *sb)
 -{
 -      int parents_shown = 0;
 -
 -      for (;;) {
 -              const char *line = *msg_p;
 -              int linelen = get_one_line(*msg_p);
 -
 -              if (!linelen)
 -                      return;
 -              *msg_p += linelen;
 -
 -              if (linelen == 1)
 -                      /* End of header */
 -                      return;
 -
 -              if (fmt == CMIT_FMT_RAW) {
 -                      strbuf_add(sb, line, linelen);
 -                      continue;
 -              }
 -
 -              if (!memcmp(line, "parent ", 7)) {
 -                      if (linelen != 48)
 -                              die("bad parent line in commit");
 -                      continue;
 -              }
 -
 -              if (!parents_shown) {
 -                      struct commit_list *parent;
 -                      int num;
 -                      for (parent = commit->parents, num = 0;
 -                           parent;
 -                           parent = parent->next, num++)
 -                              ;
 -                      /* with enough slop */
 -                      strbuf_grow(sb, num * 50 + 20);
 -                      add_merge_info(fmt, sb, commit, abbrev);
 -                      parents_shown = 1;
 -              }
 -
 -              /*
 -               * MEDIUM == DEFAULT shows only author with dates.
 -               * FULL shows both authors but not dates.
 -               * FULLER shows both authors and dates.
 -               */
 -              if (!memcmp(line, "author ", 7)) {
 -                      strbuf_grow(sb, linelen + 80);
 -                      add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
 -              }
 -              if (!memcmp(line, "committer ", 10) &&
 -                  (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
 -                      strbuf_grow(sb, linelen + 80);
 -                      add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
 -              }
 -      }
 -}
 -
 -static void pp_title_line(enum cmit_fmt fmt,
 -                        const char **msg_p,
 -                        struct strbuf *sb,
 -                        const char *subject,
 -                        const char *after_subject,
 -                        const char *encoding,
 -                        int plain_non_ascii)
 -{
 -      struct strbuf title;
 -
 -      strbuf_init(&title, 80);
 -
 -      for (;;) {
 -              const char *line = *msg_p;
 -              int linelen = get_one_line(line);
 -
 -              *msg_p += linelen;
 -              if (!linelen || is_empty_line(line, &linelen))
 -                      break;
 -
 -              strbuf_grow(&title, linelen + 2);
 -              if (title.len) {
 -                      if (fmt == CMIT_FMT_EMAIL) {
 -                              strbuf_addch(&title, '\n');
 -                      }
 -                      strbuf_addch(&title, ' ');
 -              }
 -              strbuf_add(&title, line, linelen);
 -      }
 -
 -      strbuf_grow(sb, title.len + 1024);
 -      if (subject) {
 -              strbuf_addstr(sb, subject);
 -              add_rfc2047(sb, title.buf, title.len, encoding);
 -      } else {
 -              strbuf_addbuf(sb, &title);
 -      }
 -      strbuf_addch(sb, '\n');
 -
 -      if (plain_non_ascii) {
 -              const char *header_fmt =
 -                      "MIME-Version: 1.0\n"
 -                      "Content-Type: text/plain; charset=%s\n"
 -                      "Content-Transfer-Encoding: 8bit\n";
 -              strbuf_addf(sb, header_fmt, encoding);
 -      }
 -      if (after_subject) {
 -              strbuf_addstr(sb, after_subject);
 -      }
 -      if (fmt == CMIT_FMT_EMAIL) {
 -              strbuf_addch(sb, '\n');
 -      }
 -      strbuf_release(&title);
 -}
 -
 -static void pp_remainder(enum cmit_fmt fmt,
 -                       const char **msg_p,
 -                       struct strbuf *sb,
 -                       int indent)
 -{
 -      int first = 1;
 -      for (;;) {
 -              const char *line = *msg_p;
 -              int linelen = get_one_line(line);
 -              *msg_p += linelen;
 -
 -              if (!linelen)
 -                      break;
 -
 -              if (is_empty_line(line, &linelen)) {
 -                      if (first)
 -                              continue;
 -                      if (fmt == CMIT_FMT_SHORT)
 -                              break;
 -              }
 -              first = 0;
 -
 -              strbuf_grow(sb, linelen + indent + 20);
 -              if (indent) {
 -                      memset(sb->buf + sb->len, ' ', indent);
 -                      strbuf_setlen(sb, sb->len + indent);
 -              }
 -              strbuf_add(sb, line, linelen);
 -              strbuf_addch(sb, '\n');
 -      }
 -}
 -
 -void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 -                                struct strbuf *sb, int abbrev,
 -                                const char *subject, const char *after_subject,
 -                                enum date_mode dmode, int plain_non_ascii)
 -{
 -      unsigned long beginning_of_body;
 -      int indent = 4;
 -      const char *msg = commit->buffer;
 -      char *reencoded;
 -      const char *encoding;
 -
 -      if (fmt == CMIT_FMT_USERFORMAT) {
 -              format_commit_message(commit, user_format, sb);
 -              return;
 -      }
 -
 -      encoding = (git_log_output_encoding
 -                  ? git_log_output_encoding
 -                  : git_commit_encoding);
 -      if (!encoding)
 -              encoding = "utf-8";
 -      reencoded = logmsg_reencode(commit, encoding);
 -      if (reencoded) {
 -              msg = reencoded;
 -      }
 -
 -      if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
 -              indent = 0;
 -
 -      /* After-subject is used to pass in Content-Type: multipart
 -       * MIME header; in that case we do not have to do the
 -       * plaintext content type even if the commit message has
 -       * non 7-bit ASCII character.  Otherwise, check if we need
 -       * to say this is not a 7-bit ASCII.
 -       */
 -      if (fmt == CMIT_FMT_EMAIL && !after_subject) {
 -              int i, ch, in_body;
 -
 -              for (in_body = i = 0; (ch = msg[i]); i++) {
 -                      if (!in_body) {
 -                              /* author could be non 7-bit ASCII but
 -                               * the log may be so; skip over the
 -                               * header part first.
 -                               */
 -                              if (ch == '\n' && msg[i+1] == '\n')
 -                                      in_body = 1;
 -                      }
 -                      else if (non_ascii(ch)) {
 -                              plain_non_ascii = 1;
 -                              break;
 -                      }
 -              }
 -      }
 -
 -      pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
 -      if (fmt != CMIT_FMT_ONELINE && !subject) {
 -              strbuf_addch(sb, '\n');
 -      }
 -
 -      /* Skip excess blank lines at the beginning of body, if any... */
 -      for (;;) {
 -              int linelen = get_one_line(msg);
 -              int ll = linelen;
 -              if (!linelen)
 -                      break;
 -              if (!is_empty_line(msg, &ll))
 -                      break;
 -              msg += linelen;
 -      }
 -
 -      /* These formats treat the title line specially. */
 -      if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
 -              pp_title_line(fmt, &msg, sb, subject,
 -                            after_subject, encoding, plain_non_ascii);
 -
 -      beginning_of_body = sb->len;
 -      if (fmt != CMIT_FMT_ONELINE)
 -              pp_remainder(fmt, &msg, sb, indent);
 -      strbuf_rtrim(sb);
 -
 -      /* Make sure there is an EOLN for the non-oneline case */
 -      if (fmt != CMIT_FMT_ONELINE)
 -              strbuf_addch(sb, '\n');
 -
 -      /*
 -       * The caller may append additional body text in e-mail
 -       * format.  Make sure we did not strip the blank line
 -       * between the header and the body.
 -       */
 -      if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
 -              strbuf_addch(sb, '\n');
 -      free(reencoded);
 -}
 -
  struct commit *pop_commit(struct commit_list **stack)
  {
        struct commit_list *top = *stack;
        return item;
  }
  
- void topo_sort_default_setter(struct commit *c, void *data)
- {
-       c->util = data;
- }
- void *topo_sort_default_getter(struct commit *c)
- {
-       return c->util;
- }
  /*
   * Performs an in-place topological sort on the list supplied.
   */
  void sort_in_topological_order(struct commit_list ** list, int lifo)
  {
-       sort_in_topological_order_fn(list, lifo, topo_sort_default_setter,
-                                    topo_sort_default_getter);
- }
- void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
-                                 topo_sort_set_fn_t setter,
-                                 topo_sort_get_fn_t getter)
- {
-       struct commit_list * next = *list;
-       struct commit_list * work = NULL, **insert;
-       struct commit_list ** pptr = list;
-       struct sort_node * nodes;
-       struct sort_node * next_nodes;
-       int count = 0;
-       /* determine the size of the list */
-       while (next) {
-               next = next->next;
-               count++;
-       }
+       struct commit_list *next, *orig = *list;
+       struct commit_list *work, **insert;
+       struct commit_list **pptr;
  
-       if (!count)
+       if (!orig)
                return;
-       /* allocate an array to help sort the list */
-       nodes = xcalloc(count, sizeof(*nodes));
-       /* link the list to the array */
-       next_nodes = nodes;
-       next=*list;
-       while (next) {
-               next_nodes->list_item = next;
-               setter(next->item, next_nodes);
-               next_nodes++;
-               next = next->next;
+       *list = NULL;
+       /* Mark them and clear the indegree */
+       for (next = orig; next; next = next->next) {
+               struct commit *commit = next->item;
+               commit->object.flags |= TOPOSORT;
+               commit->indegree = 0;
        }
        /* update the indegree */
-       next=*list;
-       while (next) {
+       for (next = orig; next; next = next->next) {
                struct commit_list * parents = next->item->parents;
                while (parents) {
-                       struct commit * parent=parents->item;
-                       struct sort_node * pn = (struct sort_node *) getter(parent);
+                       struct commit *parent = parents->item;
  
-                       if (pn)
-                               pn->indegree++;
-                       parents=parents->next;
+                       if (parent->object.flags & TOPOSORT)
+                               parent->indegree++;
+                       parents = parents->next;
                }
-               next=next->next;
        }
        /*
         * find the tips
         *
         *
         * the tips serve as a starting set for the work queue.
         */
-       next=*list;
+       work = NULL;
        insert = &work;
-       while (next) {
-               struct sort_node * node = (struct sort_node *) getter(next->item);
+       for (next = orig; next; next = next->next) {
+               struct commit *commit = next->item;
  
-               if (node->indegree == 0) {
-                       insert = &commit_list_insert(next->item, insert)->next;
-               }
-               next=next->next;
+               if (!commit->indegree)
+                       insert = &commit_list_insert(commit, insert)->next;
        }
  
        /* process the list in topological order */
        if (!lifo)
                sort_by_date(&work);
+       pptr = list;
+       *list = NULL;
        while (work) {
-               struct commit * work_item = pop_commit(&work);
-               struct sort_node * work_node = (struct sort_node *) getter(work_item);
-               struct commit_list * parents = work_item->parents;
+               struct commit *commit;
+               struct commit_list *parents, *work_item;
  
-               while (parents) {
-                       struct commit * parent=parents->item;
-                       struct sort_node * pn = (struct sort_node *) getter(parent);
-                       if (pn) {
-                               /*
-                                * parents are only enqueued for emission
-                                * when all their children have been emitted thereby
-                                * guaranteeing topological order.
-                                */
-                               pn->indegree--;
-                               if (!pn->indegree) {
-                                       if (!lifo)
-                                               insert_by_date(parent, &work);
-                                       else
-                                               commit_list_insert(parent, &work);
-                               }
+               work_item = work;
+               work = work_item->next;
+               work_item->next = NULL;
+               commit = work_item->item;
+               for (parents = commit->parents; parents ; parents = parents->next) {
+                       struct commit *parent=parents->item;
+                       if (!(parent->object.flags & TOPOSORT))
+                               continue;
+                       /*
+                        * parents are only enqueued for emission
+                        * when all their children have been emitted thereby
+                        * guaranteeing topological order.
+                        */
+                       if (!--parent->indegree) {
+                               if (!lifo)
+                                       insert_by_date(parent, &work);
+                               else
+                                       commit_list_insert(parent, &work);
                        }
-                       parents=parents->next;
                }
                /*
                 * work_item is a commit all of whose children
                 * have already been emitted. we can emit it now.
                 */
-               *pptr = work_node->list_item;
-               pptr = &(*pptr)->next;
-               *pptr = NULL;
-               setter(work_item, NULL);
+               commit->object.flags &= ~TOPOSORT;
+               *pptr = work_item;
+               pptr = &work_item->next;
        }
-       free(nodes);
  }
  
  /* merge-base stuff */
diff --combined revision.c
index f5b0e83ee35146a2cae9b71a9914bd818268d4ac,5796153bbd0b01f94a9c33be16c3bccab86a19a5..8f0287fcc0ff4409afa1c16774cfb6086c48ae8f
@@@ -10,6 -10,8 +10,8 @@@
  #include "reflog-walk.h"
  #include "patch-ids.h"
  
+ volatile show_early_output_fn_t show_early_output;
  static char *path_name(struct name_path *path, const char *name)
  {
        struct name_path *p;
@@@ -250,7 -252,7 +252,7 @@@ static void file_add_remove(struct diff
        }
        tree_difference = diff;
        if (tree_difference == REV_TREE_DIFFERENT)
 -              options->has_changes = 1;
 +              DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  static void file_change(struct diff_options *options,
                 const char *base, const char *path)
  {
        tree_difference = REV_TREE_DIFFERENT;
 -      options->has_changes = 1;
 +      DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  static int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
        if (!t2)
                return REV_TREE_DIFFERENT;
        tree_difference = REV_TREE_SAME;
 -      revs->pruning.has_changes = 0;
 +      DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
        if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
                           &revs->pruning) < 0)
                return REV_TREE_DIFFERENT;
@@@ -294,7 -296,7 +296,7 @@@ static int rev_same_tree_as_empty(struc
        init_tree_desc(&empty, "", 0);
  
        tree_difference = REV_TREE_SAME;
 -      revs->pruning.has_changes = 0;
 +      DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
        retval = diff_tree(&empty, &real, "", &revs->pruning);
        free(tree);
  
@@@ -306,15 -308,28 +308,28 @@@ static void try_to_simplify_commit(stru
        struct commit_list **pp, *parent;
        int tree_changed = 0, tree_same = 0;
  
+       /*
+        * If we don't do pruning, everything is interesting
+        */
+       if (!revs->prune)
+               return;
        if (!commit->tree)
                return;
  
        if (!commit->parents) {
-               if (!rev_same_tree_as_empty(revs, commit->tree))
-                       commit->object.flags |= TREECHANGE;
+               if (rev_same_tree_as_empty(revs, commit->tree))
+                       commit->object.flags |= TREESAME;
                return;
        }
  
+       /*
+        * Normal non-merge commit? If we don't want to make the
+        * history dense, we consider it always to be a change..
+        */
+       if (!revs->dense && !commit->parents->next)
+               return;
        pp = &commit->parents;
        while ((parent = *pp) != NULL) {
                struct commit *p = parent->item;
                        }
                        parent->next = NULL;
                        commit->parents = parent;
+                       commit->object.flags |= TREESAME;
                        return;
  
                case REV_TREE_NEW:
                die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
        if (tree_changed && !tree_same)
-               commit->object.flags |= TREECHANGE;
+               return;
+       commit->object.flags |= TREESAME;
  }
  
  static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
         * simplify the commit history and find the parent
         * that has no differences in the path set if one exists.
         */
-       if (revs->prune_fn)
-               revs->prune_fn(revs, commit);
+       try_to_simplify_commit(revs, commit);
  
        if (revs->no_walk)
                return 0;
@@@ -533,6 -549,7 +549,7 @@@ static int limit_list(struct rev_info *
                struct commit_list *entry = list;
                struct commit *commit = list->item;
                struct object *obj = &commit->object;
+               show_early_output_fn_t show;
  
                list = list->next;
                free(entry);
                if (revs->min_age != -1 && (commit->date > revs->min_age))
                        continue;
                p = &commit_list_insert(commit, p)->next;
+               show = show_early_output;
+               if (!show)
+                       continue;
+               show(revs, newlist);
+               show_early_output = NULL;
        }
        if (revs->cherry_pick)
                cherry_pick_list(newlist, revs);
@@@ -662,8 -686,8 +686,8 @@@ void init_revisions(struct rev_info *re
        revs->abbrev = DEFAULT_ABBREV;
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
 -      revs->pruning.recursive = 1;
 -      revs->pruning.quiet = 1;
 +      DIFF_OPT_SET(&revs->pruning, RECURSIVE);
 +      DIFF_OPT_SET(&revs->pruning, QUIET);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
        revs->lifo = 1;
        revs->skip_count = -1;
        revs->max_count = -1;
  
-       revs->prune_fn = NULL;
-       revs->prune_data = NULL;
-       revs->topo_setter = topo_sort_default_setter;
-       revs->topo_getter = topo_sort_default_getter;
        revs->commit_format = CMIT_FMT_DEFAULT;
  
        diff_setup(&revs->diffopt);
@@@ -994,6 -1012,18 +1012,18 @@@ int setup_revisions(int argc, const cha
                                revs->topo_order = 1;
                                continue;
                        }
+                       if (!prefixcmp(arg, "--early-output")) {
+                               int count = 100;
+                               switch (arg[14]) {
+                               case '=':
+                                       count = atoi(arg+15);
+                                       /* Fallthrough */
+                               case 0:
+                                       revs->topo_order = 1;
+                                       revs->early_output = count;
+                                       continue;
+                               }
+                       }
                        if (!strcmp(arg, "--parents")) {
                                revs->parents = 1;
                                continue;
                        }
                        if (!strcmp(arg, "-r")) {
                                revs->diff = 1;
 -                              revs->diffopt.recursive = 1;
 +                              DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
                                continue;
                        }
                        if (!strcmp(arg, "-t")) {
                                revs->diff = 1;
 -                              revs->diffopt.recursive = 1;
 -                              revs->diffopt.tree_in_recursive = 1;
 +                              DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
 +                              DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);
                                continue;
                        }
                        if (!strcmp(arg, "-m")) {
                revs->diff = 1;
  
        /* Pickaxe and rename following needs diffs */
 -      if (revs->diffopt.pickaxe || revs->diffopt.follow_renames)
 +      if (revs->diffopt.pickaxe || DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
                revs->diff = 1;
  
        if (revs->topo_order)
        if (revs->prune_data) {
                diff_tree_setup_paths(revs->prune_data, &revs->pruning);
                /* Can't prune commits with rename following: the paths change.. */
 -              if (!revs->diffopt.follow_renames)
 +              if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
-                       revs->prune_fn = try_to_simplify_commit;
+                       revs->prune = 1;
                if (!revs->full_diff)
                        diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
        }
@@@ -1303,9 -1333,7 +1333,7 @@@ int prepare_revision_walk(struct rev_in
                if (limit_list(revs) < 0)
                        return -1;
        if (revs->topo_order)
-               sort_in_topological_order_fn(&revs->commits, revs->lifo,
-                                            revs->topo_setter,
-                                            revs->topo_getter);
+               sort_in_topological_order(&revs->commits, revs->lifo);
        return 0;
  }
  
@@@ -1324,7 -1352,9 +1352,9 @@@ static enum rewrite_result rewrite_one(
                                return rewrite_one_error;
                if (p->parents && p->parents->next)
                        return rewrite_one_ok;
-               if (p->object.flags & (TREECHANGE | UNINTERESTING))
+               if (p->object.flags & UNINTERESTING)
+                       return rewrite_one_ok;
+               if (!(p->object.flags & TREESAME))
                        return rewrite_one_ok;
                if (!p->parents)
                        return rewrite_one_noparents;
@@@ -1381,6 -1411,36 +1411,36 @@@ static int commit_match(struct commit *
                           commit->buffer, strlen(commit->buffer));
  }
  
+ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
+ {
+       if (commit->object.flags & SHOWN)
+               return commit_ignore;
+       if (revs->unpacked && has_sha1_pack(commit->object.sha1, revs->ignore_packed))
+               return commit_ignore;
+       if (commit->object.flags & UNINTERESTING)
+               return commit_ignore;
+       if (revs->min_age != -1 && (commit->date > revs->min_age))
+               return commit_ignore;
+       if (revs->no_merges && commit->parents && commit->parents->next)
+               return commit_ignore;
+       if (!commit_match(commit, revs))
+               return commit_ignore;
+       if (revs->prune && revs->dense) {
+               /* Commit without changes? */
+               if (commit->object.flags & TREESAME) {
+                       /* drop merges unless we want parenthood */
+                       if (!revs->parents)
+                               return commit_ignore;
+                       /* non-merge - always ignore it */
+                       if (!commit->parents || !commit->parents->next)
+                               return commit_ignore;
+               }
+               if (revs->parents && rewrite_parents(revs, commit) < 0)
+                       return commit_error;
+       }
+       return commit_show;
+ }
  static struct commit *get_revision_1(struct rev_info *revs)
  {
        if (!revs->commits)
                        if (add_parents_to_list(revs, commit, &revs->commits) < 0)
                                return NULL;
                }
-               if (commit->object.flags & SHOWN)
-                       continue;
-               if (revs->unpacked && has_sha1_pack(commit->object.sha1,
-                                                   revs->ignore_packed))
-                   continue;
  
-               if (commit->object.flags & UNINTERESTING)
-                       continue;
-               if (revs->min_age != -1 && (commit->date > revs->min_age))
-                       continue;
-               if (revs->no_merges &&
-                   commit->parents && commit->parents->next)
-                       continue;
-               if (!commit_match(commit, revs))
+               switch (simplify_commit(revs, commit)) {
+               case commit_ignore:
                        continue;
-               if (revs->prune_fn && revs->dense) {
-                       /* Commit without changes? */
-                       if (!(commit->object.flags & TREECHANGE)) {
-                               /* drop merges unless we want parenthood */
-                               if (!revs->parents)
-                                       continue;
-                               /* non-merge - always ignore it */
-                               if (!commit->parents || !commit->parents->next)
-                                       continue;
-                       }
-                       if (revs->parents && rewrite_parents(revs, commit) < 0)
-                               return NULL;
+               case commit_error:
+                       return NULL;
+               default:
+                       return commit;
                }
-               return commit;
        } while (revs->commits);
        return NULL;
  }