Merge branch 'jc/topo-author-date-sort'
authorJunio C Hamano <gitster@pobox.com>
Mon, 1 Jul 2013 19:41:22 +0000 (12:41 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 1 Jul 2013 19:41:23 +0000 (12:41 -0700)
"git log" learned the "--author-date-order" option, with which the
output is topologically sorted and commits in parallel histories
are shown intermixed together based on the author timestamp.

* jc/topo-author-date-sort:
t6003: add --author-date-order test
topology tests: teach a helper to set author dates as well
t6003: add --date-order test
topology tests: teach a helper to take abbreviated timestamps
t/lib-t6000: style fixes
log: --author-date-order
sort-in-topological-order: use prio-queue
prio-queue: priority queue of pointers to structs
toposort: rename "lifo" field

1  2 
.gitignore
Documentation/rev-list-options.txt
Makefile
builtin/log.c
builtin/show-branch.c
commit.c
commit.h
revision.c
revision.h
diff --combined .gitignore
index c0e00eb37bb370ce9deee72468fb419bc25cfd62,b753817ad4b86c6a2d74c11af31c76cab8b4f1c7..efa8db00358772852e637af8758f8f76719dec01
  /git-remote-ftps
  /git-remote-fd
  /git-remote-ext
 +/git-remote-testgit
  /git-remote-testpy
  /git-remote-testsvn
  /git-repack
  /test-mktemp
  /test-parse-options
  /test-path-utils
+ /test-prio-queue
 +/test-read-cache
  /test-regex
  /test-revision-walking
  /test-run-command
  /cscope*
  *.obj
  *.lib
 +*.res
  *.sln
  *.suo
  *.ncb
index b462f17f62d4c0066b1178b405b011963643cdd9,8302402c7db8dc585ce3865fb55b36e1f8d8ed07..e157ec3fe7e6bc72e271337f6cb0b4c1d791a2eb
@@@ -271,8 -271,8 +271,8 @@@ See also linkgit:git-reflog[1]
  
  --boundary::
  
 -      Output uninteresting commits at the boundary, which are usually
 -      not shown.
 +      Output excluded boundary commits. Boundary commits are
 +      prefixed with `-`.
  
  --
  
@@@ -342,13 -342,13 +342,13 @@@ In the following, we will always refer 
  illustrate the differences between simplification settings.  We assume
  that you are filtering for a file `foo` in this commit graph:
  -----------------------------------------------------------------------
 -        .-A---M---N---O---P
 -       /     /   /   /   /
 -      I     B   C   D   E
 -       \   /   /   /   /
 -        `-------------'
 +        .-A---M---N---O---P---Q
 +       /     /   /   /   /   /
 +      I     B   C   D   E   Y
 +       \   /   /   /   /   /
 +        `-------------'   X
  -----------------------------------------------------------------------
 -The horizontal line of history A---P is taken to be the first parent of
 +The horizontal line of history A---Q is taken to be the first parent of
  each merge.  The commits are:
  
  * `I` is the initial commit, in which `foo` exists with contents
    `N` and `D` to "foobarbaz"; i.e., it is not TREESAME to any parent.
  
  * `E` changes `quux` to "xyzzy", and its merge `P` combines the
 -  strings to "quux xyzzy".  Despite appearing interesting, `P` is
 -  TREESAME to all parents.
 +  strings to "quux xyzzy".  `P` is TREESAME to `O`, but not to `E`.
 +
 +* `X` is an indpendent root commit that added a new file `side`, and `Y`
 +  modified it. `Y` is TREESAME to `X`. Its merge `Q` added `side` to `P`, and
 +  `Q` is TREESAME to `P`, but not to `Y`.
  
  'rev-list' walks backwards through history, including or excluding
  commits based on whether '\--full-history' and/or parent rewriting
@@@ -413,10 -410,10 +413,10 @@@ parent lines
        the example, we get
  +
  -----------------------------------------------------------------------
 -      I  A  B  N  D  O
 +      I  A  B  N  D  O  P  Q
  -----------------------------------------------------------------------
  +
 -`P` and `M` were excluded because they are TREESAME to a parent.  `E`,
 +`M` was excluded because it is TREESAME to both parents.  `E`,
  `C` and `B` were all walked, but only `B` was !TREESAME, so the others
  do not appear.
  +
@@@ -434,7 -431,7 +434,7 @@@ Along each parent, prune away commits t
  themselves.  This results in
  +
  -----------------------------------------------------------------------
 -        .-A---M---N---O---P
 +        .-A---M---N---O---P---Q
         /     /   /   /   /
        I     B   /   D   /
         \   /   /   /   /
  Compare to '\--full-history' without rewriting above.  Note that `E`
  was pruned away because it is TREESAME, but the parent list of P was
  rewritten to contain `E`'s parent `I`.  The same happened for `C` and
 -`N`.  Note also that `P` was included despite being TREESAME.
 +`N`, and `X`, `Y` and `Q`.
  
  In addition to the above settings, you can change whether TREESAME
  affects inclusion:
@@@ -474,9 -471,8 +474,9 @@@ history according to the following rule
  * Set `C'` to `C`.
  +
  * Replace each parent `P` of `C'` with its simplification `P'`.  In
 -  the process, drop parents that are ancestors of other parents, and
 -  remove duplicates.
 +  the process, drop parents that are ancestors of other parents or that are
 +  root commits TREESAME to an empty tree, and remove duplicates, but take care
 +  to never drop all parents that we are TREESAME to.
  +
  * If after this parent rewriting, `C'` is a root or merge commit (has
    zero or >1 parents), a boundary commit, or !TREESAME, it remains.
@@@ -494,7 -490,7 +494,7 @@@ The effect of this is best shown by wa
          `---------'
  -----------------------------------------------------------------------
  +
 -Note the major differences in `N` and `P` over '--full-history':
 +Note the major differences in `N`, `P` and `Q` over '--full-history':
  +
  --
  * `N`'s parent list had `I` removed, because it is an ancestor of the
  +
  * `P`'s parent list similarly had `I` removed.  `P` was then
    removed completely, because it had one parent and is TREESAME.
 ++
 +* `Q`'s parent list had `Y` simplified to `X`. `X` was then removed, because it
 +  was a TREESAME root. `Q` was then removed completely, because it had one
 +  parent and is TREESAME.
  --
  
  Finally, there is a fifth simplification mode available:
@@@ -625,6 -617,10 +625,10 @@@ By default, the commits are shown in re
        Show no parents before all of its children are shown, but
        otherwise show commits in the commit timestamp order.
  
+ --author-date-order::
+       Show no parents before all of its children are shown, but
+       otherwise show commits in the author timestamp order.
  --topo-order::
        Show no parents before all of its children are shown, and
        avoid showing commits on multiple lines of history
diff --combined Makefile
index e1583761dfa58c3ede93b2076e4aa0963f2aa379,0246194abe63f565e575dd448cee655672fe6c4d..5a68fe5431043d2b1b98493434c0c7989d36757c
+++ b/Makefile
@@@ -69,9 -69,6 +69,9 @@@ all:
  # Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
  # doesn't support GNU extensions like --check and --statistics
  #
 +# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than
 +# INT_MAX bytes at once (e.g. MacOS X).
 +#
  # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
  # it specifies.
  #
  # Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
  # FNM_CASEFOLD GNU extension.
  #
 -# Define USE_WILDMATCH if you want to use Git's wildmatch
 +# Define NO_WILDMATCH if you do not want to use Git's wildmatch
  # implementation as fnmatch
  #
  # Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd
  # specify your own (or DarwinPort's) include directories and
  # library directories by defining CFLAGS and LDFLAGS appropriately.
  #
 +# Define NO_APPLE_COMMON_CRYPTO if you are building on Darwin/Mac OS X
 +# and do not want to use Apple's CommonCrypto library.  This allows you
 +# to provide your own OpenSSL library, for example from MacPorts.
 +#
  # Define BLK_SHA1 environment variable to make use of the bundled
  # optimized C SHA1 routine.
  #
  #
  # Define NO_REGEX if you have no or inferior regex support in your C library.
  #
 -# Define CYGWIN_V15_WIN32API if you are using Cygwin v1.7.x but are not
 -# using the current w32api packages. The recommended approach, however,
 -# is to update your installation if compilation errors occur.
 -#
  # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
  # user.
  #
@@@ -361,39 -358,33 +361,39 @@@ STRIP ?= stri
  # Among the variables below, these:
  #   gitexecdir
  #   template_dir
 -#   mandir
 -#   infodir
 -#   htmldir
  #   sysconfdir
  # can be specified as a relative path some/where/else;
  # this is interpreted as relative to $(prefix) and "git" at
  # runtime figures out where they are based on the path to the executable.
 +# Additionally, the following will be treated as relative by "git" if they
 +# begin with "$(prefix)/":
 +#   mandir
 +#   infodir
 +#   htmldir
  # This can help installing the suite in a relocatable way.
  
  prefix = $(HOME)
  bindir_relative = bin
  bindir = $(prefix)/$(bindir_relative)
 -mandir = share/man
 -infodir = share/info
 +mandir = $(prefix)/share/man
 +infodir = $(prefix)/share/info
  gitexecdir = libexec/git-core
  mergetoolsdir = $(gitexecdir)/mergetools
  sharedir = $(prefix)/share
  gitwebdir = $(sharedir)/gitweb
  localedir = $(sharedir)/locale
  template_dir = share/git-core/templates
 -htmldir = share/doc/git-doc
 +htmldir = $(prefix)/share/doc/git-doc
  ETC_GITCONFIG = $(sysconfdir)/gitconfig
  ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes
  lib = lib
  # DESTDIR =
  pathsep = :
  
 +mandir_relative = $(patsubst $(prefix)/%,%,$(mandir))
 +infodir_relative = $(patsubst $(prefix)/%,%,$(infodir))
 +htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir))
 +
  export prefix bindir sharedir sysconfdir gitwebdir localedir
  
  CC = cc
@@@ -463,7 -454,6 +463,7 @@@ SCRIPT_SH += git-mergetool.s
  SCRIPT_SH += git-pull.sh
  SCRIPT_SH += git-quiltimport.sh
  SCRIPT_SH += git-rebase.sh
 +SCRIPT_SH += git-remote-testgit.sh
  SCRIPT_SH += git-repack.sh
  SCRIPT_SH += git-request-pull.sh
  SCRIPT_SH += git-stash.sh
@@@ -491,18 -481,11 +491,18 @@@ SCRIPT_PERL += git-svn.per
  SCRIPT_PYTHON += git-remote-testpy.py
  SCRIPT_PYTHON += git-p4.py
  
 +NO_INSTALL += git-remote-testgit
 +NO_INSTALL += git-remote-testpy
 +
  # Generated files for scripts
  SCRIPT_SH_GEN = $(patsubst %.sh,%,$(SCRIPT_SH))
  SCRIPT_PERL_GEN = $(patsubst %.perl,%,$(SCRIPT_PERL))
  SCRIPT_PYTHON_GEN = $(patsubst %.py,%,$(SCRIPT_PYTHON))
  
 +SCRIPT_SH_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_SH_GEN))
 +SCRIPT_PERL_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_PERL_GEN))
 +SCRIPT_PYTHON_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_PYTHON_GEN))
 +
  # Individual rules to allow e.g.
  # "make -C ../.. SCRIPT_PERL=contrib/foo/bar.perl build-perl-script"
  # from subdirectories like contrib/*/
@@@ -512,12 -495,12 +512,12 @@@ build-sh-script: $(SCRIPT_SH_GEN
  build-python-script: $(SCRIPT_PYTHON_GEN)
  
  .PHONY: install-perl-script install-sh-script install-python-script
 -install-sh-script: $(SCRIPT_SH_GEN)
 -      $(INSTALL) $(SCRIPT_SH_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 -install-perl-script: $(SCRIPT_PERL_GEN)
 -      $(INSTALL) $(SCRIPT_PERL_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 -install-python-script: $(SCRIPT_PYTHON_GEN)
 -      $(INSTALL) $(SCRIPT_PYTHON_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 +install-sh-script: $(SCRIPT_SH_INS)
 +      $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 +install-perl-script: $(SCRIPT_PERL_INS)
 +      $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 +install-python-script: $(SCRIPT_PYTHON_INS)
 +      $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
  
  .PHONY: clean-perl-script clean-sh-script clean-python-script
  clean-sh-script:
@@@ -527,9 -510,9 +527,9 @@@ clean-perl-script
  clean-python-script:
        $(RM) $(SCRIPT_PYTHON_GEN)
  
 -SCRIPTS = $(SCRIPT_SH_GEN) \
 -        $(SCRIPT_PERL_GEN) \
 -        $(SCRIPT_PYTHON_GEN) \
 +SCRIPTS = $(SCRIPT_SH_INS) \
 +        $(SCRIPT_PERL_INS) \
 +        $(SCRIPT_PYTHON_INS) \
          git-instaweb
  
  ETAGS_TARGET = TAGS
@@@ -569,7 -552,7 +569,8 @@@ TEST_PROGRAMS_NEED_X += test-mergesor
  TEST_PROGRAMS_NEED_X += test-mktemp
  TEST_PROGRAMS_NEED_X += test-parse-options
  TEST_PROGRAMS_NEED_X += test-path-utils
+ TEST_PROGRAMS_NEED_X += test-prio-queue
 +TEST_PROGRAMS_NEED_X += test-read-cache
  TEST_PROGRAMS_NEED_X += test-regex
  TEST_PROGRAMS_NEED_X += test-revision-walking
  TEST_PROGRAMS_NEED_X += test-run-command
@@@ -685,8 -668,6 +686,8 @@@ LIB_H += help.
  LIB_H += http.h
  LIB_H += kwset.h
  LIB_H += levenshtein.h
 +LIB_H += line-log.h
 +LIB_H += line-range.h
  LIB_H += list-objects.h
  LIB_H += ll-merge.h
  LIB_H += log-tree.h
@@@ -696,15 -677,16 +697,16 @@@ LIB_H += merge-recursive.
  LIB_H += mergesort.h
  LIB_H += notes-cache.h
  LIB_H += notes-merge.h
 +LIB_H += notes-utils.h
  LIB_H += notes.h
  LIB_H += object.h
 -LIB_H += pack-refs.h
  LIB_H += pack-revindex.h
  LIB_H += pack.h
  LIB_H += parse-options.h
  LIB_H += patch-ids.h
  LIB_H += pathspec.h
  LIB_H += pkt-line.h
+ LIB_H += prio-queue.h
  LIB_H += progress.h
  LIB_H += prompt.h
  LIB_H += quote.h
@@@ -815,8 -797,6 +817,8 @@@ LIB_OBJS += hex.
  LIB_OBJS += ident.o
  LIB_OBJS += kwset.o
  LIB_OBJS += levenshtein.o
 +LIB_OBJS += line-log.o
 +LIB_OBJS += line-range.o
  LIB_OBJS += list-objects.o
  LIB_OBJS += ll-merge.o
  LIB_OBJS += lockfile.o
@@@ -831,9 -811,9 +833,9 @@@ LIB_OBJS += name-hash.
  LIB_OBJS += notes.o
  LIB_OBJS += notes-cache.o
  LIB_OBJS += notes-merge.o
 +LIB_OBJS += notes-utils.o
  LIB_OBJS += object.o
  LIB_OBJS += pack-check.o
 -LIB_OBJS += pack-refs.o
  LIB_OBJS += pack-revindex.o
  LIB_OBJS += pack-write.o
  LIB_OBJS += pager.o
@@@ -846,6 -826,7 +848,7 @@@ LIB_OBJS += pathspec.
  LIB_OBJS += pkt-line.o
  LIB_OBJS += preload-index.o
  LIB_OBJS += pretty.o
+ LIB_OBJS += prio-queue.o
  LIB_OBJS += progress.o
  LIB_OBJS += prompt.o
  LIB_OBJS += quote.o
@@@ -1070,11 -1051,6 +1073,11 @@@ ifeq ($(uname_S),Darwin
                        BASIC_LDFLAGS += -L/opt/local/lib
                endif
        endif
 +      ifndef NO_APPLE_COMMON_CRYPTO
 +              APPLE_COMMON_CRYPTO = YesPlease
 +              COMPAT_CFLAGS += -DAPPLE_COMMON_CRYPTO
 +      endif
 +      NO_REGEX = YesPlease
        PTHREAD_LIBS =
  endif
  
@@@ -1282,7 -1258,7 +1285,7 @@@ ifdef NO_FNMATCH_CASEFOL
        COMPAT_OBJS += compat/fnmatch/fnmatch.o
  endif
  endif
 -ifdef USE_WILDMATCH
 +ifndef NO_WILDMATCH
        COMPAT_CFLAGS += -DUSE_WILDMATCH
  endif
  ifdef NO_SETENV
@@@ -1408,17 -1384,11 +1411,17 @@@ ifdef PPC_SHA
        SHA1_HEADER = "ppc/sha1.h"
        LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
        LIB_H += ppc/sha1.h
 +else
 +ifdef APPLE_COMMON_CRYPTO
 +      COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL
 +      SHA1_HEADER = <CommonCrypto/CommonDigest.h>
  else
        SHA1_HEADER = <openssl/sha.h>
        EXTLIBS += $(LIB_4_CRYPTO)
  endif
  endif
 +endif
 +
  ifdef NO_PERL_MAKEMAKER
        export NO_PERL_MAKEMAKER
  endif
@@@ -1476,6 -1446,9 +1479,6 @@@ ifdef NO_REGE
        COMPAT_CFLAGS += -Icompat/regex
        COMPAT_OBJS += compat/regex/regex.o
  endif
 -ifdef CYGWIN_V15_WIN32API
 -      COMPAT_CFLAGS += -DCYGWIN_V15_WIN32API
 -endif
  
  ifdef USE_NED_ALLOCATOR
         COMPAT_CFLAGS += -Icompat/nedmalloc
@@@ -1490,11 -1463,6 +1493,11 @@@ ifndef NO_MSGFMT_EXTENDED_OPTION
        MSGFMT += --check --statistics
  endif
  
 +ifdef NEEDS_CLIPPED_WRITE
 +      BASIC_CFLAGS += -DNEEDS_CLIPPED_WRITE
 +      COMPAT_OBJS += compat/clipped-write.o
 +endif
 +
  ifneq (,$(XDL_FAST_HASH))
        BASIC_CFLAGS += -DXDL_FAST_HASH
  endif
@@@ -1532,7 -1500,6 +1535,7 @@@ ifndef 
        QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
        QUIET_GCOV     = @echo '   ' GCOV $@;
        QUIET_SP       = @echo '   ' SP $<;
 +      QUIET_RC       = @echo '   ' RC $@;
        QUIET_SUBDIR0  = +@subdir=
        QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
                         $(MAKE) $(PRINT_DIR) -C $$subdir
@@@ -1575,12 -1542,12 +1578,12 @@@ ETC_GITATTRIBUTES_SQ = $(subst ','\'',$
  DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
  bindir_SQ = $(subst ','\'',$(bindir))
  bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
 -mandir_SQ = $(subst ','\'',$(mandir))
 -infodir_SQ = $(subst ','\'',$(infodir))
 +mandir_relative_SQ = $(subst ','\'',$(mandir_relative))
 +infodir_relative_SQ = $(subst ','\'',$(infodir_relative))
  localedir_SQ = $(subst ','\'',$(localedir))
  gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
  template_dir_SQ = $(subst ','\'',$(template_dir))
 -htmldir_SQ = $(subst ','\'',$(htmldir))
 +htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative))
  prefix_SQ = $(subst ','\'',$(prefix))
  gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
  
@@@ -1675,7 -1642,7 +1678,7 @@@ please_set_SHELL_PATH_to_a_more_modern_
  shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
  
  strip: $(PROGRAMS) git$X
 -      $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 +      $(STRIP) $(STRIP_OPTS) $^
  
  ### Target-specific flags and dependencies
  
  
  git.sp git.s git.o: GIT-PREFIX
  git.sp git.s git.o: EXTRA_CPPFLAGS = \
 -      '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
 -      '-DGIT_MAN_PATH="$(mandir_SQ)"' \
 -      '-DGIT_INFO_PATH="$(infodir_SQ)"'
 +      '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
 +      '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
 +      '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
  
  git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
@@@ -1724,9 -1691,9 +1727,9 @@@ help.sp help.s help.o: common-cmds.
  
  builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX
  builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
 -      '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
 -      '-DGIT_MAN_PATH="$(mandir_SQ)"' \
 -      '-DGIT_INFO_PATH="$(infodir_SQ)"'
 +      '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
 +      '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
 +      '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
  
  version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT
  version.sp version.s version.o: EXTRA_CPPFLAGS = \
  
  $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && \
 -      ln git$X $@ 2>/dev/null || \
 -      ln -s git$X $@ 2>/dev/null || \
 -      cp git$X $@
 +      ln $< $@ 2>/dev/null || \
 +      ln -s $< $@ 2>/dev/null || \
 +      cp $< $@
  
  common-cmds.h: ./generate-cmdlist.sh command-list.txt
  
@@@ -1778,11 -1745,6 +1781,11 @@@ $(SCRIPT_LIB) : % : %.sh GIT-SCRIPT-DEF
        $(QUIET_GEN)$(cmd_munge_script) && \
        mv $@+ $@
  
 +git.res: git.rc GIT-VERSION-FILE
 +      $(QUIET_RC)$(RC) \
 +        $(join -DMAJOR= -DMINOR= -DPATCH=, $(wordlist 1,3,$(subst -, ,$(subst ., ,$(GIT_VERSION))))) \
 +        -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" $< -o $@
 +
  ifndef NO_PERL
  $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
  
@@@ -1807,7 -1769,7 +1810,7 @@@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % 
            -e '        x' \
            -e '}' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
 -          $@.perl >$@+ && \
 +          $< >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
  
@@@ -1831,8 -1793,8 +1834,8 @@@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git
  endif # NO_PERL
  
  ifndef NO_PYTHON
 -$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
 -$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
 +$(SCRIPT_PYTHON_GEN): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
 +$(SCRIPT_PYTHON_GEN): % : %.py
        $(QUIET_GEN)$(RM) $@ $@+ && \
        INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
                --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
        sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
            -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
            -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
 -          $@.py >$@+ && \
 +          $< >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
  else # NO_PYTHON
 -$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
 +$(SCRIPT_PYTHON_GEN): % : unimplemented.sh
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
@@@ -2039,7 -2001,6 +2042,7 @@@ endi
  ifdef USE_NED_ALLOCATOR
  compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
        -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
 +compat/nedmalloc/nedmalloc.sp: SPARSE_FLAGS += -Wno-non-pointer-null
  endif
  
  git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
@@@ -2071,13 -2032,13 +2074,13 @@@ $(REMOTE_CURL_PRIMARY): remote-curl.o h
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
  
  $(LIB_FILE): $(LIB_OBJS)
 -      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
 +      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
  
  $(XDIFF_LIB): $(XDIFF_OBJS)
 -      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 +      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
  
  $(VCSSVN_LIB): $(VCSSVN_OBJS)
 -      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
 +      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
  
  export DEFAULT_EDITOR DEFAULT_PAGER
  
@@@ -2195,9 -2156,6 +2198,9 @@@ GIT-BUILD-OPTIONS: FORC
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
        @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
 +ifdef TEST_OUTPUT_DIRECTORY
 +      @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@
 +endif
  ifdef GIT_TEST_OPTS
        @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
  endif
@@@ -2237,7 -2195,6 +2240,7 @@@ endi
  test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
  
  all:: $(TEST_PROGRAMS) $(test_bindir_programs)
 +all:: $(NO_INSTALL)
  
  bin-wrappers/%: wrap-for-bin.sh
        @mkdir -p bin-wrappers
@@@ -2483,11 -2440,11 +2486,11 @@@ profile-clean
        $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
        $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
  
 -clean: profile-clean
 -      $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
 +clean: profile-clean coverage-clean
 +      $(RM) *.o *.res block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
                builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
        $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
 -      $(RM) $(TEST_PROGRAMS)
 +      $(RM) $(TEST_PROGRAMS) $(NO_INSTALL)
        $(RM) -r bin-wrappers $(dep_dirs)
        $(RM) -r po/build/
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
@@@ -2564,34 -2521,29 +2567,34 @@@ check-builtins:
  
  ### Test suite coverage testing
  #
 -.PHONY: coverage coverage-clean coverage-build coverage-report
 +.PHONY: coverage coverage-clean coverage-compile coverage-test coverage-report
 +.PHONY: coverage-clean-results
  
  coverage:
 -      $(MAKE) coverage-build
 -      $(MAKE) coverage-report
 +      $(MAKE) coverage-test
 +      $(MAKE) coverage-untested-functions
  
  object_dirs := $(sort $(dir $(OBJECTS)))
 -coverage-clean:
 +coverage-clean-results:
        $(RM) $(addsuffix *.gcov,$(object_dirs))
        $(RM) $(addsuffix *.gcda,$(object_dirs))
 -      $(RM) $(addsuffix *.gcno,$(object_dirs))
        $(RM) coverage-untested-functions
        $(RM) -r cover_db/
        $(RM) -r cover_db_html/
  
 +coverage-clean: coverage-clean-results
 +      $(RM) $(addsuffix *.gcno,$(object_dirs))
 +
  COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
  COVERAGE_LDFLAGS = $(CFLAGS)  -O0 -lgcov
  GCOVFLAGS = --preserve-paths --branch-probabilities --all-blocks
  
 -coverage-build: coverage-clean
 +coverage-compile:
        $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
 +
 +coverage-test: coverage-clean-results coverage-compile
        $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
 -              -j1 test
 +              DEFAULT_TEST_TARGET=test -j1 test
  
  coverage-report:
        $(QUIET_GCOV)for dir in $(object_dirs); do \
diff --combined builtin/log.c
index 9e2123295ffa80ecab83977099ed6a43fd4a93f9,8d26042b01a3de76e1d98c4dc498ca9737759d2a..e3222ed9f979e3140cbfc88ad2839c573c89bf06
  #include "remote.h"
  #include "string-list.h"
  #include "parse-options.h"
 +#include "line-log.h"
  #include "branch.h"
  #include "streaming.h"
  #include "version.h"
  #include "mailmap.h"
 +#include "gpg-interface.h"
  
  /* Set a default date-time format for git log ("log.date" config variable) */
  static const char *default_date_mode = NULL;
@@@ -38,17 -36,11 +38,17 @@@ static const char *fmt_patch_subject_pr
  static const char *fmt_pretty;
  
  static const char * const builtin_log_usage[] = {
 -      N_("git log [<options>] [<since>..<until>] [[--] <path>...]\n")
 +      N_("git log [<options>] [<revision range>] [[--] <path>...]\n")
        N_("   or: git show [options] <object>..."),
        NULL
  };
  
 +struct line_opt_callback_data {
 +      struct rev_info *rev;
 +      const char *prefix;
 +      struct string_list args;
 +};
 +
  static int parse_decoration_style(const char *var, const char *value)
  {
        switch (git_config_maybe_bool(var, value)) {
@@@ -83,19 -75,6 +83,19 @@@ static int decorate_callback(const stru
        return 0;
  }
  
 +static int log_line_range_callback(const struct option *option, const char *arg, int unset)
 +{
 +      struct line_opt_callback_data *data = option->value;
 +
 +      if (!arg)
 +              return -1;
 +
 +      data->rev->line_level_traverse = 1;
 +      string_list_append(&data->args, arg);
 +
 +      return 0;
 +}
 +
  static void cmd_log_init_defaults(struct rev_info *rev)
  {
        if (fmt_pretty)
@@@ -118,23 -97,16 +118,23 @@@ static void cmd_log_init_finish(int arg
  {
        struct userformat_want w;
        int quiet = 0, source = 0, mailmap = 0;
 +      static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
  
        const struct option builtin_log_options[] = {
 -              OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")),
 -              OPT_BOOLEAN(0, "source", &source, N_("show source")),
 -              OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")),
 +              OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
 +              OPT_BOOL(0, "source", &source, N_("show source")),
 +              OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
                  PARSE_OPT_OPTARG, decorate_callback},
 +              OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
 +                           "Process line range n,m in file, counting from 1",
 +                           log_line_range_callback),
                OPT_END()
        };
  
 +      line_cb.rev = rev;
 +      line_cb.prefix = prefix;
 +
        mailmap = use_mailmap_config;
        argc = parse_options(argc, argv, prefix,
                             builtin_log_options, builtin_log_usage,
                rev->show_decorations = 1;
                load_ref_decorations(decoration_style);
        }
 +
 +      if (rev->line_level_traverse)
 +              line_log_init(rev, line_cb.prefix, &line_cb.args);
 +
        setup_pager();
  }
  
@@@ -237,7 -205,7 +237,7 @@@ static void log_show_early(struct rev_i
        int i = revs->early_output;
        int show_header = 1;
  
-       sort_in_topological_order(&list, revs->lifo);
+       sort_in_topological_order(&list, revs->sort_order);
        while (list && i) {
                struct commit *commit = list->item;
                switch (simplify_commit(revs, commit)) {
@@@ -399,8 -367,6 +399,8 @@@ static int git_log_config(const char *v
  
        if (grep_config(var, value, cb) < 0)
                return -1;
 +      if (git_gpg_config(var, value, cb) < 0)
 +              return -1;
        return git_diff_ui_config(var, value, cb);
  }
  
@@@ -653,14 -619,6 +653,14 @@@ static void add_header(const char *valu
  static int thread;
  static int do_signoff;
  static const char *signature = git_version_string;
 +static int config_cover_letter;
 +
 +enum {
 +      COVER_UNSET,
 +      COVER_OFF,
 +      COVER_ON,
 +      COVER_AUTO
 +};
  
  static int git_format_config(const char *var, const char *value, void *cb)
  {
        }
        if (!strcmp(var, "format.signature"))
                return git_config_string(&signature, var, value);
 +      if (!strcmp(var, "format.coverletter")) {
 +              if (value && !strcasecmp(value, "auto")) {
 +                      config_cover_letter = COVER_AUTO;
 +                      return 0;
 +              }
 +              config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
 +              return 0;
 +      }
  
        return git_log_config(var, value, cb);
  }
@@@ -841,37 -791,9 +841,37 @@@ static void add_branch_description(stru
        }
  }
  
 +static char *find_branch_name(struct rev_info *rev)
 +{
 +      int i, positive = -1;
 +      unsigned char branch_sha1[20];
 +      const unsigned char *tip_sha1;
 +      const char *ref;
 +      char *full_ref, *branch = NULL;
 +
 +      for (i = 0; i < rev->cmdline.nr; i++) {
 +              if (rev->cmdline.rev[i].flags & UNINTERESTING)
 +                      continue;
 +              if (positive < 0)
 +                      positive = i;
 +              else
 +                      return NULL;
 +      }
 +      if (positive < 0)
 +              return NULL;
 +      ref = rev->cmdline.rev[positive].name;
 +      tip_sha1 = rev->cmdline.rev[positive].item->sha1;
 +      if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
 +          !prefixcmp(full_ref, "refs/heads/") &&
 +          !hashcmp(tip_sha1, branch_sha1))
 +              branch = xstrdup(full_ref + strlen("refs/heads/"));
 +      free(full_ref);
 +      return branch;
 +}
 +
  static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              struct commit *origin,
 -                            int nr, struct commit **list, struct commit *head,
 +                            int nr, struct commit **list,
                              const char *branch_name,
                              int quiet)
  {
        struct diff_options opts;
        int need_8bit_cte = 0;
        struct pretty_print_context pp = {0};
 +      struct commit *head = list[0];
  
        if (rev->commit_format != CMIT_FMT_EMAIL)
                die(_("Cover letter needs email format"));
                if (has_non_ascii(list[i]->buffer))
                        need_8bit_cte = 1;
  
 +      if (!branch_name)
 +              branch_name = find_branch_name(rev);
 +
        msg = body;
        pp.fmt = CMIT_FMT_EMAIL;
        pp.date_mode = DATE_RFC2822;
@@@ -1112,6 -1030,45 +1112,6 @@@ static int cc_callback(const struct opt
        return 0;
  }
  
 -static char *find_branch_name(struct rev_info *rev)
 -{
 -      int i, positive = -1;
 -      unsigned char branch_sha1[20];
 -      const unsigned char *tip_sha1;
 -      const char *ref;
 -      char *full_ref, *branch = NULL;
 -
 -      for (i = 0; i < rev->cmdline.nr; i++) {
 -              if (rev->cmdline.rev[i].flags & UNINTERESTING)
 -                      continue;
 -              if (positive < 0)
 -                      positive = i;
 -              else
 -                      return NULL;
 -      }
 -      if (0 <= positive) {
 -              ref = rev->cmdline.rev[positive].name;
 -              tip_sha1 = rev->cmdline.rev[positive].item->sha1;
 -      } else if (!rev->cmdline.nr && rev->pending.nr == 1 &&
 -                 !strcmp(rev->pending.objects[0].name, "HEAD")) {
 -              /*
 -               * No actual ref from command line, but "HEAD" from
 -               * rev->def was added in setup_revisions()
 -               * e.g. format-patch --cover-letter -12
 -               */
 -              ref = "HEAD";
 -              tip_sha1 = rev->pending.objects[0].item->sha1;
 -      } else {
 -              return NULL;
 -      }
 -      if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
 -          !prefixcmp(full_ref, "refs/heads/") &&
 -          !hashcmp(tip_sha1, branch_sha1))
 -              branch = xstrdup(full_ref + strlen("refs/heads/"));
 -      free(full_ref);
 -      return branch;
 -}
 -
  int cmd_format_patch(int argc, const char **argv, const char *prefix)
  {
        struct commit *commit;
        int start_number = -1;
        int just_numbers = 0;
        int ignore_if_in_upstream = 0;
 -      int cover_letter = 0;
 +      int cover_letter = -1;
        int boundary_count = 0;
        int no_binary_diff = 0;
 -      struct commit *origin = NULL, *head = NULL;
 +      struct commit *origin = NULL;
        const char *in_reply_to = NULL;
        struct patch_ids ids;
 -      char *add_signoff = NULL;
        struct strbuf buf = STRBUF_INIT;
        int use_patch_format = 0;
        int quiet = 0;
                { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
                            N_("use [PATCH] even with multiple patches"),
                            PARSE_OPT_NOARG, no_numbered_callback },
 -              OPT_BOOLEAN('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
 -              OPT_BOOLEAN(0, "stdout", &use_stdout,
 +              OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
 +              OPT_BOOL(0, "stdout", &use_stdout,
                            N_("print patches to standard out")),
 -              OPT_BOOLEAN(0, "cover-letter", &cover_letter,
 +              OPT_BOOL(0, "cover-letter", &cover_letter,
                            N_("generate a cover letter")),
 -              OPT_BOOLEAN(0, "numbered-files", &just_numbers,
 +              OPT_BOOL(0, "numbered-files", &just_numbers,
                            N_("use simple number sequence for output file names")),
                OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
                            N_("use <sfx> instead of '.patch'")),
                rev.subject_prefix = strbuf_detach(&sprefix, NULL);
        }
  
 -      if (do_signoff) {
 -              const char *committer;
 -              const char *endpos;
 -              committer = git_committer_info(IDENT_STRICT);
 -              endpos = strchr(committer, '>');
 -              if (!endpos)
 -                      die(_("bogus committer info %s"), committer);
 -              add_signoff = xmemdupz(committer, endpos - committer + 1);
 -      }
 -
        for (i = 0; i < extra_hdr.nr; i++) {
                strbuf_addstr(&buf, extra_hdr.items[i].string);
                strbuf_addch(&buf, '\n');
        }
  
        if (rev.pending.nr == 1) {
 +              int check_head = 0;
 +
                if (rev.max_count < 0 && !rev.show_root_diff) {
                        /*
                         * This is traditional behaviour of "git format-patch
                         * origin" that prepares what the origin side still
                         * does not have.
                         */
 -                      unsigned char sha1[20];
 -                      const char *ref;
 -
                        rev.pending.objects[0].item->flags |= UNINTERESTING;
                        add_head_to_pending(&rev);
 -                      ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
 -                      if (ref && !prefixcmp(ref, "refs/heads/"))
 -                              branch_name = xstrdup(ref + strlen("refs/heads/"));
 -                      else
 -                              branch_name = xstrdup(""); /* no branch */
 +                      check_head = 1;
                }
                /*
                 * Otherwise, it is "format-patch -22 HEAD", and/or
                 * "format-patch --root HEAD".  The user wants
                 * get_revision() to do the usual traversal.
                 */
 +
 +              if (!strcmp(rev.pending.objects[0].name, "HEAD"))
 +                      check_head = 1;
 +
 +              if (check_head) {
 +                      unsigned char sha1[20];
 +                      const char *ref;
 +                      ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
 +                      if (ref && !prefixcmp(ref, "refs/heads/"))
 +                              branch_name = xstrdup(ref + strlen("refs/heads/"));
 +                      else
 +                              branch_name = xstrdup(""); /* no branch */
 +              }
        }
  
        /*
         */
        rev.show_root_diff = 1;
  
 -      if (cover_letter) {
 -              /*
 -               * NEEDSWORK:randomly pick one positive commit to show
 -               * diffstat; this is often the tip and the command
 -               * happens to do the right thing in most cases, but a
 -               * complex command like "--cover-letter a b c ^bottom"
 -               * picks "c" and shows diffstat between bottom..c
 -               * which may not match what the series represents at
 -               * all and totally broken.
 -               */
 -              int i;
 -              for (i = 0; i < rev.pending.nr; i++) {
 -                      struct object *o = rev.pending.objects[i].item;
 -                      if (!(o->flags & UNINTERESTING))
 -                              head = (struct commit *)o;
 -              }
 -              /* There is nothing to show; it is not an error, though. */
 -              if (!head)
 -                      return 0;
 -              if (!branch_name)
 -                      branch_name = find_branch_name(&rev);
 -      }
 -
        if (ignore_if_in_upstream) {
                /* Don't say anything if head and upstream are the same. */
                if (rev.pending.nr == 2) {
                list = xrealloc(list, nr * sizeof(list[0]));
                list[nr - 1] = commit;
        }
 +      if (nr == 0)
 +              /* nothing to do */
 +              return 0;
        total = nr;
        if (!keep_subject && auto_number && total > 1)
                numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
 +      if (cover_letter == -1) {
 +              if (config_cover_letter == COVER_AUTO)
 +                      cover_letter = (total > 1);
 +              else
 +                      cover_letter = (config_cover_letter == COVER_ON);
 +      }
 +
        if (in_reply_to || thread || cover_letter)
                rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
        if (in_reply_to) {
                if (thread)
                        gen_message_id(&rev, "cover");
                make_cover_letter(&rev, use_stdout,
 -                                origin, nr, list, head, branch_name, quiet);
 +                                origin, nr, list, branch_name, quiet);
                total++;
                start_number--;
        }
 -      rev.add_signoff = add_signoff;
 +      rev.add_signoff = do_signoff;
        while (0 <= --nr) {
                int shown;
                commit = list[nr];
diff --combined builtin/show-branch.c
index 90fc6b1b9d2a397ae43dfc4c0aba6bc192c9823c,7c57985c40095a4e068f5f60772cf46617f52a55..99ec4af224464f5c6c82599dcc4ca18b6abaa7f7
@@@ -162,28 -162,29 +162,28 @@@ static void name_commits(struct commit_
                        nth = 0;
                        while (parents) {
                                struct commit *p = parents->item;
 -                              char newname[1000], *en;
 +                              struct strbuf newname = STRBUF_INIT;
                                parents = parents->next;
                                nth++;
                                if (p->util)
                                        continue;
 -                              en = newname;
                                switch (n->generation) {
                                case 0:
 -                                      en += sprintf(en, "%s", n->head_name);
 +                                      strbuf_addstr(&newname, n->head_name);
                                        break;
                                case 1:
 -                                      en += sprintf(en, "%s^", n->head_name);
 +                                      strbuf_addf(&newname, "%s^", n->head_name);
                                        break;
                                default:
 -                                      en += sprintf(en, "%s~%d",
 -                                              n->head_name, n->generation);
 +                                      strbuf_addf(&newname, "%s~%d",
 +                                                  n->head_name, n->generation);
                                        break;
                                }
                                if (nth == 1)
 -                                      en += sprintf(en, "^");
 +                                      strbuf_addch(&newname, '^');
                                else
 -                                      en += sprintf(en, "^%d", nth);
 -                              name_commit(p, xstrdup(newname), 0);
 +                                      strbuf_addf(&newname, "^%d", nth);
 +                              name_commit(p, strbuf_detach(&newname, NULL), 0);
                                i++;
                                name_first_parent_chain(p);
                        }
@@@ -630,7 -631,7 +630,7 @@@ int cmd_show_branch(int ac, const char 
        int num_rev, i, extra = 0;
        int all_heads = 0, all_remotes = 0;
        int all_mask, all_revs;
-       int lifo = 1;
+       enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
        char head[128];
        const char *head_p;
        int head_len;
                            N_("show possible merge bases")),
                OPT_BOOLEAN(0, "independent", &independent,
                            N_("show refs unreachable from any other ref")),
-               OPT_BOOLEAN(0, "topo-order", &lifo,
-                           N_("show commits in topological order")),
+               OPT_SET_INT(0, "topo-order", &sort_order,
+                           N_("show commits in topological order"),
+                           REV_SORT_IN_GRAPH_ORDER),
                OPT_BOOLEAN(0, "topics", &topics,
                            N_("show only commits not on the first branch")),
                OPT_SET_INT(0, "sparse", &dense,
                            N_("show merges reachable from only one tip"), 0),
-               OPT_SET_INT(0, "date-order", &lifo,
+               OPT_SET_INT(0, "date-order", &sort_order,
                            N_("show commits where no parent comes before its "
-                              "children"), 0),
+                              "children"),
+                           REV_SORT_BY_COMMIT_DATE),
                { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
                            N_("show <n> most recent ref-log entries starting at "
                               "base"),
                exit(0);
  
        /* Sort topologically */
-       sort_in_topological_order(&seen, lifo);
+       sort_in_topological_order(&seen, sort_order);
  
        /* Give names to commits */
        if (!sha1_name && !no_name)
diff --combined commit.c
index a1096a2b5a2c6b04155f1efc093da6fb5059551f,076c1fa606b3541d34b45a7cf67be1eb4bae9626..521e49c3094acc06340e15f7be27722be9b03ee8
+++ b/commit.c
@@@ -9,6 -9,7 +9,7 @@@
  #include "gpg-interface.h"
  #include "mergesort.h"
  #include "commit-slab.h"
+ #include "prio-queue.h"
  
  static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
  
@@@ -468,23 -469,14 +469,23 @@@ static void clear_commit_marks_1(struc
        }
  }
  
 -void clear_commit_marks(struct commit *commit, unsigned int mark)
 +void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark)
  {
        struct commit_list *list = NULL;
 -      commit_list_insert(commit, &list);
 +
 +      while (nr--) {
 +              commit_list_insert(*commit, &list);
 +              commit++;
 +      }
        while (list)
                clear_commit_marks_1(&list, pop_commit(&list), mark);
  }
  
 +void clear_commit_marks(struct commit *commit, unsigned int mark)
 +{
 +      clear_commit_marks_many(1, &commit, mark);
 +}
 +
  void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
  {
        struct object *object;
@@@ -518,31 -510,124 +519,124 @@@ struct commit *pop_commit(struct commit
  /* count number of children that have not been emitted */
  define_commit_slab(indegree_slab, int);
  
+ /* record author-date for each commit object */
+ define_commit_slab(author_date_slab, unsigned long);
+ static void record_author_date(struct author_date_slab *author_date,
+                              struct commit *commit)
+ {
+       const char *buf, *line_end;
+       char *buffer = NULL;
+       struct ident_split ident;
+       char *date_end;
+       unsigned long date;
+       if (!commit->buffer) {
+               unsigned long size;
+               enum object_type type;
+               buffer = read_sha1_file(commit->object.sha1, &type, &size);
+               if (!buffer)
+                       return;
+       }
+       for (buf = commit->buffer ? commit->buffer : buffer;
+            buf;
+            buf = line_end + 1) {
+               line_end = strchrnul(buf, '\n');
+               if (prefixcmp(buf, "author ")) {
+                       if (!line_end[0] || line_end[1] == '\n')
+                               return; /* end of header */
+                       continue;
+               }
+               if (split_ident_line(&ident,
+                                    buf + strlen("author "),
+                                    line_end - (buf + strlen("author "))) ||
+                   !ident.date_begin || !ident.date_end)
+                       goto fail_exit; /* malformed "author" line */
+               break;
+       }
+       date = strtoul(ident.date_begin, &date_end, 10);
+       if (date_end != ident.date_end)
+               goto fail_exit; /* malformed date */
+       *(author_date_slab_at(author_date, commit)) = date;
+ fail_exit:
+       free(buffer);
+ }
+ static int compare_commits_by_author_date(const void *a_, const void *b_,
+                                         void *cb_data)
+ {
+       const struct commit *a = a_, *b = b_;
+       struct author_date_slab *author_date = cb_data;
+       unsigned long a_date = *(author_date_slab_at(author_date, a));
+       unsigned long b_date = *(author_date_slab_at(author_date, b));
+       /* newer commits with larger date first */
+       if (a_date < b_date)
+               return 1;
+       else if (a_date > b_date)
+               return -1;
+       return 0;
+ }
+ static int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)
+ {
+       const struct commit *a = a_, *b = b_;
+       /* newer commits with larger date first */
+       if (a->date < b->date)
+               return 1;
+       else if (a->date > b->date)
+               return -1;
+       return 0;
+ }
  /*
   * Performs an in-place topological sort on the list supplied.
   */
- void sort_in_topological_order(struct commit_list ** list, int lifo)
+ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order sort_order)
  {
        struct commit_list *next, *orig = *list;
-       struct commit_list *work, **insert;
        struct commit_list **pptr;
        struct indegree_slab indegree;
+       struct prio_queue queue;
+       struct commit *commit;
+       struct author_date_slab author_date;
  
        if (!orig)
                return;
        *list = NULL;
  
        init_indegree_slab(&indegree);
+       memset(&queue, '\0', sizeof(queue));
+       switch (sort_order) {
+       default: /* REV_SORT_IN_GRAPH_ORDER */
+               queue.compare = NULL;
+               break;
+       case REV_SORT_BY_COMMIT_DATE:
+               queue.compare = compare_commits_by_commit_date;
+               break;
+       case REV_SORT_BY_AUTHOR_DATE:
+               init_author_date_slab(&author_date);
+               queue.compare = compare_commits_by_author_date;
+               queue.cb_data = &author_date;
+               break;
+       }
  
        /* Mark them and clear the indegree */
        for (next = orig; next; next = next->next) {
                struct commit *commit = next->item;
                *(indegree_slab_at(&indegree, commit)) = 1;
+               /* also record the author dates, if needed */
+               if (sort_order == REV_SORT_BY_AUTHOR_DATE)
+                       record_author_date(&author_date, commit);
        }
  
        /* update the indegree */
        for (next = orig; next; next = next->next) {
-               struct commit_list * parents = next->item->parents;
+               struct commit_list *parents = next->item->parents;
                while (parents) {
                        struct commit *parent = parents->item;
                        int *pi = indegree_slab_at(&indegree, parent);
         *
         * the tips serve as a starting set for the work queue.
         */
-       work = NULL;
-       insert = &work;
        for (next = orig; next; next = next->next) {
                struct commit *commit = next->item;
  
                if (*(indegree_slab_at(&indegree, commit)) == 1)
-                       insert = &commit_list_insert(commit, insert)->next;
+                       prio_queue_put(&queue, commit);
        }
  
-       /* process the list in topological order */
-       if (!lifo)
-               commit_list_sort_by_date(&work);
+       /*
+        * This is unfortunate; the initial tips need to be shown
+        * in the order given from the revision traversal machinery.
+        */
+       if (sort_order == REV_SORT_IN_GRAPH_ORDER)
+               prio_queue_reverse(&queue);
+       /* We no longer need the commit list */
+       free_commit_list(orig);
  
        pptr = list;
        *list = NULL;
-       while (work) {
-               struct commit *commit;
-               struct commit_list *parents, *work_item;
-               work_item = work;
-               work = work_item->next;
-               work_item->next = NULL;
+       while ((commit = prio_queue_get(&queue)) != NULL) {
+               struct commit_list *parents;
  
-               commit = work_item->item;
                for (parents = commit->parents; parents ; parents = parents->next) {
                        struct commit *parent = parents->item;
                        int *pi = indegree_slab_at(&indegree, parent);
                         * when all their children have been emitted thereby
                         * guaranteeing topological order.
                         */
-                       if (--(*pi) == 1) {
-                               if (!lifo)
-                                       commit_list_insert_by_date(parent, &work);
-                               else
-                                       commit_list_insert(parent, &work);
-                       }
+                       if (--(*pi) == 1)
+                               prio_queue_put(&queue, parent);
                }
                /*
-                * work_item is a commit all of whose children
-                * have already been emitted. we can emit it now.
+                * all children of commit have already been
+                * emitted. we can emit it now.
                 */
                *(indegree_slab_at(&indegree, commit)) = 0;
-               *pptr = work_item;
-               pptr = &work_item->next;
+               pptr = &commit_list_insert(commit, pptr)->next;
        }
  
        clear_indegree_slab(&indegree);
+       clear_prio_queue(&queue);
+       if (sort_order == REV_SORT_BY_AUTHOR_DATE)
+               clear_author_date_slab(&author_date);
  }
  
  /* merge-base stuff */
@@@ -825,7 -907,8 +916,7 @@@ struct commit_list *get_merge_bases_man
        if (!result || !result->next) {
                if (cleanup) {
                        clear_commit_marks(one, all_flags);
 -                      for (i = 0; i < n; i++)
 -                              clear_commit_marks(twos[i], all_flags);
 +                      clear_commit_marks_many(n, twos, all_flags);
                }
                return result;
        }
        free_commit_list(result);
  
        clear_commit_marks(one, all_flags);
 -      for (i = 0; i < n; i++)
 -              clear_commit_marks(twos[i], all_flags);
 +      clear_commit_marks_many(n, twos, all_flags);
  
        cnt = remove_redundant(rslt, cnt);
        result = NULL;
@@@ -878,36 -962,25 +969,36 @@@ int is_descendant_of(struct commit *com
  }
  
  /*
 - * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
 + * Is "commit" an ancestor of one of the "references"?
   */
 -int in_merge_bases(struct commit *commit, struct commit *reference)
 +int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
  {
        struct commit_list *bases;
 -      int ret = 0;
 +      int ret = 0, i;
  
 -      if (parse_commit(commit) || parse_commit(reference))
 +      if (parse_commit(commit))
                return ret;
 +      for (i = 0; i < nr_reference; i++)
 +              if (parse_commit(reference[i]))
 +                      return ret;
  
 -      bases = paint_down_to_common(commit, 1, &reference);
 +      bases = paint_down_to_common(commit, nr_reference, reference);
        if (commit->object.flags & PARENT2)
                ret = 1;
        clear_commit_marks(commit, all_flags);
 -      clear_commit_marks(reference, all_flags);
 +      clear_commit_marks_many(nr_reference, reference, all_flags);
        free_commit_list(bases);
        return ret;
  }
  
 +/*
 + * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
 + */
 +int in_merge_bases(struct commit *commit, struct commit *reference)
 +{
 +      return in_merge_bases_many(commit, 1, &reference);
 +}
 +
  struct commit_list *reduce_heads(struct commit_list *heads)
  {
        struct commit_list *p;
@@@ -1060,76 -1133,6 +1151,76 @@@ free_return
        free(buf);
  }
  
 +static struct {
 +      char result;
 +      const char *check;
 +} sigcheck_gpg_status[] = {
 +      { 'G', "\n[GNUPG:] GOODSIG " },
 +      { 'B', "\n[GNUPG:] BADSIG " },
 +      { 'U', "\n[GNUPG:] TRUST_NEVER" },
 +      { 'U', "\n[GNUPG:] TRUST_UNDEFINED" },
 +};
 +
 +static void parse_gpg_output(struct signature_check *sigc)
 +{
 +      const char *buf = sigc->gpg_status;
 +      int i;
 +
 +      /* Iterate over all search strings */
 +      for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
 +              const char *found, *next;
 +
 +              if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) {
 +                      /* At the very beginning of the buffer */
 +                      found = buf + strlen(sigcheck_gpg_status[i].check + 1);
 +              } else {
 +                      found = strstr(buf, sigcheck_gpg_status[i].check);
 +                      if (!found)
 +                              continue;
 +                      found += strlen(sigcheck_gpg_status[i].check);
 +              }
 +              sigc->result = sigcheck_gpg_status[i].result;
 +              /* The trust messages are not followed by key/signer information */
 +              if (sigc->result != 'U') {
 +                      sigc->key = xmemdupz(found, 16);
 +                      found += 17;
 +                      next = strchrnul(found, '\n');
 +                      sigc->signer = xmemdupz(found, next - found);
 +              }
 +      }
 +}
 +
 +void check_commit_signature(const struct commit* commit, struct signature_check *sigc)
 +{
 +      struct strbuf payload = STRBUF_INIT;
 +      struct strbuf signature = STRBUF_INIT;
 +      struct strbuf gpg_output = STRBUF_INIT;
 +      struct strbuf gpg_status = STRBUF_INIT;
 +      int status;
 +
 +      sigc->result = 'N';
 +
 +      if (parse_signed_commit(commit->object.sha1,
 +                              &payload, &signature) <= 0)
 +              goto out;
 +      status = verify_signed_buffer(payload.buf, payload.len,
 +                                    signature.buf, signature.len,
 +                                    &gpg_output, &gpg_status);
 +      if (status && !gpg_output.len)
 +              goto out;
 +      sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
 +      sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
 +      parse_gpg_output(sigc);
 +
 + out:
 +      strbuf_release(&gpg_status);
 +      strbuf_release(&gpg_output);
 +      strbuf_release(&payload);
 +      strbuf_release(&signature);
 +}
 +
 +
 +
  void append_merge_tag_headers(struct commit_list *parents,
                              struct commit_extra_header ***tail)
  {
diff --combined commit.h
index 350472114b6ddd3bad40a91c7acfb0629ece14a7,e43dfd01823481a23b542e0a4ab1a5df8b493e70..4d452dc96db3d5590878ab6f257022fe76b4130b
+++ b/commit.h
@@@ -5,7 -5,6 +5,7 @@@
  #include "tree.h"
  #include "strbuf.h"
  #include "decorate.h"
 +#include "gpg-interface.h"
  
  struct commit_list {
        struct commit *item;
@@@ -101,7 -100,6 +101,7 @@@ struct userformat_want 
  extern int has_non_ascii(const char *text);
  struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
  extern char *logmsg_reencode(const struct commit *commit,
 +                           char **commit_encoding,
                             const char *output_encoding);
  extern void logmsg_free(char *msg, const struct commit *commit);
  extern void get_commit_format(const char *arg, struct rev_info *);
@@@ -139,18 -137,26 +139,27 @@@ struct commit *pop_most_recent_commit(s
  struct commit *pop_commit(struct commit_list **stack);
  
  void clear_commit_marks(struct commit *commit, unsigned int mark);
 +void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
  void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
  
+ enum rev_sort_order {
+       REV_SORT_IN_GRAPH_ORDER = 0,
+       REV_SORT_BY_COMMIT_DATE,
+       REV_SORT_BY_AUTHOR_DATE
+ };
  /*
   * Performs an in-place topological sort of list supplied.
   *
   *   invariant of resulting list is:
   *      a reachable from b => ord(b) < ord(a)
-  *   in addition, when lifo == 0, commits on parallel tracks are
-  *   sorted in the dates order.
+  *   sort_order further specifies:
+  *   REV_SORT_IN_GRAPH_ORDER: try to show a commit on a single-parent
+  *                            chain together.
+  *   REV_SORT_BY_COMMIT_DATE: show eligible commits in committer-date order.
   */
- void sort_in_topological_order(struct commit_list ** list, int lifo);
+ void sort_in_topological_order(struct commit_list **, enum rev_sort_order);
  
  struct commit_graft {
        unsigned char sha1[20];
@@@ -176,12 -182,9 +185,12 @@@ extern int for_each_commit_graft(each_c
  extern int is_repository_shallow(void);
  extern struct commit_list *get_shallow_commits(struct object_array *heads,
                int depth, int shallow_flag, int not_shallow_flag);
 +extern void check_shallow_file_for_update(void);
 +extern void set_alternate_shallow_file(const char *path);
  
  int is_descendant_of(struct commit *, struct commit_list *);
  int in_merge_bases(struct commit *, struct commit *);
 +int in_merge_bases_many(struct commit *, int, struct commit **);
  
  extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
  extern int run_add_interactive(const char *revision, const char *patch_mode,
@@@ -236,13 -239,4 +245,13 @@@ extern void print_commit_list(struct co
                              const char *format_cur,
                              const char *format_last);
  
 +/*
 + * Check the signature of the given commit. The result of the check is stored
 + * in sig->check_result, 'G' for a good signature, 'U' for a good signature
 + * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
 + * at all.  This may allocate memory for sig->gpg_output, sig->gpg_status,
 + * sig->signer and sig->key.
 + */
 +extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc);
 +
  #endif /* COMMIT_H */
diff --combined revision.c
index f1bb731fd71eb874344faa5376e7c375ac2fcf7f,12d9b64b3aeb86818352c13e35588e6c673a1ff2..2f0142f22e21d7cfb1c74ca7035ee962e16dbd9e
@@@ -13,7 -13,6 +13,7 @@@
  #include "decorate.h"
  #include "log-tree.h"
  #include "string-list.h"
 +#include "line-log.h"
  #include "mailmap.h"
  
  volatile show_early_output_fn_t show_early_output;
@@@ -71,8 -70,7 +71,8 @@@ static int show_path_truncated(FILE *ou
        return ours || emitted;
  }
  
 -void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
 +void show_object_with_name(FILE *out, struct object *obj,
 +                         const struct name_path *path, const char *component)
  {
        struct name_path leaf;
        leaf.up = (struct name_path *)path;
@@@ -89,9 -87,7 +89,9 @@@ void add_object(struct object *obj
                struct name_path *path,
                const char *name)
  {
 -      add_object_array(obj, path_name(path, name), p);
 +      char *pn = path_name(path, name);
 +      add_object_array(obj, pn, p);
 +      free(pn);
  }
  
  static void mark_blob_uninteresting(struct blob *blob)
@@@ -190,9 -186,7 +190,9 @@@ void mark_parents_uninteresting(struct 
        }
  }
  
 -static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
 +static void add_pending_object_with_mode(struct rev_info *revs,
 +                                       struct object *obj,
 +                                       const char *name, unsigned mode)
  {
        if (!obj)
                return;
        add_object_array_with_mode(obj, name, &revs->pending, mode);
  }
  
 -void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
 +void add_pending_object(struct rev_info *revs,
 +                      struct object *obj, const char *name)
  {
        add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
  }
@@@ -233,9 -226,7 +233,9 @@@ void add_head_to_pending(struct rev_inf
        add_pending_object(revs, obj, "HEAD");
  }
  
 -static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
 +static struct object *get_reference(struct rev_info *revs, const char *name,
 +                                  const unsigned char *sha1,
 +                                  unsigned int flags)
  {
        struct object *object;
  
@@@ -256,8 -247,7 +256,8 @@@ void add_pending_sha1(struct rev_info *
        add_pending_object(revs, object, name);
  }
  
 -static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
 +static struct commit *handle_commit(struct rev_info *revs,
 +                                  struct object *object, const char *name)
  {
        unsigned long flags = object->flags;
  
@@@ -342,80 -332,6 +342,80 @@@ static int everybody_uninteresting(stru
        return 1;
  }
  
 +/*
 + * A definition of "relevant" commit that we can use to simplify limited graphs
 + * by eliminating side branches.
 + *
 + * A "relevant" commit is one that is !UNINTERESTING (ie we are including it
 + * in our list), or that is a specified BOTTOM commit. Then after computing
 + * a limited list, during processing we can generally ignore boundary merges
 + * coming from outside the graph, (ie from irrelevant parents), and treat
 + * those merges as if they were single-parent. TREESAME is defined to consider
 + * only relevant parents, if any. If we are TREESAME to our on-graph parents,
 + * we don't care if we were !TREESAME to non-graph parents.
 + *
 + * Treating bottom commits as relevant ensures that a limited graph's
 + * connection to the actual bottom commit is not viewed as a side branch, but
 + * treated as part of the graph. For example:
 + *
 + *   ....Z...A---X---o---o---B
 + *        .     /
 + *         W---Y
 + *
 + * When computing "A..B", the A-X connection is at least as important as
 + * Y-X, despite A being flagged UNINTERESTING.
 + *
 + * And when computing --ancestry-path "A..B", the A-X connection is more
 + * important than Y-X, despite both A and Y being flagged UNINTERESTING.
 + */
 +static inline int relevant_commit(struct commit *commit)
 +{
 +      return (commit->object.flags & (UNINTERESTING | BOTTOM)) != UNINTERESTING;
 +}
 +
 +/*
 + * Return a single relevant commit from a parent list. If we are a TREESAME
 + * commit, and this selects one of our parents, then we can safely simplify to
 + * that parent.
 + */
 +static struct commit *one_relevant_parent(const struct rev_info *revs,
 +                                        struct commit_list *orig)
 +{
 +      struct commit_list *list = orig;
 +      struct commit *relevant = NULL;
 +
 +      if (!orig)
 +              return NULL;
 +
 +      /*
 +       * For 1-parent commits, or if first-parent-only, then return that
 +       * first parent (even if not "relevant" by the above definition).
 +       * TREESAME will have been set purely on that parent.
 +       */
 +      if (revs->first_parent_only || !orig->next)
 +              return orig->item;
 +
 +      /*
 +       * For multi-parent commits, identify a sole relevant parent, if any.
 +       * If we have only one relevant parent, then TREESAME will be set purely
 +       * with regard to that parent, and we can simplify accordingly.
 +       *
 +       * If we have more than one relevant parent, or no relevant parents
 +       * (and multiple irrelevant ones), then we can't select a parent here
 +       * and return NULL.
 +       */
 +      while (list) {
 +              struct commit *commit = list->item;
 +              list = list->next;
 +              if (relevant_commit(commit)) {
 +                      if (relevant)
 +                              return NULL;
 +                      relevant = commit;
 +              }
 +      }
 +      return relevant;
 +}
 +
  /*
   * The goal is to get REV_TREE_NEW as the result only if the
   * diff consists of all '+' (and no other changes), REV_TREE_OLD
@@@ -452,8 -368,7 +452,8 @@@ static void file_change(struct diff_opt
        DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
 -static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct commit *commit)
 +static int rev_compare_tree(struct rev_info *revs,
 +                          struct commit *parent, struct commit *commit)
  {
        struct tree *t1 = parent->tree;
        struct tree *t2 = commit->tree;
@@@ -514,125 -429,10 +514,125 @@@ static int rev_same_tree_as_empty(struc
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
  }
  
 +struct treesame_state {
 +      unsigned int nparents;
 +      unsigned char treesame[FLEX_ARRAY];
 +};
 +
 +static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)
 +{
 +      unsigned n = commit_list_count(commit->parents);
 +      struct treesame_state *st = xcalloc(1, sizeof(*st) + n);
 +      st->nparents = n;
 +      add_decoration(&revs->treesame, &commit->object, st);
 +      return st;
 +}
 +
 +/*
 + * Must be called immediately after removing the nth_parent from a commit's
 + * parent list, if we are maintaining the per-parent treesame[] decoration.
 + * This does not recalculate the master TREESAME flag - update_treesame()
 + * should be called to update it after a sequence of treesame[] modifications
 + * that may have affected it.
 + */
 +static int compact_treesame(struct rev_info *revs, struct commit *commit, unsigned nth_parent)
 +{
 +      struct treesame_state *st;
 +      int old_same;
 +
 +      if (!commit->parents) {
 +              /*
 +               * Have just removed the only parent from a non-merge.
 +               * Different handling, as we lack decoration.
 +               */
 +              if (nth_parent != 0)
 +                      die("compact_treesame %u", nth_parent);
 +              old_same = !!(commit->object.flags & TREESAME);
 +              if (rev_same_tree_as_empty(revs, commit))
 +                      commit->object.flags |= TREESAME;
 +              else
 +                      commit->object.flags &= ~TREESAME;
 +              return old_same;
 +      }
 +
 +      st = lookup_decoration(&revs->treesame, &commit->object);
 +      if (!st || nth_parent >= st->nparents)
 +              die("compact_treesame %u", nth_parent);
 +
 +      old_same = st->treesame[nth_parent];
 +      memmove(st->treesame + nth_parent,
 +              st->treesame + nth_parent + 1,
 +              st->nparents - nth_parent - 1);
 +
 +      /*
 +       * If we've just become a non-merge commit, update TREESAME
 +       * immediately, and remove the no-longer-needed decoration.
 +       * If still a merge, defer update until update_treesame().
 +       */
 +      if (--st->nparents == 1) {
 +              if (commit->parents->next)
 +                      die("compact_treesame parents mismatch");
 +              if (st->treesame[0] && revs->dense)
 +                      commit->object.flags |= TREESAME;
 +              else
 +                      commit->object.flags &= ~TREESAME;
 +              free(add_decoration(&revs->treesame, &commit->object, NULL));
 +      }
 +
 +      return old_same;
 +}
 +
 +static unsigned update_treesame(struct rev_info *revs, struct commit *commit)
 +{
 +      if (commit->parents && commit->parents->next) {
 +              unsigned n;
 +              struct treesame_state *st;
 +              struct commit_list *p;
 +              unsigned relevant_parents;
 +              unsigned relevant_change, irrelevant_change;
 +
 +              st = lookup_decoration(&revs->treesame, &commit->object);
 +              if (!st)
 +                      die("update_treesame %s", sha1_to_hex(commit->object.sha1));
 +              relevant_parents = 0;
 +              relevant_change = irrelevant_change = 0;
 +              for (p = commit->parents, n = 0; p; n++, p = p->next) {
 +                      if (relevant_commit(p->item)) {
 +                              relevant_change |= !st->treesame[n];
 +                              relevant_parents++;
 +                      } else
 +                              irrelevant_change |= !st->treesame[n];
 +              }
 +              if (relevant_parents ? relevant_change : irrelevant_change)
 +                      commit->object.flags &= ~TREESAME;
 +              else
 +                      commit->object.flags |= TREESAME;
 +      }
 +
 +      return commit->object.flags & TREESAME;
 +}
 +
 +static inline int limiting_can_increase_treesame(const struct rev_info *revs)
 +{
 +      /*
 +       * TREESAME is irrelevant unless prune && dense;
 +       * if simplify_history is set, we can't have a mixture of TREESAME and
 +       *    !TREESAME INTERESTING parents (and we don't have treesame[]
 +       *    decoration anyway);
 +       * if first_parent_only is set, then the TREESAME flag is locked
 +       *    against the first parent (and again we lack treesame[] decoration).
 +       */
 +      return revs->prune && revs->dense &&
 +             !revs->simplify_history &&
 +             !revs->first_parent_only;
 +}
 +
  static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
  {
        struct commit_list **pp, *parent;
 -      int tree_changed = 0, tree_same = 0, nth_parent = 0;
 +      struct treesame_state *ts = NULL;
 +      int relevant_change = 0, irrelevant_change = 0;
 +      int relevant_parents, nth_parent;
  
        /*
         * If we don't do pruning, everything is interesting
        if (!revs->dense && !commit->parents->next)
                return;
  
 -      pp = &commit->parents;
 -      while ((parent = *pp) != NULL) {
 +      for (pp = &commit->parents, nth_parent = 0, relevant_parents = 0;
 +           (parent = *pp) != NULL;
 +           pp = &parent->next, nth_parent++) {
                struct commit *p = parent->item;
 +              if (relevant_commit(p))
 +                      relevant_parents++;
  
 -              /*
 -               * Do not compare with later parents when we care only about
 -               * the first parent chain, in order to avoid derailing the
 -               * traversal to follow a side branch that brought everything
 -               * in the path we are limited to by the pathspec.
 -               */
 -              if (revs->first_parent_only && nth_parent++)
 -                      break;
 +              if (nth_parent == 1) {
 +                      /*
 +                       * This our second loop iteration - so we now know
 +                       * we're dealing with a merge.
 +                       *
 +                       * Do not compare with later parents when we care only about
 +                       * the first parent chain, in order to avoid derailing the
 +                       * traversal to follow a side branch that brought everything
 +                       * in the path we are limited to by the pathspec.
 +                       */
 +                      if (revs->first_parent_only)
 +                              break;
 +                      /*
 +                       * If this will remain a potentially-simplifiable
 +                       * merge, remember per-parent treesame if needed.
 +                       * Initialise the array with the comparison from our
 +                       * first iteration.
 +                       */
 +                      if (revs->treesame.name &&
 +                          !revs->simplify_history &&
 +                          !(commit->object.flags & UNINTERESTING)) {
 +                              ts = initialise_treesame(revs, commit);
 +                              if (!(irrelevant_change || relevant_change))
 +                                      ts->treesame[0] = 1;
 +                      }
 +              }
                if (parse_commit(p) < 0)
                        die("cannot simplify commit %s (because of %s)",
                            sha1_to_hex(commit->object.sha1),
                            sha1_to_hex(p->object.sha1));
                switch (rev_compare_tree(revs, p, commit)) {
                case REV_TREE_SAME:
 -                      tree_same = 1;
 -                      if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
 +                      if (!revs->simplify_history || !relevant_commit(p)) {
                                /* Even if a merge with an uninteresting
                                 * side branch brought the entire change
                                 * we are interested in, we do not want
                                 * to lose the other branches of this
                                 * merge, so we just keep going.
                                 */
 -                              pp = &parent->next;
 +                              if (ts)
 +                                      ts->treesame[nth_parent] = 1;
                                continue;
                        }
                        parent->next = NULL;
                /* fallthrough */
                case REV_TREE_OLD:
                case REV_TREE_DIFFERENT:
 -                      tree_changed = 1;
 -                      pp = &parent->next;
 +                      if (relevant_commit(p))
 +                              relevant_change = 1;
 +                      else
 +                              irrelevant_change = 1;
                        continue;
                }
                die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
 -      if (tree_changed && !tree_same)
 -              return;
 -      commit->object.flags |= TREESAME;
 +
 +      /*
 +       * TREESAME is straightforward for single-parent commits. For merge
 +       * commits, it is most useful to define it so that "irrelevant"
 +       * parents cannot make us !TREESAME - if we have any relevant
 +       * parents, then we only consider TREESAMEness with respect to them,
 +       * allowing irrelevant merges from uninteresting branches to be
 +       * simplified away. Only if we have only irrelevant parents do we
 +       * base TREESAME on them. Note that this logic is replicated in
 +       * update_treesame, which should be kept in sync.
 +       */
 +      if (relevant_parents ? !relevant_change : !irrelevant_change)
 +              commit->object.flags |= TREESAME;
  }
  
  static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
@@@ -1034,12 -801,16 +1034,12 @@@ static void limit_to_ancestry(struct co
   * to filter the result of "A..B" further to the ones that can actually
   * reach A.
   */
 -static struct commit_list *collect_bottom_commits(struct rev_info *revs)
 +static struct commit_list *collect_bottom_commits(struct commit_list *list)
  {
 -      struct commit_list *bottom = NULL;
 -      int i;
 -      for (i = 0; i < revs->cmdline.nr; i++) {
 -              struct rev_cmdline_entry *elem = &revs->cmdline.rev[i];
 -              if ((elem->flags & UNINTERESTING) &&
 -                  elem->item->type == OBJ_COMMIT)
 -                      commit_list_insert((struct commit *)elem->item, &bottom);
 -      }
 +      struct commit_list *elem, *bottom = NULL;
 +      for (elem = list; elem; elem = elem->next)
 +              if (elem->item->object.flags & BOTTOM)
 +                      commit_list_insert(elem->item, &bottom);
        return bottom;
  }
  
@@@ -1070,7 -841,7 +1070,7 @@@ static int limit_list(struct rev_info *
        struct commit_list *bottom = NULL;
  
        if (revs->ancestry_path) {
 -              bottom = collect_bottom_commits(revs);
 +              bottom = collect_bottom_commits(list);
                if (!bottom)
                        die("--ancestry-path given but there are no bottom commits");
        }
                free_commit_list(bottom);
        }
  
 +      /*
 +       * Check if any commits have become TREESAME by some of their parents
 +       * becoming UNINTERESTING.
 +       */
 +      if (limiting_can_increase_treesame(revs))
 +              for (list = newlist; list; list = list->next) {
 +                      struct commit *c = list->item;
 +                      if (c->object.flags & (UNINTERESTING | TREESAME))
 +                              continue;
 +                      update_treesame(revs, c);
 +              }
 +
        revs->commits = newlist;
        return 0;
  }
  
 +/*
 + * Add an entry to refs->cmdline with the specified information.
 + * *name is copied.
 + */
  static void add_rev_cmdline(struct rev_info *revs,
                            struct object *item,
                            const char *name,
  
        ALLOC_GROW(info->rev, nr + 1, info->alloc);
        info->rev[nr].item = item;
 -      info->rev[nr].name = name;
 +      info->rev[nr].name = xstrdup(name);
        info->rev[nr].whence = whence;
        info->rev[nr].flags = flags;
        info->nr++;
  }
  
 +static void add_rev_cmdline_list(struct rev_info *revs,
 +                               struct commit_list *commit_list,
 +                               int whence,
 +                               unsigned flags)
 +{
 +      while (commit_list) {
 +              struct object *object = &commit_list->item->object;
 +              add_rev_cmdline(revs, object, sha1_to_hex(object->sha1),
 +                              whence, flags);
 +              commit_list = commit_list->next;
 +      }
 +}
 +
  struct all_refs_cb {
        int all_flags;
        int warned_bad_reflog;
@@@ -1258,7 -1000,7 +1258,7 @@@ static int add_parents_only(struct rev_
        const char *arg = arg_;
  
        if (*arg == '^') {
 -              flags ^= UNINTERESTING;
 +              flags ^= UNINTERESTING | BOTTOM;
                arg++;
        }
        if (get_sha1_committish(arg, sha1))
@@@ -1296,7 -1038,7 +1296,7 @@@ void init_revisions(struct rev_info *re
        DIFF_OPT_SET(&revs->pruning, QUICK);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
-       revs->lifo = 1;
+       revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
        revs->dense = 1;
        revs->prefix = prefix;
        revs->max_age = -1;
@@@ -1350,8 -1092,7 +1350,8 @@@ static void prepare_show_merge(struct r
        add_pending_object(revs, &head->object, "HEAD");
        add_pending_object(revs, &other->object, "MERGE_HEAD");
        bases = get_merge_bases(head, other, 1);
 -      add_pending_commit_list(revs, bases, UNINTERESTING);
 +      add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);
 +      add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);
        free_commit_list(bases);
        head->object.flags |= SYMMETRIC_LEFT;
  
@@@ -1387,15 -1128,13 +1387,15 @@@ int handle_revision_arg(const char *arg
        int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
        unsigned get_sha1_flags = 0;
  
 +      flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
 +
        dotdot = strstr(arg, "..");
        if (dotdot) {
                unsigned char from_sha1[20];
                const char *next = dotdot + 2;
                const char *this = arg;
                int symmetric = *next == '.';
 -              unsigned int flags_exclude = flags ^ UNINTERESTING;
 +              unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
                static const char head_by_default[] = "HEAD";
                unsigned int a_flags;
  
  
                        if (symmetric) {
                                exclude = get_merge_bases(a, b, 1);
 +                              add_rev_cmdline_list(revs, exclude,
 +                                                   REV_CMD_MERGE_BASE,
 +                                                   flags_exclude);
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
        dotdot = strstr(arg, "^!");
        if (dotdot && !dotdot[2]) {
                *dotdot = 0;
 -              if (!add_parents_only(revs, arg, flags ^ UNINTERESTING))
 +              if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))
                        *dotdot = '^';
        }
  
        local_flags = 0;
        if (*arg == '^') {
 -              local_flags = UNINTERESTING;
 +              local_flags = UNINTERESTING | BOTTOM;
                arg++;
        }
  
@@@ -1540,8 -1276,7 +1540,8 @@@ static void read_revisions_from_stdin(s
                        }
                        die("options not supported in --stdin mode");
                }
 -              if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))
 +              if (handle_revision_arg(sb.buf, revs, 0,
 +                                      REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
        if (seen_dashdash)
@@@ -1638,7 -1373,7 +1638,7 @@@ static int handle_revision_opt(struct r
        } else if (!strcmp(arg, "--merge")) {
                revs->show_merge = 1;
        } else if (!strcmp(arg, "--topo-order")) {
-               revs->lifo = 1;
+               revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
                revs->topo_order = 1;
        } else if (!strcmp(arg, "--simplify-merges")) {
                revs->simplify_merges = 1;
                revs->prune = 1;
                load_ref_decorations(DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
-               revs->lifo = 0;
+               revs->sort_order = REV_SORT_BY_COMMIT_DATE;
+               revs->topo_order = 1;
+       } else if (!strcmp(arg, "--author-date-order")) {
+               revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
                revs->topo_order = 1;
        } else if (!prefixcmp(arg, "--early-output")) {
                int count = 100;
@@@ -1954,7 -1692,7 +1957,7 @@@ static int handle_revision_pseudo_opt(c
                handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
        } else if (!strcmp(arg, "--bisect")) {
                handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
 -              handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);
 +              handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
                revs->bisect = 1;
        } else if (!strcmp(arg, "--tags")) {
                handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
        } else if (!strcmp(arg, "--reflog")) {
                handle_reflog(revs, *flags);
        } else if (!strcmp(arg, "--not")) {
 -              *flags ^= UNINTERESTING;
 +              *flags ^= UNINTERESTING | BOTTOM;
        } else if (!strcmp(arg, "--no-walk")) {
                revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
        } else if (!prefixcmp(arg, "--no-walk=")) {
@@@ -2161,12 -1899,6 +2164,12 @@@ int setup_revisions(int argc, const cha
        if (revs->combine_merges)
                revs->ignore_merges = 0;
        revs->diffopt.abbrev = revs->abbrev;
 +
 +      if (revs->line_level_traverse) {
 +              revs->limited = 1;
 +              revs->topo_order = 1;
 +      }
 +
        diff_setup_done(&revs->diffopt);
  
        grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
@@@ -2200,32 -1932,28 +2203,32 @@@ static void add_child(struct rev_info *
        l->next = add_decoration(&revs->children, &parent->object, l);
  }
  
 -static int remove_duplicate_parents(struct commit *commit)
 +static int remove_duplicate_parents(struct rev_info *revs, struct commit *commit)
  {
 +      struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
        struct commit_list **pp, *p;
        int surviving_parents;
  
        /* Examine existing parents while marking ones we have seen... */
        pp = &commit->parents;
 +      surviving_parents = 0;
        while ((p = *pp) != NULL) {
                struct commit *parent = p->item;
                if (parent->object.flags & TMP_MARK) {
                        *pp = p->next;
 +                      if (ts)
 +                              compact_treesame(revs, commit, surviving_parents);
                        continue;
                }
                parent->object.flags |= TMP_MARK;
 +              surviving_parents++;
                pp = &p->next;
        }
 -      /* count them while clearing the temporary mark */
 -      surviving_parents = 0;
 +      /* clear the temporary mark */
        for (p = commit->parents; p; p = p->next) {
                p->item->object.flags &= ~TMP_MARK;
 -              surviving_parents++;
        }
 +      /* no update_treesame() - removing duplicates can't affect TREESAME */
        return surviving_parents;
  }
  
@@@ -2245,157 -1973,9 +2248,157 @@@ static struct merge_simplify_state *loc
        return st;
  }
  
 +static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list *h = reduce_heads(commit->parents);
 +      int i = 0, marked = 0;
 +      struct commit_list *po, *pn;
 +
 +      /* Want these for sanity-checking only */
 +      int orig_cnt = commit_list_count(commit->parents);
 +      int cnt = commit_list_count(h);
 +
 +      /*
 +       * Not ready to remove items yet, just mark them for now, based
 +       * on the output of reduce_heads(). reduce_heads outputs the reduced
 +       * set in its original order, so this isn't too hard.
 +       */
 +      po = commit->parents;
 +      pn = h;
 +      while (po) {
 +              if (pn && po->item == pn->item) {
 +                      pn = pn->next;
 +                      i++;
 +              } else {
 +                      po->item->object.flags |= TMP_MARK;
 +                      marked++;
 +              }
 +              po=po->next;
 +      }
 +
 +      if (i != cnt || cnt+marked != orig_cnt)
 +              die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked);
 +
 +      free_commit_list(h);
 +
 +      return marked;
 +}
 +
 +static int mark_treesame_root_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list *p;
 +      int marked = 0;
 +
 +      for (p = commit->parents; p; p = p->next) {
 +              struct commit *parent = p->item;
 +              if (!parent->parents && (parent->object.flags & TREESAME)) {
 +                      parent->object.flags |= TMP_MARK;
 +                      marked++;
 +              }
 +      }
 +
 +      return marked;
 +}
 +
 +/*
 + * Awkward naming - this means one parent we are TREESAME to.
 + * cf mark_treesame_root_parents: root parents that are TREESAME (to an
 + * empty tree). Better name suggestions?
 + */
 +static int leave_one_treesame_to_parent(struct rev_info *revs, struct commit *commit)
 +{
 +      struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
 +      struct commit *unmarked = NULL, *marked = NULL;
 +      struct commit_list *p;
 +      unsigned n;
 +
 +      for (p = commit->parents, n = 0; p; p = p->next, n++) {
 +              if (ts->treesame[n]) {
 +                      if (p->item->object.flags & TMP_MARK) {
 +                              if (!marked)
 +                                      marked = p->item;
 +                      } else {
 +                              if (!unmarked) {
 +                                      unmarked = p->item;
 +                                      break;
 +                              }
 +                      }
 +              }
 +      }
 +
 +      /*
 +       * If we are TREESAME to a marked-for-deletion parent, but not to any
 +       * unmarked parents, unmark the first TREESAME parent. This is the
 +       * parent that the default simplify_history==1 scan would have followed,
 +       * and it doesn't make sense to omit that path when asking for a
 +       * simplified full history. Retaining it improves the chances of
 +       * understanding odd missed merges that took an old version of a file.
 +       *
 +       * Example:
 +       *
 +       *   I--------*X       A modified the file, but mainline merge X used
 +       *    \       /        "-s ours", so took the version from I. X is
 +       *     `-*A--'         TREESAME to I and !TREESAME to A.
 +       *
 +       * Default log from X would produce "I". Without this check,
 +       * --full-history --simplify-merges would produce "I-A-X", showing
 +       * the merge commit X and that it changed A, but not making clear that
 +       * it had just taken the I version. With this check, the topology above
 +       * is retained.
 +       *
 +       * Note that it is possible that the simplification chooses a different
 +       * TREESAME parent from the default, in which case this test doesn't
 +       * activate, and we _do_ drop the default parent. Example:
 +       *
 +       *   I------X         A modified the file, but it was reverted in B,
 +       *    \    /          meaning mainline merge X is TREESAME to both
 +       *    *A-*B           parents.
 +       *
 +       * Default log would produce "I" by following the first parent;
 +       * --full-history --simplify-merges will produce "I-A-B". But this is a
 +       * reasonable result - it presents a logical full history leading from
 +       * I to X, and X is not an important merge.
 +       */
 +      if (!unmarked && marked) {
 +              marked->object.flags &= ~TMP_MARK;
 +              return 1;
 +      }
 +
 +      return 0;
 +}
 +
 +static int remove_marked_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list **pp, *p;
 +      int nth_parent, removed = 0;
 +
 +      pp = &commit->parents;
 +      nth_parent = 0;
 +      while ((p = *pp) != NULL) {
 +              struct commit *parent = p->item;
 +              if (parent->object.flags & TMP_MARK) {
 +                      parent->object.flags &= ~TMP_MARK;
 +                      *pp = p->next;
 +                      free(p);
 +                      removed++;
 +                      compact_treesame(revs, commit, nth_parent);
 +                      continue;
 +              }
 +              pp = &p->next;
 +              nth_parent++;
 +      }
 +
 +      /* Removing parents can only increase TREESAMEness */
 +      if (removed && !(commit->object.flags & TREESAME))
 +              update_treesame(revs, commit);
 +
 +      return nth_parent;
 +}
 +
  static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
  {
        struct commit_list *p;
 +      struct commit *parent;
        struct merge_simplify_state *st, *pst;
        int cnt;
  
        }
  
        /*
 -       * Rewrite our list of parents.
 +       * Rewrite our list of parents. Note that this cannot
 +       * affect our TREESAME flags in any way - a commit is
 +       * always TREESAME to its simplification.
         */
        for (p = commit->parents; p; p = p->next) {
                pst = locate_simplify_state(revs, p->item);
                if (revs->first_parent_only)
                        break;
        }
 -      if (!revs->first_parent_only)
 -              cnt = remove_duplicate_parents(commit);
 -      else
 +
 +      if (revs->first_parent_only)
                cnt = 1;
 +      else
 +              cnt = remove_duplicate_parents(revs, commit);
  
        /*
         * It is possible that we are a merge and one side branch
         * does not have any commit that touches the given paths;
 -       * in such a case, the immediate parents will be rewritten
 -       * to different commits.
 +       * in such a case, the immediate parent from that branch
 +       * will be rewritten to be the merge base.
         *
         *      o----X          X: the commit we are looking at;
         *     /    /           o: a commit that touches the paths;
         * ---o----'
         *
 -       * Further reduce the parents by removing redundant parents.
 +       * Further, a merge of an independent branch that doesn't
 +       * touch the path will reduce to a treesame root parent:
 +       *
 +       *  ----o----X          X: the commit we are looking at;
 +       *          /           o: a commit that touches the paths;
 +       *         r            r: a root commit not touching the paths
 +       *
 +       * Detect and simplify both cases.
         */
        if (1 < cnt) {
 -              struct commit_list *h = reduce_heads(commit->parents);
 -              cnt = commit_list_count(h);
 -              free_commit_list(commit->parents);
 -              commit->parents = h;
 +              int marked = mark_redundant_parents(revs, commit);
 +              marked += mark_treesame_root_parents(revs, commit);
 +              if (marked)
 +                      marked -= leave_one_treesame_to_parent(revs, commit);
 +              if (marked)
 +                      cnt = remove_marked_parents(revs, commit);
        }
  
        /*
         * A commit simplifies to itself if it is a root, if it is
         * UNINTERESTING, if it touches the given paths, or if it is a
 -       * merge and its parents simplifies to more than one commits
 +       * merge and its parents don't simplify to one relevant commit
         * (the first two cases are already handled at the beginning of
         * this function).
         *
 -       * Otherwise, it simplifies to what its sole parent simplifies to.
 +       * Otherwise, it simplifies to what its sole relevant parent
 +       * simplifies to.
         */
        if (!cnt ||
            (commit->object.flags & UNINTERESTING) ||
            !(commit->object.flags & TREESAME) ||
 -          (1 < cnt))
 +          (parent = one_relevant_parent(revs, commit->parents)) == NULL)
                st->simplified = commit;
        else {
 -              pst = locate_simplify_state(revs, commit->parents->item);
 +              pst = locate_simplify_state(revs, parent);
                st->simplified = pst->simplified;
        }
        return tail;
