Merge branch 'tr/line-log'
authorJunio C Hamano <gitster@pobox.com>
Sun, 2 Jun 2013 23:00:44 +0000 (16:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 2 Jun 2013 23:00:44 +0000 (16:00 -0700)
* tr/line-log:
git-log(1): remove --full-line-diff description
line-log: fix documentation formatting
log -L: improve comments in process_all_files()
log -L: store the path instead of a diff_filespec
log -L: test merge of parallel modify/rename
t4211: pass -M to 'git log -M -L...' test
log -L: fix overlapping input ranges
log -L: check range set invariants when we look it up
Speed up log -L... -M
log -L: :pattern:file syntax to find by funcname
Implement line-history search (git log -L)
Export rewrite_parents() for 'log -L'
Refactor parse_loc

1  2 
Documentation/git-log.txt
Makefile
builtin/blame.c
builtin/log.c
log-tree.c
revision.c
revision.h
index a976534ab870a89a236ec7190f985e20f771e7f7,65707ce13ccfaba5034235439a5000eb826b6641..4687fe8192594b358f7058dcdec985ab30c2a3fb
@@@ -9,7 -9,7 +9,7 @@@ git-log - Show commit log
  SYNOPSIS
  --------
  [verse]
 -'git log' [<options>] [<since>..<until>] [[\--] <path>...]
 +'git log' [<options>] [<revision range>] [[\--] <path>...]
  
  DESCRIPTION
  -----------
@@@ -24,6 -24,13 +24,6 @@@ each commit introduces are shown
  OPTIONS
  -------
  
 -<since>..<until>::
 -      Show only commits between the named two commits.  When
 -      either <since> or <until> is omitted, it defaults to
 -      `HEAD`, i.e. the tip of the current branch.
 -      For a more complete list of ways to spell <since>
 -      and <until>, see linkgit:gitrevisions[7].
 -
  --follow::
        Continue listing the history of a file beyond renames
        (works only for a single file).
@@@ -62,23 -69,27 +62,36 @@@ produced by --stat etc
        Note that only message is considered, if also a diff is shown
        its size is not included.
  
+ -L <start>,<end>:<file>, -L :<regex>:<file>::
+       Trace the evolution of the line range given by "<start>,<end>"
+       (or the funcname regex <regex>) within the <file>.  You may
+       not give any pathspec limiters.  This is currently limited to
+       a walk starting from a single revision, i.e., you may only
+       give zero or one positive revision arguments.
+       You can specify this option more than once.
+ +
+ <start> and <end> can take one of these forms:
+ include::line-range-format.txt[]
 +<revision range>::
 +      Show only commits in the specified revision range.  When no
 +      <revision range> is specified, it defaults to `HEAD` (i.e. the
 +      whole history leading to the current commit).  `origin..HEAD`
 +      specifies all the commits reachable from the current commit
 +      (i.e. `HEAD`), but not from `origin`. For a complete list of
 +      ways to spell <revision range>, see the "Specifying Ranges"
 +      section of linkgit:gitrevisions[7].
 +
  [\--] <path>...::
        Show only commits that are enough to explain how the files
        that match the specified paths came to be.  See "History
        Simplification" below for details and other simplification
        modes.
  +
 -To prevent confusion with options and branch names, paths may need to
 -be prefixed with "\-- " to separate them from options or refnames.
 +Paths may need to be prefixed with "\-- " to separate them from
 +options or the revision range, when confusion arises.
  
  include::rev-list-options.txt[]
  
@@@ -140,6 -151,11 +153,11 @@@ Example
        This makes sense only when following a strict policy of merging all
        topic branches when staying on a single integration branch.
  
+ git log -L '/int main/',/^}/:main.c::
+       Shows how the function `main()` in the file 'main.c' evolved
+       over time.
  `git log -3`::
        Limits the number of commits to show to 3.
  
diff --combined Makefile
index 35e3f7dd8132834cecd94bd88d8dea71b53a3af6,761c698a62c8d67a9820783e9b43893dffa357a0..985598b01471428b81525a92fa2cfbf55c147bf3
+++ 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.
  #
  # 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.
  #
