Code

Merge branch 'jc/blame' (early part) into HEAD
authorJunio C Hamano <gitster@pobox.com>
Tue, 8 Jul 2008 22:25:44 +0000 (15:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 8 Jul 2008 22:25:44 +0000 (15:25 -0700)
* 'jc/blame' (early part):
  git-blame --reverse
  builtin-blame.c: allow more than 16 parents
  builtin-blame.c: move prepare_final() into a separate function.
  rev-list --children
  revision traversal: --children option

Conflicts:

Documentation/rev-list-options.txt
revision.c

1  2 
Documentation/rev-list-options.txt
builtin-blame.c
builtin-rev-list.c
revision.c
revision.h

index 37dd1d61ea36d7afb1e1b16cc46bee0d357ad1c8,e5823950e2a199a0ee4853147dd687d18f0c4387..b6f5d87e723bec4f00a3929274c43bfd478cc083
@@@ -13,11 -13,10 +13,11 @@@ include::pretty-options.txt[
  
        Synonym for `--date=relative`.
  
 ---date={relative,local,default,iso,rfc}::
 +--date={relative,local,default,iso,rfc,short}::
  
        Only takes effect for dates shown in human-readable format, such
 -      as when using "--pretty".
 +      as when using "--pretty". `log.date` config variable sets a default
 +      value for log command's --date option.
  +
  `--date=relative` shows dates relative to the current time,
  e.g. "2 hours ago".
@@@ -34,21 -33,21 +34,25 @@@ format, often found in E-mail messages
  `--date=default` shows timestamps in the original timezone
  (either committer's or author's).
  
 +ifdef::git-rev-list[]
  --header::
  
        Print the contents of the commit in raw-format; each record is
        separated with a NUL character.
 +endif::git-rev-list[]
  
  --parents::
  
        Print the parents of the commit.
  
+ --children::
+       Print the children of the commit.
 +ifdef::git-rev-list[]
  --timestamp::
        Print the raw commit timestamp.
 +endif::git-rev-list[]
  
  --left-right::
  
@@@ -80,16 -79,6 +84,16 @@@ you would get an output line this
        -xxxxxxx... 1st on a
  -----------------------------------------------------------------------
  
 +--graph::
 +
 +      Draw a text-based graphical representation of the commit history
 +      on the left hand side of the output.  This may cause extra lines
 +      to be printed in between commits, in order for the graph history
 +      to be drawn properly.
 ++
 +This implies the '--topo-order' option by default, but the
 +'--date-order' option may also be specified.
 +
  Diff Formatting
  ~~~~~~~~~~~~~~~
  
@@@ -129,8 -118,7 +133,8 @@@ limiting may be applied
  
  --
  
 --n 'number', --max-count='number'::
 +-n 'number'::
 +--max-count='number'::
  
        Limit the number of commits output.
  
  
        Skip 'number' commits before starting to show the commit output.
  
 ---since='date', --after='date'::
 +--since='date'::
 +--after='date'::
  
        Show commits more recent than a specific date.
  
 ---until='date', --before='date'::
 +--until='date'::
 +--before='date'::
  
        Show commits older than a specific date.
  
  ifdef::git-rev-list[]
 ---max-age='timestamp', --min-age='timestamp'::
 +--max-age='timestamp'::
 +--min-age='timestamp'::
  
        Limit the commits output to specified time range.
  endif::git-rev-list[]
  
 ---author='pattern', --committer='pattern'::
 +--author='pattern'::
 +--committer='pattern'::
  
        Limit the commits output to ones with author/committer
        header lines that match the specified pattern (regular expression).
        Limit the commits output to ones with log message that
        matches the specified pattern (regular expression).
  
 --i, --regexp-ignore-case::
 +-i::
 +--regexp-ignore-case::
  
        Match the regexp limiting patterns without regard to letters case.
  
 --E, --extended-regexp::
 +-E::
 +--extended-regexp::
  
        Consider the limiting patterns to be extended regular expressions
        instead of the default basic regular expressions.
  
 --F, --fixed-strings::
 +-F::
 +--fixed-strings::
  
        Consider the limiting patterns to be fixed strings (don't interpret
        pattern as a regular expression).
        Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the
        command line as '<commit>'.
  
 +ifdef::git-rev-list[]
  --stdin::
  
        In addition to the '<commit>' listed on the command
        test the exit status to see if a range of objects is fully
        connected (or not).  It is faster than redirecting stdout
        to /dev/null as the output does not have to be formatted.
 +endif::git-rev-list[]
  
  --cherry-pick::
  
@@@ -247,8 -226,7 +251,8 @@@ from the other branch (for example, "3r
  from branch A).  With this option, such pairs of commits are
  excluded from the output.
  
 --g, --walk-reflogs::
 +-g::
 +--walk-reflogs::
  
        Instead of walking the commit ancestry chain, walk
        reflog entries from the most recent one to older ones.
@@@ -277,8 -255,7 +281,8 @@@ See also linkgit:git-reflog[1]
        Output uninteresting commits at the boundary, which are usually
        not shown.
  
 ---dense, --sparse::
 +--dense::
 +--sparse::
  
  When optional paths are given, the default behaviour ('--dense') is to
  only output commits that changes at least one of them, and also ignore
diff --combined builtin-blame.c
index b451f6c64dde8ce6358bb5c8dfccc8bad181a6bb,5c7546db2514742851b405481373add26018756a..cf41511c798330a7e0ec02db5785747ad2b662a5
@@@ -43,6 -43,7 +43,7 @@@ static int max_orig_digits
  static int max_digits;
  static int max_score_digits;
  static int show_root;
+ static int reverse;
  static int blank_boundary;
  static int incremental;
  static int cmd_is_annotate;
@@@ -91,7 -92,7 +92,7 @@@ struct origin 
   * Given an origin, prepare mmfile_t structure to be used by the
   * diff machinery
   */
- static char *fill_origin_blob(struct origin *o, mmfile_t *file)
+ static void fill_origin_blob(struct origin *o, mmfile_t *file)
  {
        if (!o->file.ptr) {
                enum object_type type;
        }
        else
                *file = o->file;
-       return file->ptr;
  }
  
  /*
@@@ -178,7 -178,7 +178,7 @@@ struct blame_entry 
  struct scoreboard {
        /* the final commit (i.e. where we started digging from) */
        struct commit *final;
+       struct rev_info *revs;
        const char *path;
  
        /*
@@@ -1192,18 -1192,48 +1192,48 @@@ static void pass_whole_blame(struct sco
        }
  }
  
- #define MAXPARENT 16
+ /*
+  * We pass blame from the current commit to its parents.  We keep saying
+  * "parent" (and "porigin"), but what we mean is to find scapegoat to
+  * exonerate ourselves.
+  */
+ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+ {
+       if (!reverse)
+               return commit->parents;
+       return lookup_decoration(&revs->children, &commit->object);
+ }
+ static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+ {
+       int cnt;
+       struct commit_list *l = first_scapegoat(revs, commit);
+       for (cnt = 0; l; l = l->next)
+               cnt++;
+       return cnt;
+ }
+ #define MAXSG 16
  
  static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
  {
-       int i, pass;
+       struct rev_info *revs = sb->revs;
+       int i, pass, num_sg;
        struct commit *commit = origin->commit;
-       struct commit_list *parent;
-       struct origin *parent_origin[MAXPARENT], *porigin;
-       memset(parent_origin, 0, sizeof(parent_origin));
+       struct commit_list *sg;
+       struct origin *sg_buf[MAXSG];
+       struct origin *porigin, **sg_origin = sg_buf;
+       num_sg = num_scapegoats(revs, commit);
+       if (!num_sg)
+               goto finish;
+       else if (num_sg < ARRAY_SIZE(sg_buf))
+               memset(sg_buf, 0, sizeof(sg_buf));
+       else
+               sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
  
-       /* The first pass looks for unrenamed path to optimize for
+       /*
+        * The first pass looks for unrenamed path to optimize for
         * common cases, then we look for renames in the second pass.
         */
        for (pass = 0; pass < 2; pass++) {
                                       struct commit *, struct origin *);
                find = pass ? find_rename : find_origin;
  
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct commit *p = parent->item;
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct commit *p = sg->item;
                        int j, same;
  
-                       if (parent_origin[i])
+                       if (sg_origin[i])
                                continue;
                        if (parse_commit(p))
                                continue;
                                goto finish;
                        }
                        for (j = same = 0; j < i; j++)
-                               if (parent_origin[j] &&
-                                   !hashcmp(parent_origin[j]->blob_sha1,
+                               if (sg_origin[j] &&
+                                   !hashcmp(sg_origin[j]->blob_sha1,
                                             porigin->blob_sha1)) {
                                        same = 1;
                                        break;
                                }
                        if (!same)
-                               parent_origin[i] = porigin;
+                               sg_origin[i] = porigin;
                        else
                                origin_decref(porigin);
                }
        }
  
        num_commits++;
-       for (i = 0, parent = commit->parents;
-            i < MAXPARENT && parent;
-            parent = parent->next, i++) {
-               struct origin *porigin = parent_origin[i];
+       for (i = 0, sg = first_scapegoat(revs, commit);
+            i < num_sg && sg;
+            sg = sg->next, i++) {
+               struct origin *porigin = sg_origin[i];
                if (!porigin)
                        continue;
                if (pass_blame_to_parent(sb, origin, porigin))
         * Optionally find moves in parents' files.
         */
        if (opt & PICKAXE_BLAME_MOVE)
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct origin *porigin = parent_origin[i];
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
                        if (!porigin)
                                continue;
                        if (find_move_in_parent(sb, origin, porigin))
         * Optionally find copies from parents' files.
         */
        if (opt & PICKAXE_BLAME_COPY)
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct origin *porigin = parent_origin[i];
-                       if (find_copy_in_parent(sb, origin, parent->item,
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
+                       if (find_copy_in_parent(sb, origin, sg->item,
                                                porigin, opt))
                                goto finish;
                }
  
   finish:
-       for (i = 0; i < MAXPARENT; i++) {
-               if (parent_origin[i]) {
-                       drop_origin_blob(parent_origin[i]);
-                       origin_decref(parent_origin[i]);
+       for (i = 0; i < num_sg; i++) {
+               if (sg_origin[i]) {
+                       drop_origin_blob(sg_origin[i]);
+                       origin_decref(sg_origin[i]);
                }
        }
        drop_origin_blob(origin);
+       if (sg_buf != sg_origin)
+               free(sg_origin);
  }
  
  /*
@@@ -1487,8 -1519,10 +1519,10 @@@ static void found_guilty_entry(struct b
   * is still unknown, pick one blame_entry, and allow its current
   * suspect to pass blames to its parents.
   */
- static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+ static void assign_blame(struct scoreboard *sb, int opt)
  {
+       struct rev_info *revs = sb->revs;
        while (1) {
                struct blame_entry *ent;
                struct commit *commit;
                commit = suspect->commit;
                if (!commit->object.parsed)
                        parse_commit(commit);
-               if (!(commit->object.flags & UNINTERESTING) &&
-                   !(revs->max_age != -1 && commit->date < revs->max_age))
+               if (reverse ||
+                   (!(commit->object.flags & UNINTERESTING) &&
+                    !(revs->max_age != -1 && commit->date < revs->max_age)))
                        pass_blame(sb, suspect, opt);
                else {
                        commit->object.flags |= UNINTERESTING;
@@@ -1993,7 -2028,7 +2028,7 @@@ static void prepare_blame_range(struct 
                usage(blame_usage);
  }
  
 -static int git_blame_config(const char *var, const char *value)
 +static int git_blame_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "blame.showroot")) {
                show_root = git_config_bool(var, value);
                blank_boundary = git_config_bool(var, value);
                return 0;
        }
 -      return git_default_config(var, value);
 +      return git_default_config(var, value, cb);
  }
  
+ /*
+  * Prepare a dummy commit that represents the work tree (or staged) item.
+  * Note that annotating work tree item never works in the reverse.
+  */
  static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
  {
        struct commit *commit;
        return commit;
  }
  
+ static const char *prepare_final(struct scoreboard *sb)
+ {
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+       /*
+        * There must be one and only one positive commit in the
+        * revs->pending array.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (obj->flags & UNINTERESTING)
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig from %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       return final_commit_name;
+ }
+ static const char *prepare_initial(struct scoreboard *sb)
+ {
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+       /*
+        * There must be one and only one negative commit, and it must be
+        * the boundary.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (!(obj->flags & UNINTERESTING))
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig down to %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       if (!final_commit_name)
+               die("No commit to dig down to?");
+       return final_commit_name;
+ }
  int cmd_blame(int argc, const char **argv, const char *prefix)
  {
        struct rev_info revs;
  
        cmd_is_annotate = !strcmp(argv[0], "annotate");
  
 -      git_config(git_blame_config);
 +      git_config(git_blame_config, NULL);
        save_commit_buffer = 0;
  
        opt = 0;
                        blank_boundary = 1;
                else if (!strcmp("--root", arg))
                        show_root = 1;
+               else if (!strcmp("--reverse", arg)) {
+                       argv[unk++] = "--children";
+                       reverse = 1;
+               }
                else if (!strcmp(arg, "--show-stats"))
                        show_stats = 1;
                else if (!strcmp("-c", arg))
        setup_revisions(unk, argv, &revs, NULL);
        memset(&sb, 0, sizeof(sb));
  
-       /*
-        * There must be one and only one positive commit in the
-        * revs->pending array.
-        */
-       for (i = 0; i < revs.pending.nr; i++) {
-               struct object *obj = revs.pending.objects[i].item;
-               if (obj->flags & UNINTERESTING)
-                       continue;
-               while (obj->type == OBJ_TAG)
-                       obj = deref_tag(obj, NULL, 0);
-               if (obj->type != OBJ_COMMIT)
-                       die("Non commit %s?",
-                           revs.pending.objects[i].name);
-               if (sb.final)
-                       die("More than one commit to dig from %s and %s?",
-                           revs.pending.objects[i].name,
-                           final_commit_name);
-               sb.final = (struct commit *) obj;
-               final_commit_name = revs.pending.objects[i].name;
-       }
+       sb.revs = &revs;
+       if (!reverse)
+               final_commit_name = prepare_final(&sb);
+       else if (contents_from)
+               die("--contents and --children do not blend well.");
+       else
+               final_commit_name = prepare_initial(&sb);
  
        if (!sb.final) {
                /*
        if (!incremental)
                setup_pager();
  
-       assign_blame(&sb, &revs, opt);
+       assign_blame(&sb, opt);
  
        if (incremental)
                return 0;
diff --combined builtin-rev-list.c
index 83a7b1349e06dbf1a355888272d9b13a7d4c22c4,9da2f76375ee0160a554d691c714446146e1b1cf..11a7eae551601abcd1eddfae389b86fac462b9a7
@@@ -10,7 -10,6 +10,7 @@@
  #include "list-objects.h"
  #include "builtin.h"
  #include "log-tree.h"
 +#include "graph.h"
  
  /* bits #0-15 in revision.h */
  
@@@ -37,6 -36,7 +37,7 @@@ static const char rev_list_usage[] 
  "    --reverse\n"
  "  formatting output:\n"
  "    --parents\n"
+ "    --children\n"
  "    --objects | --objects-edge\n"
  "    --unpacked\n"
  "    --header | --pretty\n"
@@@ -59,37 -59,41 +60,46 @@@ static const char *header_prefix
  static void finish_commit(struct commit *commit);
  static void show_commit(struct commit *commit)
  {
 +      graph_show_commit(revs.graph);
 +
        if (show_timestamp)
                printf("%lu ", commit->date);
        if (header_prefix)
                fputs(header_prefix, stdout);
 -      if (commit->object.flags & BOUNDARY)
 -              putchar('-');
 -      else if (commit->object.flags & UNINTERESTING)
 -              putchar('^');
 -      else if (revs.left_right) {
 -              if (commit->object.flags & SYMMETRIC_LEFT)
 -                      putchar('<');
 -              else
 -                      putchar('>');
 +
 +      if (!revs.graph) {
 +              if (commit->object.flags & BOUNDARY)
 +                      putchar('-');
 +              else if (commit->object.flags & UNINTERESTING)
 +                      putchar('^');
 +              else if (revs.left_right) {
 +                      if (commit->object.flags & SYMMETRIC_LEFT)
 +                              putchar('<');
 +                      else
 +                              putchar('>');
 +              }
        }
        if (revs.abbrev_commit && revs.abbrev)
                fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
                      stdout);
        else
                fputs(sha1_to_hex(commit->object.sha1), stdout);
 -      if (revs.parents) {
 +      if (revs.print_parents) {
                struct commit_list *parents = commit->parents;
                while (parents) {
                        printf(" %s", sha1_to_hex(parents->item->object.sha1));
                        parents = parents->next;
                }
        }
+       if (revs.children.name) {
+               struct commit_list *children;
+               children = lookup_decoration(&revs.children, &commit->object);
+               while (children) {
+                       printf(" %s", sha1_to_hex(children->item->object.sha1));
+                       children = children->next;
+               }
+       }
        show_decorations(commit);
        if (revs.commit_format == CMIT_FMT_ONELINE)
                putchar(' ');
                pretty_print_commit(revs.commit_format, commit,
                                    &buf, revs.abbrev, NULL, NULL,
                                    revs.date_mode, 0);
 -              if (buf.len)
 -                      printf("%s%c", buf.buf, hdr_termination);
 +              if (revs.graph) {
 +                      if (buf.len) {
 +                              if (revs.commit_format != CMIT_FMT_ONELINE)
 +                                      graph_show_oneline(revs.graph);
 +
 +                              graph_show_commit_msg(revs.graph, &buf);
 +
 +                              /*
 +                               * Add a newline after the commit message.
 +                               *
 +                               * Usually, this newline produces a blank
 +                               * padding line between entries, in which case
 +                               * we need to add graph padding on this line.
 +                               *
 +                               * However, the commit message may not end in a
 +                               * newline.  In this case the newline simply
 +                               * ends the last line of the commit message,
 +                               * and we don't need any graph output.  (This
 +                               * always happens with CMIT_FMT_ONELINE, and it
 +                               * happens with CMIT_FMT_USERFORMAT when the
 +                               * format doesn't explicitly end in a newline.)
 +                               */
 +                              if (buf.len && buf.buf[buf.len - 1] == '\n')
 +                                      graph_show_padding(revs.graph);
 +                              putchar('\n');
 +                      } else {
 +                              /*
 +                               * If the message buffer is empty, just show
 +                               * the rest of the graph output for this
 +                               * commit.
 +                               */
 +                              if (graph_show_remainder(revs.graph))
 +                                      putchar('\n');
 +                      }
 +              } else {
 +                      if (buf.len)
 +                              printf("%s%c", buf.buf, hdr_termination);
 +              }
                strbuf_release(&buf);
 +      } else {
 +              if (graph_show_remainder(revs.graph))
 +                      putchar('\n');
        }
        maybe_flush_or_die(stdout, "stdout");
        finish_commit(commit);
@@@ -591,7 -556,7 +601,7 @@@ int cmd_rev_list(int argc, const char *
        int bisect_find_all = 0;
        int quiet = 0;
  
 -      git_config(git_default_config);
 +      git_config(git_default_config, NULL);
        init_revisions(&revs, prefix);
        revs.abbrev = 0;
        revs.commit_format = CMIT_FMT_UNSPECIFIED;
diff --combined revision.c
index fc667552592daa3c894d0df4e97469d1809dbf27,979241eb0dd7a3fe0123c2c9af9c06ad77d3ac52..5a1a948a41e8f2ae2df01c0a61d2e228cc79caab
@@@ -6,10 -6,10 +6,11 @@@
  #include "diff.h"
  #include "refs.h"
  #include "revision.h"
 +#include "graph.h"
  #include "grep.h"
  #include "reflog-walk.h"
  #include "patch-ids.h"
+ #include "decorate.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -416,6 -416,7 +417,6 @@@ static int add_parents_to_list(struct r
  {
        struct commit_list *parent = commit->parents;
        unsigned left_flag;
 -      int add, rest;
  
        if (commit->object.flags & ADDED)
                return 0;
  
        left_flag = (commit->object.flags & SYMMETRIC_LEFT);
  
 -      rest = !revs->first_parent_only;
 -      for (parent = commit->parents, add = 1; parent; add = rest) {
 +      for (parent = commit->parents; parent; parent = parent->next) {
                struct commit *p = parent->item;
  
 -              parent = parent->next;
                if (parse_commit(p) < 0)
                        return -1;
                p->object.flags |= left_flag;
 -              if (p->object.flags & SEEN)
 -                      continue;
 -              p->object.flags |= SEEN;
 -              if (add)
 +              if (!(p->object.flags & SEEN)) {
 +                      p->object.flags |= SEEN;
                        insert_by_date(p, list);
 +              }
 +              if(revs->first_parent_only)
 +                      break;
        }
        return 0;
  }
@@@ -1104,8 -1106,7 +1105,8 @@@ int setup_revisions(int argc, const cha
                                }
                        }
                        if (!strcmp(arg, "--parents")) {
 -                              revs->parents = 1;
 +                              revs->rewrite_parents = 1;
 +                              revs->print_parents = 1;
                                continue;
                        }
                        if (!strcmp(arg, "--dense")) {
                                revs->verbose_header = 1;
                                continue;
                        }
 -                      if (!prefixcmp(arg, "--pretty")) {
 +                      if (!strcmp(arg, "--pretty")) {
 +                              revs->verbose_header = 1;
 +                              get_commit_format(arg+8, revs);
 +                              continue;
 +                      }
 +                      if (!prefixcmp(arg, "--pretty=")) {
                                revs->verbose_header = 1;
 -                              revs->commit_format = get_commit_format(arg+8);
 +                              get_commit_format(arg+9, revs);
 +                              continue;
 +                      }
 +                      if (!strcmp(arg, "--graph")) {
 +                              revs->topo_order = 1;
 +                              revs->rewrite_parents = 1;
 +                              revs->graph = graph_init(revs);
                                continue;
                        }
                        if (!strcmp(arg, "--root")) {
                                revs->no_walk = 0;
                                continue;
                        }
+                       if (!strcmp(arg, "--children")) {
+                               revs->children.name = "children";
+                               revs->limited = 1;
+                               continue;
+                       }
  
                        opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
                        if (opts > 0) {
  
        if (revs->reverse && revs->reflog_info)
                die("cannot combine --reverse with --walk-reflogs");
 -      if (revs->parents && revs->children.name)
++      if (revs->rewrite_parents && revs->children.name)
+               die("cannot combine --parents and --children");
 +
 +      /*
 +       * Limitations on the graph functionality
 +       */
 +      if (revs->reverse && revs->graph)
 +              die("cannot combine --reverse with --graph");
 +
 +      if (revs->reflog_info && revs->graph)
 +              die("cannot combine --walk-reflogs with --graph");
 +
        return left;
  }
  
+ static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child)
+ {
+       struct commit_list *l = xcalloc(1, sizeof(*l));
+       l->item = child;
+       l->next = add_decoration(&revs->children, &parent->object, l);
+ }
+ static void set_children(struct rev_info *revs)
+ {
+       struct commit_list *l;
+       for (l = revs->commits; l; l = l->next) {
+               struct commit *commit = l->item;
+               struct commit_list *p;
+               for (p = commit->parents; p; p = p->next)
+                       add_child(revs, p->item, commit);
+       }
+ }
  int prepare_revision_walk(struct rev_info *revs)
  {
        int nr = revs->pending.nr;
                        return -1;
        if (revs->topo_order)
                sort_in_topological_order(&revs->commits, revs->lifo);
+       if (revs->children.name)
+               set_children(revs);
        return 0;
  }
  
@@@ -1524,6 -1533,11 +1554,11 @@@ static int commit_match(struct commit *
                           commit->buffer, strlen(commit->buffer));
  }
  
 -      return (revs->parents || revs->children.name);
+ static inline int want_ancestry(struct rev_info *revs)
+ {
++      return (revs->rewrite_parents || revs->children.name);
+ }
  enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
  {
        if (commit->object.flags & SHOWN)
                /* Commit without changes? */
                if (commit->object.flags & TREESAME) {
                        /* drop merges unless we want parenthood */
-                       if (!revs->rewrite_parents)
+                       if (!want_ancestry(revs))
                                return commit_ignore;
                        /* non-merge - always ignore it */
                        if (!commit->parents || !commit->parents->next)
                                return commit_ignore;
                }
-               if (revs->rewrite_parents && rewrite_parents(revs, commit) < 0)
+               if (want_ancestry(revs) && rewrite_parents(revs, commit) < 0)
                        return commit_error;
        }
        return commit_show;
@@@ -1617,62 -1631,28 +1652,62 @@@ static void gc_boundary(struct object_a
        }
  }
  
 -struct commit *get_revision(struct rev_info *revs)
 +static void create_boundary_commit_list(struct rev_info *revs)
 +{
 +      unsigned i;
 +      struct commit *c;
 +      struct object_array *array = &revs->boundary_commits;
 +      struct object_array_entry *objects = array->objects;
 +
 +      /*
 +       * If revs->commits is non-NULL at this point, an error occurred in
 +       * get_revision_1().  Ignore the error and continue printing the
 +       * boundary commits anyway.  (This is what the code has always
 +       * done.)
 +       */
 +      if (revs->commits) {
 +              free_commit_list(revs->commits);
 +              revs->commits = NULL;
 +      }
 +
 +      /*
 +       * Put all of the actual boundary commits from revs->boundary_commits
 +       * into revs->commits
 +       */
 +      for (i = 0; i < array->nr; i++) {
 +              c = (struct commit *)(objects[i].item);
 +              if (!c)
 +                      continue;
 +              if (!(c->object.flags & CHILD_SHOWN))
 +                      continue;
 +              if (c->object.flags & (SHOWN | BOUNDARY))
 +                      continue;
 +              c->object.flags |= BOUNDARY;
 +              commit_list_insert(c, &revs->commits);
 +      }
 +
 +      /*
 +       * If revs->topo_order is set, sort the boundary commits
 +       * in topological order
 +       */
 +      sort_in_topological_order(&revs->commits, revs->lifo);
 +}
 +
 +static struct commit *get_revision_internal(struct rev_info *revs)
  {
        struct commit *c = NULL;
        struct commit_list *l;
  
        if (revs->boundary == 2) {
 -              unsigned i;
 -              struct object_array *array = &revs->boundary_commits;
 -              struct object_array_entry *objects = array->objects;
 -              for (i = 0; i < array->nr; i++) {
 -                      c = (struct commit *)(objects[i].item);
 -                      if (!c)
 -                              continue;
 -                      if (!(c->object.flags & CHILD_SHOWN))
 -                              continue;
 -                      if (!(c->object.flags & SHOWN))
 -                              break;
 -              }
 -              if (array->nr <= i)
 -                      return NULL;
 -
 -              c->object.flags |= SHOWN | BOUNDARY;
 +              /*
 +               * All of the normal commits have already been returned,
 +               * and we are now returning boundary commits.
 +               * create_boundary_commit_list() has populated
 +               * revs->commits with the remaining commits to return.
 +               */
 +              c = pop_commit(&revs->commits);
 +              if (c)
 +                      c->object.flags |= SHOWN;
                return c;
        }
  
                 * switch to boundary commits output mode.
                 */
                revs->boundary = 2;
 -              return get_revision(revs);
 +
 +              /*
 +               * Update revs->commits to contain the list of
 +               * boundary commits.
 +               */
 +              create_boundary_commit_list(revs);
 +
 +              return get_revision_internal(revs);
        }
  
        /*
  
        return c;
  }
 +
 +struct commit *get_revision(struct rev_info *revs)
 +{
 +      struct commit *c = get_revision_internal(revs);
 +      if (c && revs->graph)
 +              graph_update(revs->graph, c);
 +      return c;
 +}
diff --combined revision.h
index abce5001f19a60bb15b519b26773b57c83563021,966116cd5b107959995b758f120269f579190578..dcf08e089abc053bfecebc9b73fe870236915acd
@@@ -46,8 -46,7 +46,8 @@@ struct rev_info 
                        unpacked:1, /* see also ignore_packed below */
                        boundary:2,
                        left_right:1,
 -                      parents:1,
 +                      rewrite_parents:1,
 +                      print_parents:1,
                        reverse:1,
                        cherry_pick:1,
                        first_parent_only:1;
@@@ -65,9 -64,7 +65,9 @@@
  
        /* Format info */
        unsigned int    shown_one:1,
 -                      abbrev_commit:1;
 +                      abbrev_commit:1,
 +                      use_terminator:1,
 +                      missing_newline:1;
        enum date_mode date_mode;
  
        const char **ignore_packed; /* pretend objects in these are unpacked */
@@@ -90,9 -87,6 +90,9 @@@
        /* Filter by commit log message */
        struct grep_opt *grep_filter;
  
 +      /* Display history graph */
 +      struct git_graph *graph;
 +
        /* special limits */
        int skip_count;
        int max_count;
        struct diff_options pruning;
  
        struct reflog_walk_info *reflog_info;
+       struct decoration children;
  };
  
  #define REV_TREE_SAME         0