@@@ -2593,11 -2160,6 +2596,11 @@@ int prepare_revision_walk(struct rev_in
        if (!revs->leak_pending)
                free(list);
  
 +      /* Signal whether we need per-parent treesame decoration */
 +      if (revs->simplify_merges ||
 +          (revs->limited && limiting_can_increase_treesame(revs)))
 +              revs->treesame.name = "treesame";
 +
        if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
                commit_list_sort_by_date(&revs->commits);
        if (revs->no_walk)
                if (limit_list(revs) < 0)
                        return -1;
        if (revs->topo_order)
-               sort_in_topological_order(&revs->commits, revs->lifo);
+               sort_in_topological_order(&revs->commits, revs->sort_order);
 +      if (revs->line_level_traverse)
 +              line_log_filter(revs);
        if (revs->simplify_merges)
                simplify_merges(revs);
        if (revs->children.name)
        return 0;
  }
  
 -enum rewrite_result {
 -      rewrite_one_ok,
 -      rewrite_one_noparents,
 -      rewrite_one_error
 -};
 -
  static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
  {
        struct commit_list *cache = NULL;
                if (!revs->limited)
                        if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
                                return rewrite_one_error;
 -              if (p->parents && p->parents->next)
 -                      return rewrite_one_ok;
                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;
 -              *pp = p->parents->item;
 +              if ((p = one_relevant_parent(revs, p->parents)) == NULL)
 +                      return rewrite_one_ok;
 +              *pp = p;
        }
  }
  
 -static int rewrite_parents(struct rev_info *revs, struct commit *commit)
 +int rewrite_parents(struct rev_info *revs, struct commit *commit,
 +      rewrite_parent_fn_t rewrite_parent)
  {
        struct commit_list **pp = &commit->parents;
        while (*pp) {
                struct commit_list *parent = *pp;
 -              switch (rewrite_one(revs, &parent->item)) {
 +              switch (rewrite_parent(revs, &parent->item)) {
                case rewrite_one_ok:
                        break;
                case rewrite_one_noparents:
                }
                pp = &parent->next;
        }
 -      remove_duplicate_parents(commit);
 +      remove_duplicate_parents(revs, commit);
        return 0;
  }
  
@@@ -2728,7 -2293,7 +2731,7 @@@ static int commit_match(struct commit *
         * in it.
         */
        encoding = get_log_output_encoding();
 -      message = logmsg_reencode(commit, encoding);
 +      message = logmsg_reencode(commit, NULL, encoding);
  
        /* Copy the commit to temporary if we are using "fake" headers */
        if (buf.len)
@@@ -2778,7 -2343,10 +2781,7 @@@ enum commit_action get_commit_action(st
        if (revs->min_age != -1 && (commit->date > revs->min_age))
                return commit_ignore;
        if (revs->min_parents || (revs->max_parents >= 0)) {
 -              int n = 0;
 -              struct commit_list *p;
 -              for (p = commit->parents; p; p = p->next)
 -                      n++;
 +              int n = commit_list_count(commit->parents);
                if ((n < revs->min_parents) ||
                    ((revs->max_parents >= 0) && (n > revs->max_parents)))
                        return commit_ignore;
        if (revs->prune && revs->dense) {
                /* Commit without changes? */
                if (commit->object.flags & TREESAME) {
 +                      int n;
 +                      struct commit_list *p;
                        /* drop merges unless we want parenthood */
                        if (!want_ancestry(revs))
                                return commit_ignore;
 -                      /* non-merge - always ignore it */
 -                      if (!commit->parents || !commit->parents->next)
 -                              return commit_ignore;
 +                      /*
 +                       * If we want ancestry, then need to keep any merges
 +                       * between relevant commits to tie together topology.
 +                       * For consistency with TREESAME and simplification
 +                       * use "relevant" here rather than just INTERESTING,
 +                       * to treat bottom commit(s) as part of the topology.
 +                       */
 +                      for (n = 0, p = commit->parents; p; p = p->next)
 +                              if (relevant_commit(p->item))
 +                                      if (++n >= 2)
 +                                              return commit_show;
 +                      return commit_ignore;
                }
        }
        return commit_show;
@@@ -2817,7 -2374,7 +2820,7 @@@ enum commit_action simplify_commit(stru
        if (action == commit_show &&
            !revs->show_all &&
            revs->prune && revs->dense && want_ancestry(revs)) {
 -              if (rewrite_parents(revs, commit) < 0)
 +              if (rewrite_parents(revs, commit, rewrite_one) < 0)
                        return commit_error;
        }
        return action;
@@@ -2867,23 -2424,25 +2870,23 @@@ static struct commit *get_revision_1(st
        return NULL;
  }
  
 -static void gc_boundary(struct object_array *array)
 +/*
 + * Return true for entries that have not yet been shown.  (This is an
 + * object_array_each_func_t.)
 + */
 +static int entry_unshown(struct object_array_entry *entry, void *cb_data_unused)
  {
 -      unsigned nr = array->nr;
 -      unsigned alloc = array->alloc;
 -      struct object_array_entry *objects = array->objects;
 +      return !(entry->item->flags & SHOWN);
 +}
  
 -      if (alloc <= nr) {
 -              unsigned i, j;
 -              for (i = j = 0; i < nr; i++) {
 -                      if (objects[i].item->flags & SHOWN)
 -                              continue;
 -                      if (i != j)
 -                              objects[j] = objects[i];
 -                      j++;
 -              }
 -              for (i = j; i < nr; i++)
 -                      objects[i].item = NULL;
 -              array->nr = j;
 -      }
 +/*
 + * If array is on the verge of a realloc, garbage-collect any entries
 + * that have already been shown to try to free up some space.
 + */
 +static void gc_boundary(struct object_array *array)
 +{
 +      if (array->nr == array->alloc)
 +              object_array_filter(array, entry_unshown, NULL);
  }
  
  static void create_boundary_commit_list(struct rev_info *revs)
         * If revs->topo_order is set, sort the boundary commits
         * in topological order
         */
-       sort_in_topological_order(&revs->commits, revs->lifo);
+       sort_in_topological_order(&revs->commits, revs->sort_order);
  }
  
  static struct commit *get_revision_internal(struct rev_info *revs)
diff --combined revision.h
index eeea6fba3c5408d5567eec238d0a331bf64ddc04,2a5e325a36367d250f6978fac79529b7fd35f499..92d6614af6da62160219b0df40d0caf56335ffce
@@@ -4,6 -4,7 +4,7 @@@
  #include "parse-options.h"
  #include "grep.h"
  #include "notes.h"
+ #include "commit.h"
  
  #define SEEN          (1u<<0)
  #define UNINTERESTING   (1u<<1)
@@@ -15,8 -16,7 +16,8 @@@
  #define ADDED         (1u<<7) /* Parents already parsed and added? */
  #define SYMMETRIC_LEFT        (1u<<8)
  #define PATCHSAME     (1u<<9)
 -#define ALL_REV_FLAGS ((1u<<10)-1)
 +#define BOTTOM                (1u<<10)
 +#define ALL_REV_FLAGS ((1u<<11)-1)
  
  #define DECORATE_SHORT_REFS   1
  #define DECORATE_FULL_REFS    2
@@@ -36,7 -36,6 +37,7 @@@ struct rev_cmdline_info 
                        REV_CMD_PARENTS_ONLY,
                        REV_CMD_LEFT,
                        REV_CMD_RIGHT,
 +                      REV_CMD_MERGE_BASE,
                        REV_CMD_REV
                } whence;
                unsigned flags;
@@@ -62,6 -61,10 +63,10 @@@ struct rev_info 
        const char *prefix;
        const char *def;
        struct pathspec prune_data;
+       /* topo-sort */
+       enum rev_sort_order sort_order;
        unsigned int    early_output:1,
                        ignore_missing:1;
  
@@@ -72,7 -75,6 +77,6 @@@
                        show_all:1,
                        remove_empty_trees:1,
                        simplify_history:1,
-                       lifo:1,
                        topo_order:1,
                        simplify_merges:1,
                        simplify_by_decoration:1,
                        cherry_mark:1,
                        bisect:1,
                        ancestry_path:1,
 -                      first_parent_only:1;
 +                      first_parent_only:1,
 +                      line_level_traverse:1;
  
        /* Diff flags */
        unsigned int    diff:1,
        int             reroll_count;
        char            *message_id;
        struct string_list *ref_message_ids;
 -      const char      *add_signoff;
 +      int             add_signoff;
        const char      *extra_headers;
        const char      *log_reencode;
        const char      *subject_prefix;
        struct reflog_walk_info *reflog_info;
        struct decoration children;
        struct decoration merge_simplification;
 +      struct decoration treesame;
  
        /* notes-specific options: which refs to show */
        struct display_notes_opt notes_opt;
        int count_left;
        int count_right;
        int count_same;
 +
 +      /* line level range that we are chasing */
 +      struct decoration line_log_data;
  };
  
  #define REV_TREE_SAME         0
@@@ -202,23 -199,19 +206,23 @@@ struct setup_revision_opt 
  };
  
  extern void init_revisions(struct rev_info *revs, const char *prefix);
 -extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *);
 +extern int setup_revisions(int argc, const char **argv, struct rev_info *revs,
 +                         struct setup_revision_opt *);
  extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
 -                               const struct option *options,
 -                               const char * const usagestr[]);
 +                             const struct option *options,
 +                             const char * const usagestr[]);
  #define REVARG_CANNOT_BE_FILENAME 01
  #define REVARG_COMMITTISH 02
 -extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt);
 +extern int handle_revision_arg(const char *arg, struct rev_info *revs,
 +                             int flags, unsigned revarg_opt);
  
  extern void reset_revision_walk(void);
  extern int prepare_revision_walk(struct rev_info *revs);
  extern struct commit *get_revision(struct rev_info *revs);
 -extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);
 -extern void put_revision_mark(const struct rev_info *revs, const struct commit *commit);
 +extern char *get_revision_mark(const struct rev_info *revs,
 +                             const struct commit *commit);
 +extern void put_revision_mark(const struct rev_info *revs,
 +                            const struct commit *commit);
  
  extern void mark_parents_uninteresting(struct commit *commit);
  extern void mark_tree_uninteresting(struct tree *tree);