@@@ -365,39 -358,33 +365,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
@@@ -467,7 -454,6 +467,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
@@@ -631,6 -617,22 +631,6 @@@ LIB_FILE = libgit.
  XDIFF_LIB = xdiff/lib.a
  VCSSVN_LIB = vcs-svn/lib.a
  
 -LIB_H += xdiff/xinclude.h
 -LIB_H += xdiff/xmacros.h
 -LIB_H += xdiff/xdiff.h
 -LIB_H += xdiff/xtypes.h
 -LIB_H += xdiff/xutils.h
 -LIB_H += xdiff/xprepare.h
 -LIB_H += xdiff/xdiffi.h
 -LIB_H += xdiff/xemit.h
 -
 -LIB_H += vcs-svn/line_buffer.h
 -LIB_H += vcs-svn/sliding_window.h
 -LIB_H += vcs-svn/repo_tree.h
 -LIB_H += vcs-svn/fast_export.h
 -LIB_H += vcs-svn/svndiff.h
 -LIB_H += vcs-svn/svndump.h
 -
  GENERATED_H += common-cmds.h
  
  LIB_H += advice.h
@@@ -681,6 -683,8 +681,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
@@@ -692,6 -696,7 +694,6 @@@ LIB_H += notes-cache.
  LIB_H += notes-merge.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
@@@ -731,24 -736,11 +733,24 @@@ LIB_H += url.
  LIB_H += userdiff.h
  LIB_H += utf8.h
  LIB_H += varint.h
 +LIB_H += vcs-svn/fast_export.h
 +LIB_H += vcs-svn/line_buffer.h
 +LIB_H += vcs-svn/repo_tree.h
 +LIB_H += vcs-svn/sliding_window.h
 +LIB_H += vcs-svn/svndiff.h
 +LIB_H += vcs-svn/svndump.h
  LIB_H += walker.h
  LIB_H += wildmatch.h
  LIB_H += wt-status.h
  LIB_H += xdiff-interface.h
  LIB_H += xdiff/xdiff.h
 +LIB_H += xdiff/xdiffi.h
 +LIB_H += xdiff/xemit.h
 +LIB_H += xdiff/xinclude.h
 +LIB_H += xdiff/xmacros.h
 +LIB_H += xdiff/xprepare.h
 +LIB_H += xdiff/xtypes.h
 +LIB_H += xdiff/xutils.h
  
  LIB_OBJS += abspath.o
  LIB_OBJS += advice.o
@@@ -808,6 -800,8 +810,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
@@@ -824,6 -818,7 +828,6 @@@ LIB_OBJS += notes-cache.
  LIB_OBJS += notes-merge.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
@@@ -1060,11 -1055,6 +1064,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
  
@@@ -1398,17 -1388,11 +1402,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
@@@ -1483,11 -1467,6 +1487,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
@@@ -1567,12 -1546,12 +1571,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))
  
@@@ -1704,9 -1683,9 +1708,9 @@@ strip: $(PROGRAMS) git$
  
  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 \
@@@ -1716,9 -1695,9 +1720,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 = \
@@@ -2026,7 -2005,6 +2030,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)
@@@ -2182,9 -2160,6 +2186,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
@@@ -2469,7 -2444,7 +2473,7 @@@ profile-clean
        $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
        $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
  
 -clean: profile-clean
 +clean: profile-clean coverage-clean
        $(RM) *.o 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
@@@ -2550,34 -2525,29 +2554,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/blame.c
index 57a487e052fccc9ad45fa90a68a74fd54a1cdf72,1c09d552fa00f6180cd2a49bf713c7e3299c1d28..079dcd3407881ce0ea988c3f487faf8ca6d3e5d2
@@@ -21,6 -21,7 +21,7 @@@
  #include "parse-options.h"
  #include "utf8.h"
  #include "userdiff.h"
+ #include "line-range.h"
  
  static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
  
@@@ -566,11 -567,16 +567,16 @@@ static void dup_entry(struct blame_entr
        dst->score = 0;
  }
  
- static const char *nth_line(struct scoreboard *sb, int lno)
+ static const char *nth_line(struct scoreboard *sb, long lno)
  {
        return sb->final_buf + sb->lineno[lno];
  }
  
+ static const char *nth_line_cb(void *data, long lno)
+ {
+       return nth_line((struct scoreboard *)data, lno);
+ }
  /*
   * It is known that lines between tlno to same came from parent, and e
   * has an overlap with that range.  it also is known that parent's
@@@ -1375,15 -1381,10 +1381,15 @@@ static void get_ac_line(const char *inb
        maillen = ident.mail_end - ident.mail_begin;
        mailbuf = ident.mail_begin;
  
 -      *time = strtoul(ident.date_begin, NULL, 10);
 +      if (ident.date_begin && ident.date_end)
 +              *time = strtoul(ident.date_begin, NULL, 10);
 +      else
 +              *time = 0;
  
 -      len = ident.tz_end - ident.tz_begin;
 -      strbuf_add(tz, ident.tz_begin, len);
 +      if (ident.tz_begin && ident.tz_end)
 +              strbuf_add(tz, ident.tz_begin, ident.tz_end - ident.tz_begin);
 +      else
 +              strbuf_addstr(tz, "(unknown)");
  
        /*
         * Now, convert both name and e-mail using mailmap
@@@ -1430,7 -1431,7 +1436,7 @@@ static void get_commit_info(struct comm
        commit_info_init(ret);
  
        encoding = get_log_output_encoding();
 -      message = logmsg_reencode(commit, encoding);
 +      message = logmsg_reencode(commit, NULL, encoding);
        get_ac_line(message, "\nauthor ",
                    &ret->author, &ret->author_mail,
                    &ret->author_time, &ret->author_tz);
@@@ -1931,83 -1932,6 +1937,6 @@@ static const char *add_prefix(const cha
        return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
  }
  
- /*
-  * Parsing of (comma separated) one item in the -L option
-  */
- static const char *parse_loc(const char *spec,
-                            struct scoreboard *sb, long lno,
-                            long begin, long *ret)
- {
-       char *term;
-       const char *line;
-       long num;
-       int reg_error;
-       regex_t regexp;
-       regmatch_t match[1];
-       /* Allow "-L <something>,+20" to mean starting at <something>
-        * for 20 lines, or "-L <something>,-5" for 5 lines ending at
-        * <something>.
-        */
-       if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
-               num = strtol(spec + 1, &term, 10);
-               if (term != spec + 1) {
-                       if (spec[0] == '-')
-                               num = 0 - num;
-                       if (0 < num)
-                               *ret = begin + num - 2;
-                       else if (!num)
-                               *ret = begin;
-                       else
-                               *ret = begin + num;
-                       return term;
-               }
-               return spec;
-       }
-       num = strtol(spec, &term, 10);
-       if (term != spec) {
-               *ret = num;
-               return term;
-       }
-       if (spec[0] != '/')
-               return spec;
-       /* it could be a regexp of form /.../ */
-       for (term = (char *) spec + 1; *term && *term != '/'; term++) {
-               if (*term == '\\')
-                       term++;
-       }
-       if (*term != '/')
-               return spec;
-       /* try [spec+1 .. term-1] as regexp */
-       *term = 0;
-       begin--; /* input is in human terms */
-       line = nth_line(sb, begin);
-       if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
-           !(reg_error = regexec(&regexp, line, 1, match, 0))) {
-               const char *cp = line + match[0].rm_so;
-               const char *nline;
-               while (begin++ < lno) {
-                       nline = nth_line(sb, begin);
-                       if (line <= cp && cp < nline)
-                               break;
-                       line = nline;
-               }
-               *ret = begin;
-               regfree(&regexp);
-               *term++ = '/';
-               return term;
-       }
-       else {
-               char errbuf[1024];
-               regerror(reg_error, &regexp, errbuf, 1024);
-               die("-L parameter '%s': %s", spec + 1, errbuf);
-       }
- }
  /*
   * Parsing of -L option
   */
@@@ -2016,15 -1940,7 +1945,7 @@@ static void prepare_blame_range(struct 
                                long lno,
                                long *bottom, long *top)
  {
-       const char *term;
-       term = parse_loc(bottomtop, sb, lno, 1, bottom);
-       if (*term == ',') {
-               term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
-               if (*term)
-                       usage(blame_usage);
-       }
-       if (*term)
+       if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
                usage(blame_usage);
  }
  
@@@ -2574,10 -2490,6 +2495,6 @@@ parse_done
        bottom = top = 0;
        if (bottomtop)
                prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
-       if (bottom && top && top < bottom) {
-               long tmp;
-               tmp = top; top = bottom; bottom = tmp;
-       }
        if (bottom < 1)
                bottom = 1;
        if (top < 1)