@@@ -231,19 -224,15 +235,19 @@@ struct name_path 
  
  char *path_name(const struct name_path *path, const char *name);
  
 -extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *);
 +extern void show_object_with_name(FILE *, struct object *,
 +                                const struct name_path *, const char *);
  
  extern void add_object(struct object *obj,
                       struct object_array *p,
                       struct name_path *path,
                       const char *name);
  
 -extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
 -extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags);
 +extern void add_pending_object(struct rev_info *revs,
 +                             struct object *obj, const char *name);
 +extern void add_pending_sha1(struct rev_info *revs,
 +                           const char *name, const unsigned char *sha1,
 +                           unsigned int flags);
  
  extern void add_head_to_pending(struct rev_info *);
  
@@@ -253,19 -242,7 +257,19 @@@ enum commit_action 
        commit_error
  };
  
 -extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);
 -extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
 +extern enum commit_action get_commit_action(struct rev_info *revs,
 +                                          struct commit *commit);
 +extern enum commit_action simplify_commit(struct rev_info *revs,
 +                                        struct commit *commit);
 +
 +enum rewrite_result {
 +      rewrite_one_ok,
 +      rewrite_one_noparents,
 +      rewrite_one_error
 +};
 +
 +typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp);
  
 +extern int rewrite_parents(struct rev_info *revs, struct commit *commit,
 +      rewrite_parent_fn_t rewrite_parent);
  #endif