diff --combined builtin/log.c
index 6e56a50002194e0a452266a580932b9e4e9647c9,d584ff1a39209a656581b347f645c93fbaf26996..9e2123295ffa80ecab83977099ed6a43fd4a93f9
  #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;
@@@ -37,11 -37,17 +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)) {
@@@ -76,6 -82,19 +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)
@@@ -98,16 -117,23 +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();
  }
  
@@@ -368,8 -398,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);
  }
  
@@@ -622,14 -650,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);
  }
@@@ -810,37 -822,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;
@@@ -1081,6 -1061,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 log-tree.c
index 1946e9ce8d88b73f134490760f76e00b63f1be69,793d2c8e9963b22e8684a318d8cd854e0a324857..2eb69bcfed096d28bff736deca715458605d902a
@@@ -9,7 -9,7 +9,8 @@@
  #include "string-list.h"
  #include "color.h"
  #include "gpg-interface.h"
 +#include "sequencer.h"
+ #include "line-log.h"
  
  struct decoration name_decoration = { "object names" };
  
@@@ -175,52 -175,119 +176,52 @@@ static void show_children(struct rev_in
        }
  }
  
 -void show_decorations(struct rev_info *opt, struct commit *commit)
 +/*
 + * The caller makes sure there is no funny color before
 + * calling. format_decorations makes sure the same after return.
 + */
 +void format_decorations(struct strbuf *sb,
 +                      const struct commit *commit,
 +                      int use_color)
  {
        const char *prefix;
        struct name_decoration *decoration;
        const char *color_commit =
 -              diff_get_color_opt(&opt->diffopt, DIFF_COMMIT);
 +              diff_get_color(use_color, DIFF_COMMIT);
        const char *color_reset =
 -              decorate_get_color_opt(&opt->diffopt, DECORATION_NONE);
 +              decorate_get_color(use_color, DECORATION_NONE);
  
 -      if (opt->show_source && commit->util)
 -              printf("\t%s", (char *) commit->util);
 -      if (!opt->show_decorations)
 -              return;
        decoration = lookup_decoration(&name_decoration, &commit->object);
        if (!decoration)
                return;
        prefix = " (";
        while (decoration) {
 -              printf("%s", prefix);
 -              fputs(decorate_get_color_opt(&opt->diffopt, decoration->type),
 -                    stdout);
 +              strbuf_addstr(sb, color_commit);
 +              strbuf_addstr(sb, prefix);
 +              strbuf_addstr(sb, decorate_get_color(use_color, decoration->type));
                if (decoration->type == DECORATION_REF_TAG)
 -                      fputs("tag: ", stdout);
 -              printf("%s", decoration->name);
 -              fputs(color_reset, stdout);
 -              fputs(color_commit, stdout);
 +                      strbuf_addstr(sb, "tag: ");
 +              strbuf_addstr(sb, decoration->name);
 +              strbuf_addstr(sb, color_reset);
                prefix = ", ";
                decoration = decoration->next;
        }
 -      putchar(')');
 -}
 -
 -/*
 - * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
 - * Signed-off-by: and Acked-by: lines.
 - */
 -static int detect_any_signoff(char *letter, int size)
 -{
 -      char *cp;
 -      int seen_colon = 0;
 -      int seen_at = 0;
 -      int seen_name = 0;
 -      int seen_head = 0;
 -
 -      cp = letter + size;
 -      while (letter <= --cp && *cp == '\n')
 -              continue;
 -
 -      while (letter <= cp) {
 -              char ch = *cp--;
 -              if (ch == '\n')
 -                      break;
 -
 -              if (!seen_at) {
 -                      if (ch == '@')
 -                              seen_at = 1;
 -                      continue;
 -              }
 -              if (!seen_colon) {
 -                      if (ch == '@')
 -                              return 0;
 -                      else if (ch == ':')
 -                              seen_colon = 1;
 -                      else
 -                              seen_name = 1;
 -                      continue;
 -              }
 -              if (('A' <= ch && ch <= 'Z') ||
 -                  ('a' <= ch && ch <= 'z') ||
 -                  ch == '-') {
 -                      seen_head = 1;
 -                      continue;
 -              }
 -              /* no empty last line doesn't match */
 -              return 0;
 -      }
 -      return seen_head && seen_name;
 +      strbuf_addstr(sb, color_commit);
 +      strbuf_addch(sb, ')');
 +      strbuf_addstr(sb, color_reset);
  }
  
 -static void append_signoff(struct strbuf *sb, const char *signoff)
 +void show_decorations(struct rev_info *opt, struct commit *commit)
  {
 -      static const char signed_off_by[] = "Signed-off-by: ";
 -      size_t signoff_len = strlen(signoff);
 -      int has_signoff = 0;
 -      char *cp;
 -
 -      cp = sb->buf;
 -
 -      /* First see if we already have the sign-off by the signer */
 -      while ((cp = strstr(cp, signed_off_by))) {
 +      struct strbuf sb = STRBUF_INIT;
  
 -              has_signoff = 1;
 -
 -              cp += strlen(signed_off_by);
 -              if (cp + signoff_len >= sb->buf + sb->len)
 -                      break;
 -              if (strncmp(cp, signoff, signoff_len))
 -                      continue;
 -              if (!isspace(cp[signoff_len]))
 -                      continue;
 -              /* we already have him */
 +      if (opt->show_source && commit->util)
 +              printf("\t%s", (char *) commit->util);
 +      if (!opt->show_decorations)
                return;
 -      }
 -
 -      if (!has_signoff)
 -              has_signoff = detect_any_signoff(sb->buf, sb->len);
 -
 -      if (!has_signoff)
 -              strbuf_addch(sb, '\n');
 -
 -      strbuf_addstr(sb, signed_off_by);
 -      strbuf_add(sb, signoff, signoff_len);
 -      strbuf_addch(sb, '\n');
 +      format_decorations(&sb, commit, opt->diffopt.use_color);
 +      fputs(sb.buf, stdout);
 +      strbuf_release(&sb);
  }
  
  static unsigned int digits_in_number(unsigned int number)
@@@ -378,7 -445,7 +379,7 @@@ static void show_signature(struct rev_i
  
        status = verify_signed_buffer(payload.buf, payload.len,
                                      signature.buf, signature.len,
 -                                    &gpg_output);
 +                                    &gpg_output, NULL);
        if (status && !gpg_output.len)
                strbuf_addstr(&gpg_output, "No signature\n");
  
@@@ -442,17 -509,20 +443,17 @@@ static void show_one_mergetag(struct re
        gpg_message_offset = verify_message.len;
  
        payload_size = parse_signature(extra->value, extra->len);
 -      if ((extra->len <= payload_size) ||
 -          (verify_signed_buffer(extra->value, payload_size,
 -                                extra->value + payload_size,
 -                                extra->len - payload_size,
 -                                &verify_message) &&
 -           verify_message.len <= gpg_message_offset)) {
 -              strbuf_addstr(&verify_message, "No signature\n");
 -              status = -1;
 -      }
 -      else if (strstr(verify_message.buf + gpg_message_offset,
 -                      ": Good signature from "))
 -              status = 0;
 -      else
 -              status = -1;
 +      status = -1;
 +      if (extra->len > payload_size)
 +              if (verify_signed_buffer(extra->value, payload_size,
 +                                       extra->value + payload_size,
 +                                       extra->len - payload_size,
 +                                       &verify_message, NULL)) {
 +                      if (verify_message.len <= gpg_message_offset)
 +                              strbuf_addstr(&verify_message, "No signature\n");
 +                      else
 +                              status = 0;
 +              }
  
        show_sig_lines(opt, status, verify_message.buf);
        strbuf_release(&verify_message);
@@@ -556,8 -626,8 +557,8 @@@ void show_log(struct rev_info *opt
                        printf(" (from %s)",
                               find_unique_abbrev(parent->object.sha1,
                                                  abbrev_commit));
 +              fputs(diff_get_color_opt(&opt->diffopt, DIFF_RESET), stdout);
                show_decorations(opt, commit);
 -              printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
                if (opt->commit_format == CMIT_FMT_ONELINE) {
                        putchar(' ');
                } else {
        /*
         * And then the pretty-printed message itself
         */
 -      if (ctx.need_8bit_cte >= 0)
 -              ctx.need_8bit_cte = has_non_ascii(opt->add_signoff);
 +      if (ctx.need_8bit_cte >= 0 && opt->add_signoff)
 +              ctx.need_8bit_cte =
 +                      has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"),
 +                                             getenv("GIT_COMMITTER_EMAIL")));
        ctx.date_mode = opt->date_mode;
        ctx.date_mode_explicit = opt->date_mode_explicit;
        ctx.abbrev = opt->diffopt.abbrev;
        pretty_print_commit(&ctx, commit, &msgbuf);
  
        if (opt->add_signoff)
 -              append_signoff(&msgbuf, opt->add_signoff);
 +              append_signoff(&msgbuf, 0, APPEND_SIGNOFF_DEDUP);
  
        if ((ctx.fmt != CMIT_FMT_USERFORMAT) &&
            ctx.notes_message && *ctx.notes_message) {
@@@ -725,14 -793,11 +726,14 @@@ static int log_tree_diff(struct rev_inf
  {
        int showed_log;
        struct commit_list *parents;
 -      unsigned const char *sha1 = commit->object.sha1;
 +      unsigned const char *sha1;
  
        if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
                return 0;
  
 +      parse_commit(commit);
 +      sha1 = commit->tree->object.sha1;
 +
        /* Root commit? */
        parents = commit->parents;
        if (!parents) {
                         * parent, showing summary diff of the others
                         * we merged _in_.
                         */
 -                      diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt);
 +                      parse_commit(parents->item);
 +                      diff_tree_sha1(parents->item->tree->object.sha1,
 +                                     sha1, "", &opt->diffopt);
                        log_tree_diff_flush(opt);
                        return !opt->loginfo;
                }
        for (;;) {
                struct commit *parent = parents->item;
  
 -              diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
 +              parse_commit(parent);
 +              diff_tree_sha1(parent->tree->object.sha1,
 +                             sha1, "", &opt->diffopt);
                log_tree_diff_flush(opt);
  
                showed_log |= !opt->loginfo;
@@@ -796,6 -857,9 +797,9 @@@ int log_tree_commit(struct rev_info *op
        log.parent = NULL;
        opt->loginfo = &log;
  
+       if (opt->line_level_traverse)
+               return line_log_print(opt, commit);
        shown = log_tree_diff(opt, commit, &log);
        if (!shown && opt->loginfo && opt->always_show_header) {
                log.parent = NULL;
diff --combined revision.c
index 7f7a8ab7cb38eaceeb177472385a4a5fcfabe91c,3ad9d08c7fc02cd8d346715cb0a8b88fecfdbb36..518cd086939f306f8fc6d1228bd6e61ea6d0bc22
@@@ -13,6 -13,7 +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;
@@@ -709,7 -710,7 +710,7 @@@ static int still_interesting(struct com
         * Does the destination list contain entries with a date
         * before the source list? Definitely _not_ done.
         */
 -      if (date < src->item->date)
 +      if (date <= src->item->date)
                return SLOP;
  
        /*
@@@ -915,19 -916,6 +916,19 @@@ static void add_rev_cmdline(struct rev_
        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;
@@@ -1105,7 -1093,6 +1106,7 @@@ 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_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING);
        add_pending_commit_list(revs, bases, UNINTERESTING);
        free_commit_list(bases);
        head->object.flags |= SYMMETRIC_LEFT;
@@@ -1193,9 -1180,6 +1194,9 @@@ int handle_revision_arg(const char *arg
  
                        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);
@@@ -1293,8 -1277,7 +1294,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(xstrdup(sb.buf), revs, 0,
 +                                      REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
        if (seen_dashdash)
@@@ -1914,6 -1897,12 +1915,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,
@@@ -2040,11 -2029,10 +2047,11 @@@ static struct commit_list **simplify_on
                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(commit);
  
        /*
         * It is possible that we are a merge and one side branch
@@@ -2185,6 -2173,8 +2192,8 @@@ int prepare_revision_walk(struct rev_in
                        return -1;
        if (revs->topo_order)
                sort_in_topological_order(&revs->commits, revs->lifo);
+       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;
        }
  }
  
- 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:
@@@ -2309,7 -2294,7 +2313,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)
@@@ -2390,7 -2375,7 +2394,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;
diff --combined revision.h
index 878a5556fd5e1c82f0fc35e1669e33623397ceeb,6b2870ca10d4b057453f39320ab7d67535b490cc..a313a1340536321080c44d502a2bb6bc105940d5
@@@ -35,7 -35,6 +35,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;
@@@ -97,7 -96,8 +97,8 @@@ struct rev_info 
                        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;
        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
@@@ -242,4 -245,14 +246,14 @@@ enum commit_action 
  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