Git development
 help / color / mirror / Atom feed
* html userdiff is not showing all my changes
From: Scott Johnson @ 2010-12-15  3:47 UTC (permalink / raw)
  To: git, trast

I am attempting to do a word diff of an html source file. Part of the removed 
html is disappearing from the diff when I enable the fancy html word diff.

Here's the output from basic `git diff`:
diff --git a/adv_layout_source.html b/adv_layout_source.html
index 18a81dd..c4ed609 100644
--- a/adv_layout_source.html
+++ b/adv_layout_source.html
@@ -42,8 +42,8 @@
       <ul>
         <li class="ydn-patterns"><em></em><a href="#">ydn-patterns</a></li>
         <li class="ydn-mail"><em></em><a href="#">ydn-mail</a></li>
-        <li class="yws-maps"><em></em><a href="#">yws-maps</a></li>
-        <li class="ydn-delicious"><em></em><a href="#">ydn-delicious</a></li>
+        <li><em></em><a href="#">yws-maps</a></li>
+        <li><em></em><a href="#">ydn-delicious</a></li>
         <li class="yws-flickr"><em></em><a href="#">yws-flickr</a></li>
         <li class="yws-events"><em></em><a href="#">yws-events</a></li>
       </ul>


Here's the default `git diff --word-diff`:
diff --git a/adv_layout_source.html b/adv_layout_source.html
index 18a81dd..c4ed609 100644
--- a/adv_layout_source.html
+++ b/adv_layout_source.html
@@ -42,8 +42,8 @@
      <ul>
        <li class="ydn-patterns"><em></em><a href="#">ydn-patterns</a></li>
        <li class="ydn-mail"><em></em><a href="#">ydn-mail</a></li>
        [-<li class="yws-maps"><em></em><a-]{+<li><em></em><a+} 
href="#">yws-maps</a></li>
        [-<li class="ydn-delicious"><em></em><a-]{+<li><em></em><a+} 
href="#">ydn-delicious</a></li>
        <li class="yws-flickr"><em></em><a href="#">yws-flickr</a></li>
        <li class="yws-events"><em></em><a href="#">yws-events</a></li>
      </ul>

Which is correct, but less than ideal because it highlights much more than the 
actual changes.

So I create a .gitattributes file with one line:
*.html diff=html

And rerun `git diff --word-diff`:
diff --git a/adv_layout_source.html b/adv_layout_source.html
index 18a81dd..c4ed609 100644
--- a/adv_layout_source.html
+++ b/adv_layout_source.html
@@ -42,8 +42,8 @@
      <ul>
        <li class="ydn-patterns"><em></em><a href="#">ydn-patterns</a></li>
        <li class="ydn-mail"><em></em><a href="#">ydn-mail</a></li>
        <li[-class="yws-maps"-]><em></em><a href="#">yws-maps</a></li>
        <li><em></em><a href="#">ydn-delicious</a></li>
        <li class="yws-flickr"><em></em><a href="#">yws-flickr</a></li>
        <li class="yws-events"><em></em><a href="#">yws-events</a></li>
      </ul>

Yikes! What happened to the second line of changes? The removed code is not 
displayed at all.

This is running git 1.7.3.3.

I suspect the problem is in the html patterns in userdiff.c, but I don't 
understand the word-diff-regex well enough to fix it.

^ permalink raw reply related

* Re: [PATCH 3/3] get_sha1: support ref^{/regex} syntax
From: Nguyen Thai Ngoc Duy @ 2010-12-15  3:12 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Jonathan Niedier, Kevin Ballard, Yann Dirson, Jeff King,
	Jakub Narebski, Thiago Farina
In-Reply-To: <7v62uvrbfk.fsf@alter.siamese.dyndns.org>

On Wed, Dec 15, 2010 at 9:56 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
>
>> Perhaps we should just reject this case. No sane body would ever use it.
>
> I'd have to say that dismissing before even trying is not a very
> disciplined attitude.
>
> Shouldn't an empty regexp simply match everything?  Even on FBSD8,
>
>    $ grep '' /etc/passwd
>
> seems to show everything.  How hard would it be to do this in the codepath
> we are discussing?

To make '' match everything is easy. I'll cook up something when I get home.
-- 
Duy

^ permalink raw reply

* Re: [PATCH v3 3/8] mingw: make failures to unlink or move raise a question
From: Junio C Hamano @ 2010-12-15  3:05 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Erik Faye-Lund, Heiko Voigt, Johannes Sixt, Pat Thoyts, msysgit,
	git, Junio C Hamano, Albert Dvornik
In-Reply-To: <alpine.DEB.1.00.1012150109340.1461@bonsai2>

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> > @@ -129,6 +130,78 @@ static inline int is_file_in_use_error(DWORD errcode)
>> >        return 0;
>> >  }
>> >
>> > +static int read_yes_no_answer()
>> 
>> Perhaps "static int read_yes_no_answer(void)" for portability?
>
> LOL. This file is called compat/mingw.c... :-)

I had the same reaction.  Maybe MinGW will get a different compiler
someday ;-)

> But I have no objection to stay with the convention of the rest of Git. 
> Nobody needs to convince me that consistency is good.

I recall there are a few old-style declaration in compat/ directory,
especially in borrowed code like nedmalloc/ and possibly regex/, and
I am not so sure if we want to touch them.

I'll leave this up to msysgit folks.

Thanks.

^ permalink raw reply

* Re: [PATCH 3/3] get_sha1: support ref^{/regex} syntax
From: Junio C Hamano @ 2010-12-15  2:56 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy
  Cc: git, Jonathan Niedier, Kevin Ballard, Yann Dirson, Jeff King,
	Jakub Narebski, Thiago Farina
In-Reply-To: <AANLkTikgXAg9+0TXzpFH7LT1yD2sV5Dp=025GbudnY5S@mail.gmail.com>

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:

> Perhaps we should just reject this case. No sane body would ever use it.

I'd have to say that dismissing before even trying is not a very
disciplined attitude.

Shouldn't an empty regexp simply match everything?  Even on FBSD8,

    $ grep '' /etc/passwd

seems to show everything.  How hard would it be to do this in the codepath
we are discussing?

^ permalink raw reply

* Re: [PATCH 3/3] get_sha1: support ref^{/regex} syntax
From: Nguyen Thai Ngoc Duy @ 2010-12-15  1:58 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Jonathan Niedier, Kevin Ballard, Yann Dirson, Jeff King,
	Jakub Narebski, Thiago Farina
In-Reply-To: <7vaak7rh9k.fsf@alter.siamese.dyndns.org>

2010/12/15 Junio C Hamano <gitster@pobox.com>:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> +test_expect_success 'ref^{/}' '
>> +     git rev-parse master >expected &&
>> +     git rev-parse master^{/} >actual &&
>> +     test_cmp expected actual
>> +'
>
> This test fails on FBSD 8, which refuses to regcomp("")
> saying "empty (sub)expression", which is somewhat bogus.
>
> I'd change it to "/." for now ;-).

Perhaps we should just reject this case. No sane body would ever use it.
-- 
Duy

^ permalink raw reply

* Re: [PATCH 3/3] get_sha1: support ref^{/regex} syntax
From: Junio C Hamano @ 2010-12-15  0:50 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, Jonathan Niedier, Kevin Ballard, Yann Dirson, Jeff King,
	Jakub Narebski, Thiago Farina
In-Reply-To: <1292209275-17451-3-git-send-email-pclouds@gmail.com>

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> +test_expect_success 'ref^{/}' '
> +	git rev-parse master >expected &&
> +	git rev-parse master^{/} >actual &&
> +	test_cmp expected actual
> +'

This test fails on FBSD 8, which refuses to regcomp("")
saying "empty (sub)expression", which is somewhat bogus.

I'd change it to "/." for now ;-).

^ permalink raw reply

* Re: [PATCH] branch: do not attempt to track HEAD implicitly
From: Martin von Zweigbergk @ 2010-12-15  0:50 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git
In-Reply-To: <6ee1f0174b757e25ab873d2d037545ac7db698ee.1292351886.git.trast@student.ethz.ch>

On Tue, Dec 14, 2010 at 1:38 PM, Thomas Rast <trast@student.ethz.ch> wrote:
> With branch.autosetupmerge=always, 'HEAD' becomes a valid target for
> branch tracking.  This is a bit silly, and in fact the code already
> guards against it when attempting it explicitly:
>
>  $ git checkout HEAD^0
>  $ g branch -t foo

missing the "it" in "git"

>  fatal: Cannot setup tracking information; starting point is not a branch.
>
> Silently drop the HEAD candidate in the implicit (i.e. without -t
> flag) case, so that the branch starts out without an upstream.

Thanks. This has been on my todo list for a while.

Should it only check for HEAD? How about ORIG_HEAD and FETCH_HEAD?
Simply anything outside of refs/ maybe? Would that make sense?

/Martin

^ permalink raw reply

* Re: [PATCH v3 5/5] mingw_rmdir: set errno=ENOTEMPTY when appropriate
From: Johannes Schindelin @ 2010-12-15  0:21 UTC (permalink / raw)
  To: Erik Faye-Lund
  Cc: Heiko Voigt, Johannes Sixt, Pat Thoyts, msysgit, git,
	Junio C Hamano
In-Reply-To: <AANLkTi=12WqYn5GJpqPYqKE_2L51Em7Ek5szTZYryHd9@mail.gmail.com>

Hi,

On Tue, 14 Dec 2010, Erik Faye-Lund wrote:

> On Tue, Dec 14, 2010 at 11:28 PM, Heiko Voigt <hvoigt@hvoigt.net> wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > On Windows, EACCES overrules ENOTEMPTY when calling rmdir(). But if the
> > directory is busy, we only want to retry deleting the directory if it
> > is empty, so test specifically for that case and set ENOTEMPTY rather
> > than EACCES.
> >
> 
> Hmm... According to MSDN, rmdir(*) should already handle ENOTEMPTY. 
> Isn't the problem rather the structure of that loop? Shouldn't it be 
> sufficient to do something like this (note: untested, but the concept 
> should work, no)?

This is how the patch looks in current 4msysgit.git's devel branch:

-- snip --
 #undef rmdir
 int mingw_rmdir(const char *pathname)
 {
-    int ret, tries = 0;
+       int ret, tries = 0;
 
        while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) 
{
                if (errno != EACCES)
                        break;
+               if (!is_dir_empty(pathname)) {
+                       errno = ENOTEMPTY;
+                       break;
+               }
                /*
                 * We assume that some other process had the source or
                 * destination file open at the wrong moment and retry.
-- snap --

Of course, with so much water running down the Elbe between me writing 
that patch and me answering you, I cannot really say whether rmdir() 
set errno to ENOTEMPTY.

But as the patch looked the same when I wrote it originally (you can see 
it in the history, since I introduced rebasing merges prior to making this 
patch) I would assume that in my tests, errno was set to EACCESS rather 
than ENOTEMPTY.

However, since I am distrusted on the Git mailing list it would be good if 
you tried your version and verified whether what I say is true.

Thanks,
Dscho

^ permalink raw reply

* Re: [PATCH v6.1 4/8] Implement line-history search (git log -L)
From: Junio C Hamano @ 2010-12-15  0:20 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Bo Yang
In-Reply-To: <426fca7313fc9466efa036b7b86947f23548fc26.1292366984.git.trast@student.ethz.ch>

Thomas Rast <trast@student.ethz.ch> writes:

> +void line_log_init(struct rev_info *rev, struct diff_line_range *r)
> +{
> +	struct commit *commit = NULL;
> +	struct diff_options *opt = &rev->diffopt;
> +
> +	commit = (struct commit *)verify_commit(rev);
> +	parse_lines(commit, r);
> +
> +	add_line_range(rev, commit, r);
> +	/*
> +	 * Note we support -M/-C to detect file rename
> +	 */
> +	opt->nr_paths = 0;
> +	diff_tree_release_paths(opt);
> +}

Note that opt->nr_paths may be going away soon (cf. nd/struct-pathspec
topic).  Do you need this assignment here?

^ permalink raw reply

* Re: [PATCH v3 3/8] mingw: make failures to unlink or move raise a question
From: Johannes Schindelin @ 2010-12-15  0:11 UTC (permalink / raw)
  To: Erik Faye-Lund
  Cc: Heiko Voigt, Johannes Sixt, Pat Thoyts, msysgit, git,
	Junio C Hamano, Albert Dvornik
In-Reply-To: <AANLkTi=cHb2kV2MaYu72nXVOksO7O9HhJLEo-fU0sV5N@mail.gmail.com>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 5394 bytes --]

Hi,

On Tue, 14 Dec 2010, Erik Faye-Lund wrote:

> On Tue, Dec 14, 2010 at 11:21 PM, Heiko Voigt <hvoigt@hvoigt.net> wrote:
> > On Windows in case a program is accessing a file unlink or
> > move operations may fail. To give the user a chance to correct
> > this we simply wait until the user asks us to retry or fail.
> >
> > This is useful because of the following use case which seem
> > to happen rarely but when it does it is a mess:
> >
> > After making some changes the user realizes that he was on the
> > incorrect branch. When trying to change the branch some file
> > is still in use by some other process and git stops in the
> > middle of changing branches. Now the user has lots of files
> > with changes mixed with his own. This is especially confusing
> > on repositories that contain lots of files.
> >
> > Although the recent implementation of automatic retry makes
> > this scenario much more unlikely lets provide a fallback as
> > a last resort.
> >
> > Thanks to Albert Dvornik for disabling the question if users can't see it.
> >
> > If the stdout of the command is connected to a terminal but the stderr
> > has been redirected, the odds are good that the user can't see any
> > question we print out to stderr.  This will result in a "mysterious
> > hang" while the app is waiting for user input.
> >
> > It seems better to be conservative, and avoid asking for input
> > whenever the stderr is not a terminal, just like we do for stdin.
> >
> > Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>
> > Signed-off-by: Albert Dvornik <dvornik+git@gmail.com>
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > I have added the sign-off from the squashed commit of Albert and
> > Johannes. I hope its ok this way.

I'm fine with it.

> >  compat/mingw.c |   82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  1 files changed, 82 insertions(+), 0 deletions(-)
> >
> > diff --git a/compat/mingw.c b/compat/mingw.c
> > index 52183a7..ac9fb4a 100644
> > --- a/compat/mingw.c
> > +++ b/compat/mingw.c
> > @@ -2,6 +2,7 @@
> >  #include "win32.h"
> >  #include <conio.h>
> >  #include "../strbuf.h"
> > +#include "../run-command.h"
> >
> >  static const int delay[] = { 0, 1, 10, 20, 40 };
> >
> > @@ -129,6 +130,78 @@ static inline int is_file_in_use_error(DWORD errcode)
> >        return 0;
> >  }
> >
> > +static int read_yes_no_answer()
> 
> Perhaps "static int read_yes_no_answer(void)" for portability?

LOL. This file is called compat/mingw.c... :-)

But I have no objection to stay with the convention of the rest of Git. 
Nobody needs to convince me that consistency is good.

> > +{
> > +       char answer[1024];
> > +
> > +       if (fgets(answer, sizeof(answer), stdin)) {
> > +               size_t answer_len = strlen(answer);
> > +               int got_full_line = 0, c;
> > +
> > +               /* remove the newline */
> > +               if (answer_len >= 2 && answer[answer_len-2] == '\r') {
> > +                       answer[answer_len-2] = '\0';
> > +                       got_full_line = 1;
> > +               }
> > +               else if (answer_len >= 1 && answer[answer_len-1] == '\n') {
> > +                       answer[answer_len-1] = '\0';
> > +                       got_full_line = 1;
> > +               }
> > +               /* flush the buffer in case we did not get the full line */
> > +               if (!got_full_line)
> > +                       while((c = getchar()) != EOF && c != '\n');
> > +       } else
> > +               /* we could not read, return the
> > +                * default answer which is no */
> > +               return 0;
> > +
> > +       if (answer[0] == 'y' && strlen(answer) == 1)
> > +               return 1;
> > +       if (!strncasecmp(answer, "yes", sizeof(answer)))
> > +               return 1;
> > +       if (answer[0] == 'n' && strlen(answer) == 1)
> > +               return 0;
> > +       if (!strncasecmp(answer, "no", sizeof(answer)))
> > +               return 0;
> 
> Since you're doing case insensitive checks for "yes" and "no", perhaps
> it'd make sense to allow upper case 'Y' and 'N' also? Something like:
> 
> -       if (answer[0] == 'n' && strlen(answer) == 1)
> +       if (tolower(answer[0]) == 'n' && strlen(answer) == 1)
> 
> hm?

Makes sense to me.

> > +static int ask_user_yes_no(const char *format, ...)
> > +{
> > +       char question[4096];
> > +       const char *retry_hook[] = { NULL, NULL, NULL };
> > +       va_list args;
> > +
> > +       if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) {
> > +
> > +               va_start(args, format);
> > +               vsnprintf(question, sizeof(question), format, args);
> > +               va_end(args);
> > +
> > +               retry_hook[1] = question;
> > +               return !run_command_v_opt(retry_hook, 0);
> > +       }
> > +
> > +       if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
> > +               return 0;
> 
> I'm wondering, doesn't this make the semantics a bit wrong? The
> function is called "ask_user_yes_no", but it might end up not asking
> after all. Perhaps it should be called something that reflects this?
> "maybe_ask_yes_no", "ask_yes_no_if_tty", "should_retry"? I don't have
> a non-ugly suggestion, but I suspect something like that might leave
> other people less puzzled when reading the code.

I like ask_yes_no_if_tty.

Ciao,
Dscho

^ permalink raw reply

* Re: [PATCH v3 3/8] mingw: make failures to unlink or move raise a question
From: Junio C Hamano @ 2010-12-14 23:52 UTC (permalink / raw)
  To: kusmabite
  Cc: Heiko Voigt, Johannes Sixt, Pat Thoyts, msysgit, git,
	Junio C Hamano, Albert Dvornik, Johannes Schindelin
In-Reply-To: <AANLkTi=cHb2kV2MaYu72nXVOksO7O9HhJLEo-fU0sV5N@mail.gmail.com>

Erik Faye-Lund <kusmabite@gmail.com> writes:

> Since you're doing case insensitive checks for "yes" and "no", perhaps
> it'd make sense to allow upper case 'Y' and 'N' also? Something like:
>
> -       if (answer[0] == 'n' && strlen(answer) == 1)
> +       if (tolower(answer[0]) == 'n' && strlen(answer) == 1)
>
> hm?

Why not

	if (tolower(answer[0]) == 'n' && !answer[1])

think of the case answer[] is very long ;-)

>> +       if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
>> +               return 0;
>
> I'm wondering, doesn't this make the semantics a bit wrong? The
> function is called "ask_user_yes_no", but it might end up not asking
> after all.

I don't think that is such a big issue.

Imagine you had only getenv("GIT_ASK_YESNO") codepath, and no fallback
"tty" codepath.  And you ship with a separate program as a default
"asker".

The implementation of that asker happens to read yes/no from the tty, but
it defauts to "no" if there is no tty interaction available.

If you view it that way, the code we see above is just an optimization to
avoid spawning that default "asker" as a separate process.

I was more puzzled by the code to formulate question[]; why doesn't it
build the same question for both codepaths and spit that out to stderr
with fputs() in the fallvack asker?

^ permalink raw reply

* Re: [PATCH] git-p4: Fix 'p4 opened' in git-p4 for names with spaces
From: Reece Dunn @ 2010-12-14 23:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jerzy Kozera, git
In-Reply-To: <7vvd2wq72l.fsf@alter.siamese.dyndns.org>

On 14 December 2010 23:16, Junio C Hamano <gitster@pobox.com> wrote:
> Jerzy Kozera <jerzy.kozera@gmail.com> writes:
>
>> Signed-off-by: Jerzy Kozera <jerzy.kozera@gmail.com>
>> ---
>>  contrib/fast-import/git-p4 |    2 +-
>>  1 files changed, 1 insertions(+), 1 deletions(-)
>>
>> diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
>> index 04ce7e3..a5297e7 100755
>> --- a/contrib/fast-import/git-p4
>> +++ b/contrib/fast-import/git-p4
>> @@ -144,7 +144,7 @@ def setP4ExecBit(file, mode):
>>  def getP4OpenedType(file):
>>      # Returns the perforce file type for the given file.
>>
>> -    result = p4_read_pipe("opened %s" % file)
>> +    result = p4_read_pipe("opened \"%s\"" % file)
>
> Don't you need a lot more than that?  What if file has " or \ in it?

Those are invalid characters for a filename on Windows, so cannot be
entered/present in the filename. On Linux, they are accepted, but
don't get put into the filename, so it all depends on where the data
for file comes from (API call or user/external source). Not sure how
Mac/BSD/Solaris handle those characters.

This looks fine to me, but I wonder if there are other places
referencing file paths that require quoting to correctly handle
spaces.

Also, escaping the quote characters can be avoided by using single
quoted string literals:

+    result = p4_read_pipe('opened "%s"' % file)

- Reece

^ permalink raw reply

* Re: [PATCH 11/14] t3032-*.sh: Pass the -b (--binary) option to sed on cygwin
From: Eric Sunshine @ 2010-12-14 23:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Ramsay Jones, GIT Mailing-list
In-Reply-To: <7vtyigtaxn.fsf@alter.siamese.dyndns.org>

On 12/14/2010 2:24 PM, Junio C Hamano wrote:
> Ramsay Jones<ramsay@ramsay1.demon.co.uk>  writes:
>> The test using the conflict_hunks helper function (test 9) fails
>> on cygwin, since sed (by default) throws away the CR from CRLF
>> line endings. This behaviour is undesirable, since the validation
>> code expects the CRLF line-ending to be present. In order to fix
>> the problem we pass the -b (--binary) option to sed, using the
>> SED_OPTIONS variable. We use the SED_STRIPS_CR prerequisite in the
>> conditional initialisation of SED_OPTIONS.
>>
>> Signed-off-by: Ramsay Jones<ramsay@ramsay1.demon.co.uk>
>> ---
>>
>> Note that this test does not fail on MinGW, but I don't
>> really know why, given commit ca02ad3... ahem ;-)
>
> Ahem, indeed.  Why?

t3032 does indeed fail on MinGW, and was fixed in the msysgit port by 
[1], but was subsequently "lost" when msysgit was rebased onto 
junio/next [2] which did not have that test. Consequently, the fix never 
made it into the mainline git source.

[1]: 
http://groups.google.com/group/msysgit/browse_thread/thread/587d32ee034b0cbe/dca93dc6ad755012#dca93dc6ad755012
[2]: 
http://groups.google.com/group/msysgit/browse_thread/thread/d522ec5c13a3af0b/718eaedffc042fb5#718eaedffc042fb5

-- ES

^ permalink raw reply

* Re: [PATCH 1/8] Refactor parse_loc
From: Junio C Hamano @ 2010-12-14 23:20 UTC (permalink / raw)
  To: Thomas Rast; +Cc: Junio C Hamano, git, Bo Yang
In-Reply-To: <201012150006.12462.trast@student.ethz.ch>

Thomas Rast <trast@student.ethz.ch> writes:

> Junio C Hamano wrote:
>> Thomas Rast <trast@student.ethz.ch> writes:
>> > +test_expect_success 'blame -L parses end' '
>> > +	git blame -L1,1 tres >out &&
>> > +	cat out &&
>> > +	test $(wc -l < out) -eq 1
>> > +'
>> 
>> What does this test exactly?  "end"?
>
> That's the test I was referring to in the commit message:
>
>   The new test is for a case that made me pause during debugging: the
>   'blame -L with invalid end' test was the only one that noticed an
>   outright failure to parse the end *at all*.  So make a more explicit
>   test for that.
>
> So I guess a more thorough test title would be
>
>   blame -L parses <end> part of its argument
>
> IOW, the test verifies that -L1,1 is not parsed the same as -L1, which
> would result in *two* lines of output.

Oh the comment definitely was misleading.  It made me think that "-L1,1" was
a typo of "-L1,$" which was the new feature in the patch.

^ permalink raw reply

* Re: [PATCH] git-p4: Fix 'p4 opened' in git-p4 for names with spaces
From: Junio C Hamano @ 2010-12-14 23:16 UTC (permalink / raw)
  To: Jerzy Kozera; +Cc: git
In-Reply-To: <1292360165-26771-2-git-send-email-jerzy.kozera@gmail.com>

Jerzy Kozera <jerzy.kozera@gmail.com> writes:

> Signed-off-by: Jerzy Kozera <jerzy.kozera@gmail.com>
> ---
>  contrib/fast-import/git-p4 |    2 +-
>  1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
> index 04ce7e3..a5297e7 100755
> --- a/contrib/fast-import/git-p4
> +++ b/contrib/fast-import/git-p4
> @@ -144,7 +144,7 @@ def setP4ExecBit(file, mode):
>  def getP4OpenedType(file):
>      # Returns the perforce file type for the given file.
>  
> -    result = p4_read_pipe("opened %s" % file)
> +    result = p4_read_pipe("opened \"%s\"" % file)

Don't you need a lot more than that?  What if file has " or \ in it?

^ permalink raw reply

* Re: [PATCH 2/8] Export three functions from diff.c
From: Junio C Hamano @ 2010-12-14 23:08 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Bo Yang
In-Reply-To: <4973127c3f9251b92c4836b19d818a340741102a.1292291624.git.trast@student.ethz.ch>

Thomas Rast <trast@student.ethz.ch> writes:

> From: Bo Yang <struggleyb.nku@gmail.com>
>
> Use fill_metainfo to fill the line level diff meta data,
> emit_line to print out a line and quote_two to quote
> paths.
>
> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>
> ---
>  diff.c |    6 +++---
>  diff.h |   17 +++++++++++++++++
>  2 files changed, 20 insertions(+), 3 deletions(-)
>
> diff --git a/diff.c b/diff.c
> index 6991ed4..a0ea9e5 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -151,7 +151,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
>  	return git_color_default_config(var, value, cb);
>  }
>  
> -static char *quote_two(const char *one, const char *two)
> +char *quote_two(const char *one, const char *two)
>  {
>  	int need_one = quote_c_style(one, NULL, NULL, 1);
>  	int need_two = quote_c_style(two, NULL, NULL, 1);

This is not specific to "diff" anymore (it is a utility to cquote a path
that happens to be stored as two separate components); wouldn't quote.c
be a better home for it?

> -static void emit_line(struct diff_options *o, const char *set, const char *reset,
> +void emit_line(struct diff_options *o, const char *set, const char *reset,
>  		      const char *line, int len)
>  {
>  	emit_line_0(o, set, reset, line[0], line+1, len-1);

Within the context of "diff", it is clear that a function called
emit_line() will be used to emit a single line of patch output, and within
that context, it is understandable that it takes coloring related arguments
as we do emit diff output in color.

But does it still make sense to give it such a generic sounding name when
exported outside of its original context?  Call it either emit-diff-line
(if "diff-ness" is more important, and the new callers will use the
function to emit diff output) or emit-colored-line (if the new caller
produces output not necessarily related to diff, but is merely borrowing
the coloring infrastructure from this implementation), perhaps?

> @@ -2583,7 +2583,7 @@ static int similarity_index(struct diff_filepair *p)
>  	return p->score * 100 / MAX_SCORE;
>  }
>  
> -static void fill_metainfo(struct strbuf *msg,
> +void fill_metainfo(struct strbuf *msg,
>  			  const char *name,
>  			  const char *other,
>  			  struct diff_filespec *one,

Likewise.  Within the context of "diff", it is clear what kind of metainfo
we mean (that's the one between "diff --git" and the first "@@ ... @@").
Other APIs with different kind of "metainfo" may later want to introduce a
totally different function that fills their metainfo, and calling this
"fill_metainfo" forces them to use some different name, and cause
confusion to readers.  Perhaps fill-diff-metainfo?

^ permalink raw reply

* Re: [PATCH 1/8] Refactor parse_loc
From: Thomas Rast @ 2010-12-14 23:06 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Bo Yang
In-Reply-To: <7v8vzsrmhk.fsf@alter.siamese.dyndns.org>

Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> > +test_expect_success 'blame -L parses end' '
> > +	git blame -L1,1 tres >out &&
> > +	cat out &&
> > +	test $(wc -l < out) -eq 1
> > +'
> 
> What does this test exactly?  "end"?

That's the test I was referring to in the commit message:

  The new test is for a case that made me pause during debugging: the
  'blame -L with invalid end' test was the only one that noticed an
  outright failure to parse the end *at all*.  So make a more explicit
  test for that.

So I guess a more thorough test title would be

  blame -L parses <end> part of its argument

IOW, the test verifies that -L1,1 is not parsed the same as -L1, which
would result in *two* lines of output.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply

* Re: subdirectory-filter does not delete files before the directory came into existence?
From: Thomas Rast @ 2010-12-14 23:03 UTC (permalink / raw)
  To: Jan Wielemaker; +Cc: git
In-Reply-To: <1292365295.6666.22.camel@ct>

Jan Wielemaker wrote:
> I try to extract a directory.  The result is fine, but there is a lot
> of history in the result from *before* the directory was added to the
> project.  Why?  How can I get rid of this?
[...]
> Now use e.g. qgit to look at the history.  As from 03/07/2002, when
> the packages/odbc directory was created, all looks just fine.  Before
> though ...

That history is not connected to the filtered one.  git-filter-branch
alerts you to it with messages like

  WARNING: Ref 'refs/tags/V5.0.4' is unchanged
  WARNING: Ref 'refs/tags/V5.0.5' is unchanged
  WARNING: Ref 'refs/tags/V5.0.6' is unchanged
  WARNING: Ref 'refs/tags/V5.0.7' is unchanged

I haven't made up my mind if this is a bug report or a feature
request, but in any case you can delete all of them and the problem
goes away.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply

* Re: [PATCH 1/8] Refactor parse_loc
From: Junio C Hamano @ 2010-12-14 22:57 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Bo Yang
In-Reply-To: <ff9def9ac3f207b9f9be9c59ec3a313c9b3d4d84.1292291624.git.trast@student.ethz.ch>

Thomas Rast <trast@student.ethz.ch> writes:

> diff --git a/line.c b/line.c
> new file mode 100644
> index 0000000..29898ec
> --- /dev/null
> +++ b/line.c
> @@ -0,0 +1,106 @@
> +#include "git-compat-util.h"
> +#include "line.h"
> +
> +/*
> + * Parse one item in the -L option
> + */
> +const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
> +		void *data, long lines, long begin, long *ret)
> +{
> +        char *term;
> +        const char *line;
> +        long num;
> +        int reg_error;
> +        regex_t regexp;
> +        regmatch_t match[1];
> +
> +        /* Catch the '$' matcher, now it is used to match the last
> +         * line of the file. */

"now"?  What now, as opposed to which then?

Ahh, is it an artifact of squashing multiple patches, one that moves the
function and then another that adds a new feature?

In any case, please fix the style of multi-line comment.  I wouldn't mind
if you fixed the other one you moved from blame.c to this function (I
omitted it from the context but you know which one I mean).

> ...
> +int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
> +		void *cb_data, long lines, long *begin, long *end)
> +{
> +	arg = parse_loc(arg, nth_line_cb, cb_data, lines, -1, begin);
> +
> +        if (*arg == ',') {
> +		arg = parse_loc(arg+1, nth_line_cb, cb_data, lines, *begin+1, end);
> +		if (*begin > *end) {
> +			long tmp = *begin;
> +			*begin = *end;
> +			*end = tmp;
> +		}

It is somewhat unfortunate that this "swap begin and end given -L9,4" is
done here not in the caller---for some callers 9,4 and 4,9 may mean
different things.  But for now this would do.

> diff --git a/t/t8003-blame.sh b/t/t8003-blame.sh
> index 230143c..51d313e 100755
> --- a/t/t8003-blame.sh
> +++ b/t/t8003-blame.sh
> @@ -175,6 +175,12 @@ test_expect_success 'blame -L with invalid end' '
>  	grep "has only 2 lines" errors
>  '
>  
> +test_expect_success 'blame -L parses end' '
> +	git blame -L1,1 tres >out &&
> +	cat out &&
> +	test $(wc -l < out) -eq 1
> +'

What does this test exactly?  "end"?

^ permalink raw reply

* [PATCH v6.1 3/8] Export rewrite_parents() for 'log -L'
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Bo Yang
In-Reply-To: <cover.1292366984.git.trast@student.ethz.ch>

From: Bo Yang <struggleyb.nku@gmail.com>

The function rewrite_one is used to rewrite a single
parent of the current commit, and is used by rewrite_parents
to rewrite all the parents.

Decouple the dependence between them by making rewrite_one
a callback function that is passed to rewrite_parents. Then
export rewrite_parents for reuse by the line history browser.

We will use this function in line.c.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 revision.c |   13 ++++---------
 revision.h |   10 ++++++++++
 2 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/revision.c b/revision.c
index ded8812..6465c45 100644
--- a/revision.c
+++ b/revision.c
@@ -1912,12 +1912,6 @@ int prepare_revision_walk(struct rev_info *revs)
 	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;
@@ -1939,12 +1933,13 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
 	}
 }
 
-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:
@@ -2012,7 +2007,7 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
 	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 --git a/revision.h b/revision.h
index 05659c6..8897368 100644
--- a/revision.h
+++ b/revision.h
@@ -193,4 +193,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
-- 
1.7.3.3.807.g6ee1f

^ permalink raw reply related

* [PATCH v6.1 2/8] Export three functions from diff.c
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Bo Yang
In-Reply-To: <cover.1292366984.git.trast@student.ethz.ch>

From: Bo Yang <struggleyb.nku@gmail.com>

Use fill_metainfo to fill the line level diff meta data,
emit_line to print out a line and quote_two to quote
paths.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 diff.c |    6 +++---
 diff.h |   17 +++++++++++++++++
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/diff.c b/diff.c
index 0a43869..a16ce69 100644
--- a/diff.c
+++ b/diff.c
@@ -151,7 +151,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
 	return git_color_default_config(var, value, cb);
 }
 
-static char *quote_two(const char *one, const char *two)
+char *quote_two(const char *one, const char *two)
 {
 	int need_one = quote_c_style(one, NULL, NULL, 1);
 	int need_two = quote_c_style(two, NULL, NULL, 1);
@@ -332,7 +332,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 		fputc('\n', file);
 }
 
-static void emit_line(struct diff_options *o, const char *set, const char *reset,
+void emit_line(struct diff_options *o, const char *set, const char *reset,
 		      const char *line, int len)
 {
 	emit_line_0(o, set, reset, line[0], line+1, len-1);
@@ -2583,7 +2583,7 @@ static int similarity_index(struct diff_filepair *p)
 	return p->score * 100 / MAX_SCORE;
 }
 
-static void fill_metainfo(struct strbuf *msg,
+void fill_metainfo(struct strbuf *msg,
 			  const char *name,
 			  const char *other,
 			  struct diff_filespec *one,
diff --git a/diff.h b/diff.h
index 0083d92..165f368 100644
--- a/diff.h
+++ b/diff.h
@@ -12,6 +12,7 @@ struct diff_queue_struct;
 struct strbuf;
 struct diff_filespec;
 struct userdiff_driver;
+struct diff_filepair;
 
 typedef void (*change_fn_t)(struct diff_options *options,
 		 unsigned old_mode, unsigned new_mode,
@@ -317,4 +318,20 @@ extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
 
 extern int parse_rename_score(const char **cp_p);
 
+/* some output functions line.c need */
+extern void fill_metainfo(struct strbuf *msg,
+			  const char *name,
+			  const char *other,
+			  struct diff_filespec *one,
+			  struct diff_filespec *two,
+			  struct diff_options *o,
+			  struct diff_filepair *p,
+			  int *must_show_header,
+			  int use_color);
+
+extern void emit_line(struct diff_options *o, const char *set, const char *reset,
+		      const char *line, int len);
+
+extern char *quote_two(const char *one, const char *two);
+
 #endif /* DIFF_H */
-- 
1.7.3.3.807.g6ee1f

^ permalink raw reply related

* [PATCH v6.1 8/8] log -L: implement move/copy detection (-M/-C)
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Bo Yang
In-Reply-To: <cover.1292366984.git.trast@student.ethz.ch>

From: Bo Yang <struggleyb.nku@gmail.com>

The basic idea is:

* Keep a list of "candidate snippets".  Start out empty.

* Go through all candidates (determined by the level of detection
  chosen) and diff them against the target file.

  - For each common part in the diff, put it in the "candidate
    snippets" if it's "worth it".  (Notably there is no point in
    adding a snippet that is fully contained in another.)

* Score the snippets.  Lines with alphanumeric characters count more.

* Filter out snippets with low score.  Where there are overlaps,
  favour higher scores.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 line.c                          |  556 ++++++++++++++++++++++++++++++++++++++-
 t/t4303-log-line-move-detect.sh |  238 +++++++++++++++++
 t/t4304-log-line-copy-detect.sh |  220 +++++++++++++++
 3 files changed, 1001 insertions(+), 13 deletions(-)
 create mode 100755 t/t4303-log-line-move-detect.sh
 create mode 100755 t/t4304-log-line-copy-detect.sh

diff --git a/line.c b/line.c
index 742c17f..ae176da 100644
--- a/line.c
+++ b/line.c
@@ -17,6 +17,7 @@ struct print_range {
 	int start, end;		/* Line range of post-image */
 	int pstart, pend;	/* Line range of pre-image */
 	int line_added : 1;	/* whether this range is added */
+	int copied : 1;
 };
 
 struct print_pair {
@@ -30,6 +31,7 @@ struct line_range {
 	long pstart, pend;	/* The corresponding range of parent commit */
 	struct print_pair pair;
 			/* The changed lines inside this range */
+	int copy_score;
 	unsigned int diff:1;
 };
 
@@ -66,6 +68,7 @@ static inline void print_range_init(struct print_range *r)
 	r->start = r->end = 0;
 	r->pstart = r->pend = 0;
 	r->line_added = 0;
+	r->copied = 0;
 }
 
 static inline void print_pair_init(struct print_pair *p)
@@ -104,6 +107,7 @@ static inline void line_range_clear(struct line_range *r)
 	r->start = r->end = 0;
 	r->pstart = r->pend = 0;
 	print_pair_clear(&r->pair);
+	r->copy_score = 0;
 	r->diff = 0;
 }
 
@@ -594,7 +598,7 @@ static void add_line_range(struct rev_info *revs, struct commit *commit,
 	ret = lookup_decoration(&revs->line_range, &commit->object);
 	if (ret && r)
 		diff_line_range_merge(ret, r);
-	else
+	else if (r != NULL)
 		add_decoration(&revs->line_range, &commit->object, r);
 
 	if (r)
@@ -620,6 +624,16 @@ static void clear_commit_line_range(struct rev_info *revs, struct commit *commit
 	return ret;
 }
 
+struct diff_line_range *delete_line_range(struct rev_info *revs, struct commit *commit)
+{
+	struct diff_line_range *ret = NULL;
+
+	ret = lookup_decoration(&revs->line_range, &commit->object);
+	add_decoration(&revs->line_range, &commit->object, NULL);
+
+	return ret;
+}
+
 void line_log_init(struct rev_info *rev, struct diff_line_range *r)
 {
 	struct commit *commit = NULL;
@@ -647,6 +661,515 @@ struct take_range_cb_data {
 		 * commit and its parent */
 };
 
+struct map {
+	long start, end;
+	long pstart, pend;
+	struct diff_filespec *spec;
+	int score;
+};
+
+struct mac_cb_data {
+	long plno, tlno;
+	int nr;
+	int alloc;
+	struct map *maps;
+	struct diff_filespec *spec;
+};
+
+struct mac_state {
+	int nr;
+	int alloc;
+	struct map *maps;
+};
+
+static void mac_state_init(struct mac_state *state)
+{
+	state->nr = state->alloc = 0;
+	state->maps = NULL;
+}
+
+static void mac_cb(void *data, long same, long p_next, long t_next)
+{
+	struct mac_cb_data *d = data;
+	long p_start = d->plno + 1, t_start = d->tlno + 1;
+	long p_end = p_start + same - t_start, t_end = same;
+
+	if (t_end >= t_start) {
+		ALLOC_GROW(d->maps, (d->nr + 1), d->alloc);
+		d->maps[d->nr].start = t_start;
+		d->maps[d->nr].end = t_end;
+		d->maps[d->nr].pstart = p_start;
+		d->maps[d->nr].pend = p_end;
+		d->maps[d->nr].spec = d->spec;
+		d->nr++;
+	}
+
+	d->plno = p_next;
+	d->tlno = t_next;
+}
+
+static void mac_state_insert(struct mac_state *state, long s_start, long s_end,
+		long start, long end, struct diff_filespec *spec)
+{
+	int i = 0;
+	struct map *maps;
+
+	for (; i < state->nr; i++) {
+		if (state->maps[i].start > end)
+			break;
+	}
+
+	state->nr++;
+	ALLOC_GROW(state->maps, state->nr, state->alloc);
+	maps = state->maps;
+	memmove(maps + i + 1, maps + i, (state->nr - i - 1) * sizeof(*maps));
+	maps[i].start = s_start;
+	maps[i].end = s_end;
+	maps[i].pstart = start;
+	maps[i].pend = end;
+	maps[i].spec = spec;
+	spec->count++;
+}
+
+static void mac_state_remove(struct mac_state *state, long s_start, long s_end,
+		long start, long end)
+{
+	int i = 0;
+	struct map *maps = state->maps;
+
+	while (i < state->nr) {
+		if (maps[i].start < start && maps[i].end > start) {
+			maps[i].end = s_start - 1;
+			maps[i].pend -= start - s_start;
+		}
+		if (maps[i].start < end && maps[i].end > end) {
+			maps[i].start = s_end + 1;
+			maps[i].pstart += end - s_end;
+		}
+		if (maps[i].start >=start && maps[i].end <= end) {
+			memmove(maps + i, maps + i + 1, (state->nr - i - 1) * sizeof(*maps));
+			state->nr--;
+			i--;
+		}
+
+		i++;
+	}
+}
+
+/*
+ * Generally, the 'struct line_range's pstart/pend is used to store
+ * the pre-image of current range. But here, we use it to store the
+ * line range of the 'artificial' memory file.
+ */
+static struct mac_state *merge_mac(struct mac_cb_data *data,
+		struct mac_state *state, long lines)
+{
+	struct map *maps = data->maps;
+	int i = 0;
+
+	assert(state);
+
+	while (i < data->nr) {
+		int j = 0;
+		long start = maps[i].start;
+		long end = maps[i].end;
+		int should_insert = 0;
+		long should_start = start;
+		long should_end = end;
+		long may_start = start, may_end = end;
+		long start_range_len = 0, end_range_len = 0;
+
+		/* first round: finds whether this range should be inserted */
+		if (!state->nr)
+			should_insert = 1;
+		while (state && j < state->nr) {
+			if (state->maps[j].end <= start) {
+				if (j+1 == state->nr)
+					should_insert = 1;
+				j++;
+				continue;
+			}
+			if (state->maps[j].start > end)
+				should_insert = 1;
+			else {
+				if (state->maps[j].start <= start) {
+					if (state->maps[j].end >= end)
+						should_insert = 0;
+					else {
+						should_insert = 1;
+						may_start = state->maps[j].end + 1;
+						start_range_len = state->maps[j].end -
+							state->maps[j].start + 1;
+					}
+				} else {
+					if (state->maps[j].end > end) {
+						should_insert = 1;
+						may_end = state->maps[j].start - 1;
+						end_range_len = state->maps[j].end -
+							state->maps[j].start + 1;
+					} else
+						should_insert = 1;
+				}
+			}
+
+			j++;
+		}
+
+		/* second round: insert the new range and adjust the current one */
+		if (should_insert) {
+			/* We always keep the longest range in prior */
+			if (start_range_len > end_range_len) {
+				if (start_range_len > should_end - should_start + 1)
+					should_start = may_start;
+				if (end_range_len > should_end - should_start + 1)
+					should_end = may_end;
+			} else {
+				if (end_range_len > should_end - should_start + 1)
+					should_end = may_end;
+				if (start_range_len > should_end - should_start + 1)
+					should_start = may_start;
+			}
+
+			mac_state_remove(state, should_start, should_end,
+					maps[i].start, maps[i].end);
+
+			start = maps[i].pstart + should_start - maps[i].start;
+			end = maps[i].pend - (maps[i].end - should_end);
+			mac_state_insert(state, should_start, should_end, start, end, data->spec);
+		}
+
+		i++;
+	}
+
+	return state;
+}
+
+static struct mac_state *find_mac_in_file(mmfile_t *file_p, mmfile_t *file_t,
+		long lines, unsigned char *scores, struct diff_filespec *spec,
+		struct mac_state *state)
+{
+	xpparam_t xpp;
+	xdemitconf_t xecfg;
+	struct mac_cb_data cb = {0, 0, 0, 0, NULL, spec};
+
+	memset(&xpp, 0, sizeof(xpp));
+	memset(&xecfg, 0, sizeof(xecfg));
+	xecfg.ctxlen = xecfg.interhunkctxlen = 0;
+
+	xdi_diff_hunks(file_p, file_t, mac_cb, &cb, &xpp, &xecfg);
+
+	if (cb.tlno < lines) {
+		ALLOC_GROW(cb.maps, (cb.nr + 1), cb.alloc);
+		cb.maps[cb.nr].start = cb.tlno + 1;
+		cb.maps[cb.nr].end = lines;
+		cb.maps[cb.nr].pstart = cb.plno + 1;
+		cb.maps[cb.nr].pend = cb.plno + lines - cb.tlno;
+		cb.maps[cb.nr].spec = spec;
+		cb.nr++;
+	}
+
+	if (cb.nr)
+		state = merge_mac(&cb, state, lines);
+	free(cb.maps);
+
+	return state;
+}
+
+#define LINE_SCORE 6
+#define TRIVIAL_SCORE 2
+static void setup_mac_file(struct diff_filespec *spec, mmfile_t *lines,
+		unsigned char *scores, long start, long end)
+{
+	int i = 0;
+	int line = 1;
+	int size = 0;
+	char *data = spec->data;
+
+	memset(scores, TRIVIAL_SCORE, end - start + 1);
+	while (i < spec->size) {
+		int index = line - start;
+		if (line < start) {
+			if (data[i] == '\n')
+				line++;
+			i++;
+			continue;
+		}
+		if (line > end)
+			break;
+		if (size == 0)
+			lines->ptr = data + i;
+		size++;
+		if (scores[index] == TRIVIAL_SCORE && data[i] <= 'z' && data[i] >= 'a')
+			scores[index] = LINE_SCORE;
+		if (scores[index] == TRIVIAL_SCORE && data[i] <= 'Z' && data[i] >= 'A')
+			scores[index] = LINE_SCORE;
+		if (scores[index] == TRIVIAL_SCORE && data[i] <= '9' && data[i] >= '0')
+			scores[index] = LINE_SCORE;
+		if (data[i] == '\n')
+			line++;
+
+		i++;
+	}
+
+	lines->size = size;
+}
+
+static void mac_state_cal_score(long lines, unsigned char *scores, struct mac_state *state)
+{
+	struct map *maps = state->maps;
+	int i = 0;
+
+	while (i < state->nr) {
+		int score = 0;
+		int j = maps[i].start;
+		while (j <= maps[i].end) {
+			assert(j <= lines);
+			score += scores[j - 1];
+			j++;
+		}
+
+		maps[i].score = score;
+		i++;
+	}
+}
+
+#define MIN_GAP 3
+#define RANGE_MIN_SCORE 20
+static struct diff_line_range *mac_combine_remove(struct diff_line_range *range)
+{
+	struct diff_line_range *r = range, *prev = range, *ret = range;
+
+	// Firstly to combine adjacent-enough range
+	while (r) {
+		int i = 0;
+		struct line_range *rs = r->ranges;
+		for (; i < r->nr; i++) {
+			if (i + 1 < r->nr &&
+				(rs[i + 1].end - rs[i].start < MIN_GAP)) {
+				rs[i].end = rs[i + 1].end;
+				rs[i].copy_score += rs[i + 1].copy_score;
+				memmove(rs + i + 1, rs + i + 2, (r->nr - i - 1) * sizeof(*rs));
+				r->nr--;
+				i--;
+			}
+		}
+		r = r->next;
+	}
+
+	// then delete the trivial ones
+	r = range;
+	while (r) {
+		int i = 0;
+		struct line_range *rs = r->ranges;
+		for (; i < r->nr; i++) {
+			if (rs[i].copy_score < RANGE_MIN_SCORE) {
+				memmove(rs + i, rs + i + 1, (r->nr - i) * sizeof(*rs));
+				r->nr--;
+				i--;
+			}
+		}
+		r = r->next;
+	}
+
+	// then delete the empty diff_line_range
+	r = range;
+	while (r) {
+		struct diff_line_range *next = r->next;
+		if (!r->nr) {
+			diff_line_range_clear(r);
+			free(r);
+			if (ret == r) {
+				ret = next;
+				prev = next;
+			}
+		} else if (prev != r) {
+				prev->next = r;
+				prev = r;
+		}
+
+		r = next;
+	}
+
+	return ret;
+}
+
+static struct diff_line_range *mac_state_to_line_range(struct mac_state *state)
+{
+	struct diff_line_range *ret = NULL;
+	struct diff_line_range *prev = NULL;
+	struct map *maps = state->maps;
+	int i = 0;
+	struct line_range *rg = NULL;
+
+	while (i < state->nr) {
+		struct diff_line_range *r = ret;
+		while (r) {
+			if (r->spec == maps[i].spec) {
+				rg = diff_line_range_insert(r, NULL, maps[i].pstart,
+						maps[i].pend);
+				rg->copy_score += maps[i].score;
+				break;
+			}
+			r = r->next;
+		}
+
+		if (!r) {
+			r = xmalloc(sizeof(*r));
+			diff_line_range_init(r);
+			r->spec = maps[i].spec;
+			rg = diff_line_range_insert(r, NULL, maps[i].pstart, maps[i].pend);
+			rg->copy_score += maps[i].score;
+
+			if (!ret) {
+				ret = r;
+				prev = r;
+			} else {
+				prev->next = r;
+				prev = r;
+			}
+		}
+
+		i++;
+	}
+
+	return mac_combine_remove(ret);
+}
+
+struct mac_state *find_mac_in_one_file(struct commit *p,
+		char *path, mmfile_t *file_t, long lines,
+		unsigned char *scores)
+{
+	struct diff_filespec *spec = alloc_filespec(path);
+	unsigned char sha1[20];
+	unsigned mode;
+	mmfile_t file_p;
+	struct mac_state *ret = xmalloc(sizeof(*ret));
+
+	mac_state_init(ret);
+	if (get_tree_entry(p->object.sha1, path, sha1, &mode))
+		return NULL;
+	fill_filespec(spec, sha1, mode);
+	diff_populate_filespec(spec, 0);
+	file_p.ptr = spec->data;
+	file_p.size = spec->size;
+
+	ret = find_mac_in_file(&file_p, file_t, lines, scores, spec, ret);
+	mac_state_cal_score(lines, scores, ret);
+
+	return ret;
+}
+
+struct mac_state *find_mac_in_all_file(struct rev_info *rev, struct commit *c,
+		struct commit *p, mmfile_t *file_t, long lines,
+		unsigned char *scores)
+{
+	struct diff_options diff_opts;
+	const char *paths[1];
+	int j = 0;
+	struct mac_state *state = xmalloc(sizeof(*state));
+
+	mac_state_init(state);
+	/* ok, we can start to do the move/copy detect now */
+	diff_setup(&diff_opts);
+	DIFF_OPT_SET(&diff_opts, RECURSIVE);
+	diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+	paths[0] = NULL;
+	diff_tree_setup_paths(paths, &diff_opts);
+	if (diff_setup_done(&diff_opts) < 0)
+		die("diff-setup in line.c");
+	if (DIFF_OPT_TST(&rev->diffopt, FIND_COPIES_HARDER))
+		DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
+
+	diff_tree_sha1(p->tree->object.sha1, c->tree->object.sha1,
+			"", &diff_opts);
+	for (j = 0; j < diff_queued_diff.nr; j++) {
+		struct diff_filepair *p = diff_queued_diff.queue[j];
+		mmfile_t file_p;
+
+		if (!DIFF_FILE_VALID(p->one))
+			continue;
+		if (S_ISGITLINK(p->one->mode))
+			continue;
+		diff_populate_filespec(p->one, 0);
+		file_p.ptr = p->one->data;
+		file_p.size = p->one->size;
+
+		p->one->count++;
+		state = find_mac_in_file(&file_p, file_t, lines, scores, p->one, state);
+	}
+
+	diff_flush(&diff_opts);
+	diff_tree_release_paths(&diff_opts);
+
+	mac_state_cal_score(lines, scores, state);
+
+	return state;
+}
+
+static void find_mac_for_range(struct rev_info *rev,
+		struct commit *c, struct commit *p,
+		struct diff_line_range *r, struct print_range *pr)
+{
+	unsigned char *scores;
+	struct mac_state *state;
+	mmfile_t lines = {NULL, 0};
+	struct diff_line_range *copied = NULL;
+
+	/* Do not search for source of ranges shorter than 3 lines */
+	if (pr->line_added && (pr->end - pr->start) < 3)
+		return;
+
+	scores = xmalloc(pr->end - pr->start + 1);
+	setup_mac_file(r->spec, &lines, scores, pr->start, pr->end);
+
+	if (rev->diffopt.detect_rename == DIFF_DETECT_RENAME)
+		state = find_mac_in_one_file(p, r->spec->path, &lines,
+					     pr->end - pr->start + 1, scores);
+	else
+		state = find_mac_in_all_file(rev, c, p, &lines,
+					     pr->end - pr->start + 1, scores);
+
+	copied = mac_state_to_line_range(state);
+	if (copied) {
+		add_line_range(rev, p, copied);
+		pr->copied = 1;
+	}
+	free(scores);
+}
+
+/*
+ * Find the code move/copy, here we reuse the '-M/-C' options in diff options.
+ * -M means that finds the code in the same file;
+ * -C means that finds the code in all the files in parent commit.
+ */
+static void find_mac(struct rev_info *rev, struct commit *c)
+{
+	struct diff_line_range *r = lookup_line_range(rev, c);
+	struct diff_line_range *prange = NULL;
+	struct commit *p = NULL;
+
+	if (c->parents == NULL)
+		return;
+
+	assert(c->parents->next == NULL);
+	p = c->parents->item;
+	parse_commit(p);
+
+	while (r) {
+		int n;
+		for (n = 0; n < r->nr; n++) {
+			struct print_pair *pair = &r->ranges[n].pair;
+			int i;
+			for (i = 0; i < pair->nr; i++)
+				find_mac_for_range(rev, c, p, r, &pair->ranges[i]);
+		}
+
+		r = r->next;
+	}
+
+	add_line_range(rev, p, prange);
+}
+
 #define SCALE_FACTOR 4
 /*
  * [p_start, p_end] represents the pre-image of current diff hunk,
@@ -942,7 +1465,7 @@ static int assign_range_to_parent(struct rev_info *rev, struct commit *commit,
 	void *tree1 = NULL, *tree2 = NULL;
 	struct tree_desc desc1, desc2;
 	struct diff_queue_struct *queue;
-	struct take_range_cb_data cb_data = {NULL, cur_range, 0, 0};
+	struct take_range_cb_data cb_data = {NULL, cur_range, 0, 0, 0};
 	xpparam_t xpp;
 	xdemitconf_t xecfg;
 	int i, diff = 0;
@@ -1083,15 +1606,6 @@ static int assign_range_to_parent(struct rev_info *rev, struct commit *commit,
 		assert(parent);
 		assert(final_range->spec);
 		add_line_range(rev, parent, final_range);
-	} else {
-		/*
-		 * If there is no new ranges assigned to the parent,
-		 * we should mark it as a 'root' commit.
-		 */
-		if (commit->parents && !commit->parents->next) {
-			free(commit->parents);
-			commit->parents = NULL;
-		}
 	}
 
 	/* and the ranges of current commit is updated */
@@ -1120,6 +1634,18 @@ static void diff_update_parent_range(struct rev_info *rev,
 	}
 
 	assign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);
+
+	if (rev->diffopt.detect_rename > 0)
+		find_mac(rev, commit);
+
+	/*
+	 * If there is no new ranges assigned to the parent,
+	 * we should mark it as a 'root' commit.
+	 */
+	if (c != NULL && lookup_line_range(rev, c) == NULL) {
+		free(commit->parents);
+		commit->parents = NULL;
+	}
 }
 
 struct commit_state {
@@ -1306,8 +1832,12 @@ static void diff_flush_range(struct diff_options *opt, struct line_chunk *chunk,
 		if (!pr->line_added)
 			flush_lines(opt, &chunk->one, chunk->one_end,
 				pr->pstart, pr->pend, &chunk->lone, old, '-');
-		flush_lines(opt, &chunk->two, chunk->two_end,
-			pr->start, pr->end, &chunk->ltwo, new, '+');
+		if (pr->copied)
+			flush_lines(opt, &chunk->two, chunk->two_end,
+				pr->start, pr->end, &chunk->ltwo, new, ' ');
+		else
+			flush_lines(opt, &chunk->two, chunk->two_end,
+				pr->start, pr->end, &chunk->ltwo, new, '+');
 
 		cur = pr->end + 1;
 	}
diff --git a/t/t4303-log-line-move-detect.sh b/t/t4303-log-line-move-detect.sh
new file mode 100755
index 0000000..0462ec0
--- /dev/null
+++ b/t/t4303-log-line-move-detect.sh
@@ -0,0 +1,238 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test git log -L with code movement'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+}
+
+void output()
+{
+	printf("hello world");
+}
+EOF
+
+test_expect_success 'add path0 and commit.' '
+	git add path0 &&
+	git commit -m "Base commit"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+}
+
+void output()
+{
+	int d = 3;
+	int e = 5;
+	printf("hello world");
+	printf("bye!");
+}
+EOF
+
+test_expect_success 'Change the some lines of path0.' '
+	git add path0 &&
+	git commit -m "Change some lines of path0"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+}
+
+void output()
+{
+	int d = 3;
+	int e = 5;
+	printf("hello world");
+	printf("bye!");
+}
+
+void comb()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+	int d = 3;
+	int e = 5;
+	printf("hello world");
+	printf("bye!");
+}
+EOF
+
+test_expect_success 'Move two functions into one' '
+	git add path0 &&
+	git commit -m "Move two functions into one"
+'
+
+cat >path0 <<\EOF
+void comb()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+	printf("hello world");
+	printf("bye!");
+}
+EOF
+
+test_expect_success 'Final change of path0.' '
+	git add path0 &&
+	git commit -m "Final change of path0"
+'
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-no-M <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index 495f978..b744a93 100644
+| --- a/path0
+| +++ b/path0
+| @@ -17,11 +1,9 @@
+|  void comb()
+|  {
+|QQ	int a = 0;
+|QQ	int b = 1;
+|QQ	int c;
+|QQ	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|QQ	printf("hello world");
+|QQ	printf("bye!");
+|  }
+|  #
+* Move two functions into one
+  #
+  diff --git a/path0 b/path0
+  index cd42622..495f978 100644
+  --- a/path0
+  +++ b/path0
+  @@ -0,0 +17,11 @@
+  +void comb()
+  +{
+  +	int a = 0;
+  +	int b = 1;
+  +	int c;
+  +	c = a + b;
+  +	int d = 3;
+  +	int e = 5;
+  +	printf("hello world");
+  +	printf("bye!");
+  +}
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-M <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index 495f978..b744a93 100644
+| --- a/path0
+| +++ b/path0
+| @@ -17,11 +1,9 @@
+|  void comb()
+|  {
+|QQ	int a = 0;
+|QQ	int b = 1;
+|QQ	int c;
+|QQ	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|QQ	printf("hello world");
+|QQ	printf("bye!");
+|  }
+|  #
+* Move two functions into one
+| #
+| diff --git a/path0 b/path0
+| index cd42622..495f978 100644
+| --- a/path0
+| +++ b/path0
+| @@ -0,0 +17,11 @@
+|  void comb()
+|  {
+|QQ	int a = 0;
+|QQ	int b = 1;
+|QQ	int c;
+|QQ	c = a + b;
+|QQ	int d = 3;
+|QQ	int e = 5;
+|QQ	printf("hello world");
+|QQ	printf("bye!");
+|  }
+|  #
+* Change some lines of path0
+| #
+| diff --git a/path0 b/path0
+| index f5e09df..cd42622 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,5 +2,5 @@
+|  {
+|QQ	int a = 0;
+|QQ	int b = 1;
+|QQ	int c;
+|QQ	c = a + b;
+| @@ -11,2 +11,5 @@
+| +	int d = 3;
+| +	int e = 5;
+|QQ	printf("hello world");
+| +	printf("bye!");
+|  }
+|  #
+* Base commit
+  #
+  diff --git a/path0 b/path0
+  new file mode 100644
+  index 0000000..f5e09df
+  --- /dev/null
+  +++ b/path0
+  @@ -0,0 +2,5 @@
+  +{
+  +	int a = 0;
+  +	int b = 1;
+  +	int c;
+  +	c = a + b;
+  @@ -0,0 +11,2 @@
+  +	printf("hello world");
+  +}
+EOF
+
+test_expect_success 'Show the line level log of path0' '
+	git log --pretty=format:%s%n%b --graph -L /comb/,/^}/:path0 > current-no-M
+'
+
+test_expect_success 'validate the path0 output.' '
+	test_cmp current-no-M expected-no-M
+'
+
+test_expect_success 'Show the line level log of path0 with -M' '
+	git log --pretty=format:%s%n%b --graph -M -L /comb/,/^}/:path0 > current-M
+'
+
+test_expect_success 'validate the path1 output.' '
+	test_cmp current-M expected-M
+'
+
+test_done
diff --git a/t/t4304-log-line-copy-detect.sh b/t/t4304-log-line-copy-detect.sh
new file mode 100755
index 0000000..de0e004
--- /dev/null
+++ b/t/t4304-log-line-copy-detect.sh
@@ -0,0 +1,220 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test git log -L with -C'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+}
+EOF
+
+cat >path1 <<\EOF
+void output()
+{
+	printf("hello world");
+}
+EOF
+
+test_expect_success 'add path0/path1 and commit.' '
+	git add path0 path1 &&
+	git commit -m "Base commit"
+'
+
+cat >path1 <<\EOF
+void output()
+{
+	int d = 3;
+	int e = 5;
+	printf("hello world");
+	printf("bye!");
+}
+EOF
+
+test_expect_success 'Change the some lines of path1.' '
+	git add path1 &&
+	git commit -m "Change some lines of path1"
+'
+
+cat >path2 <<\EOF
+void comb()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+	int d = 3;
+	int e = 5;
+	printf("hello world");
+	printf("bye!");
+}
+EOF
+
+test_expect_success 'Move two functions into one in path2' '
+	git add path2 &&
+	git rm path0 path1 &&
+	git commit -m "Move two functions into path2"
+'
+
+cat >path2 <<\EOF
+void comb()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+	printf("hello world");
+	printf("bye!");
+}
+EOF
+
+test_expect_success 'Final change of path2.' '
+	git add path2 &&
+	git commit -m "Final change of path2"
+'
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-no-C <<\EOF
+* Final change of path2
+| #
+| diff --git a/path2 b/path2
+| index ca6a800..b744a93 100644
+| --- a/path2
+| +++ b/path2
+| @@ -1,11 +1,9 @@
+|  void comb()
+|  {
+|QQ	int a = 0;
+|QQ	int b = 1;
+|QQ	int c;
+|QQ	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|QQ	printf("hello world");
+|QQ	printf("bye!");
+|  }
+|  #
+* Move two functions into path2
+  #
+  diff --git a/path2 b/path2
+  new file mode 100644
+  index 0000000..ca6a800
+  --- /dev/null
+  +++ b/path2
+  @@ -0,0 +1,11 @@
+  +void comb()
+  +{
+  +	int a = 0;
+  +	int b = 1;
+  +	int c;
+  +	c = a + b;
+  +	int d = 3;
+  +	int e = 5;
+  +	printf("hello world");
+  +	printf("bye!");
+  +}
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-C <<\EOF
+* Final change of path2
+| #
+| diff --git a/path2 b/path2
+| index ca6a800..b744a93 100644
+| --- a/path2
+| +++ b/path2
+| @@ -1,11 +1,9 @@
+|  void comb()
+|  {
+|QQ	int a = 0;
+|QQ	int b = 1;
+|QQ	int c;
+|QQ	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|QQ	printf("hello world");
+|QQ	printf("bye!");
+|  }
+|  #
+* Move two functions into path2
+| #
+| diff --git a/path2 b/path2
+| new file mode 100644
+| index 0000000..ca6a800
+| --- /dev/null
+| +++ b/path2
+| @@ -0,0 +1,11 @@
+|  void comb()
+|  {
+|QQ	int a = 0;
+|QQ	int b = 1;
+|QQ	int c;
+|QQ	c = a + b;
+|QQ	int d = 3;
+|QQ	int e = 5;
+|QQ	printf("hello world");
+|QQ	printf("bye!");
+|  }
+|  #
+* Change some lines of path1
+| #
+| diff --git a/path1 b/path1
+| index 52be2a5..bf3a80f 100644
+| --- a/path1
+| +++ b/path1
+| @@ -2,3 +2,6 @@
+|  {
+| +	int d = 3;
+| +	int e = 5;
+|QQ	printf("hello world");
+| +	printf("bye!");
+|  }
+|  #
+* Base commit
+  #
+  diff --git a/path1 b/path1
+  new file mode 100644
+  index 0000000..52be2a5
+  --- /dev/null
+  +++ b/path1
+  @@ -0,0 +2,3 @@
+  +{
+  +	printf("hello world");
+  +}
+  diff --git a/path0 b/path0
+  new file mode 100644
+  index 0000000..fb33939
+  --- /dev/null
+  +++ b/path0
+  @@ -0,0 +2,5 @@
+  +{
+  +	int a = 0;
+  +	int b = 1;
+  +	int c;
+  +	c = a + b;
+EOF
+
+test_expect_success 'Show the line level log of path2' '
+	git log --pretty=format:%s%n%b --graph -L /comb/,/^}/:path2 > current-no-C
+'
+
+test_expect_success 'validate the path2 output.' '
+	test_cmp current-no-C expected-no-C
+'
+
+test_expect_success 'Show the line level log of path2 with -C' '
+	git log --pretty=format:%s%n%b --graph -C -L /comb/,/^}/:path2 > current-C
+'
+
+test_expect_success 'validate the path2 output.' '
+	test_cmp current-C expected-C
+'
+
+test_done
-- 
1.7.3.3.807.g6ee1f

^ permalink raw reply related

* [PATCH v6.1 1/8] Refactor parse_loc
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Bo Yang
In-Reply-To: <cover.1292366984.git.trast@student.ethz.ch>

From: Bo Yang <struggleyb.nku@gmail.com>

We want to use the same style of -L n,m argument for 'git log -L' as
for git-blame.  Refactor the argument parsing of the range arguments
from builtin/blame.c to the (new) file that will hold the 'git log -L'
logic.

To accommodate different data structures in blame and log -L, the file
contents are abstracted away; parse_range_arg takes a callback that it
uses to get the contents of a line of the (notional) file.

The new test is for a case that made me pause during debugging: the
'blame -L with invalid end' test was the only one that noticed an
outright failure to parse the end *at all*.  So make a more explicit
test for that.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Makefile                      |    2 +
 builtin/blame.c               |   99 +++-----------------------------------
 line.c                        |  106 +++++++++++++++++++++++++++++++++++++++++
 line.h                        |   23 +++++++++
 t/t8003-blame-corner-cases.sh |    6 ++
 5 files changed, 145 insertions(+), 91 deletions(-)
 create mode 100644 line.c
 create mode 100644 line.h

diff --git a/Makefile b/Makefile
index 57d9c65..b015c61 100644
--- a/Makefile
+++ b/Makefile
@@ -519,6 +519,7 @@ LIB_H += grep.h
 LIB_H += hash.h
 LIB_H += help.h
 LIB_H += levenshtein.h
+LIB_H += line.h
 LIB_H += list-objects.h
 LIB_H += ll-merge.h
 LIB_H += log-tree.h
@@ -606,6 +607,7 @@ LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
 LIB_OBJS += levenshtein.o
+LIB_OBJS += line.o
 LIB_OBJS += list-objects.o
 LIB_OBJS += ll-merge.o
 LIB_OBJS += lockfile.o
diff --git a/builtin/blame.c b/builtin/blame.c
index aa30ec5..5eeddcb 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -21,6 +21,7 @@
 #include "parse-options.h"
 #include "utf8.h"
 #include "userdiff.h"
+#include "line.h"
 
 static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
 
@@ -551,11 +552,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
 	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
@@ -1925,83 +1931,6 @@ static unsigned parse_score(const char *arg)
 }
 
 /*
- * 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
  */
 static void prepare_blame_range(struct scoreboard *sb,
@@ -2009,15 +1938,7 @@ static void prepare_blame_range(struct scoreboard *sb,
 				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))
 		usage(blame_usage);
 }
 
@@ -2504,10 +2425,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 	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 --git a/line.c b/line.c
new file mode 100644
index 0000000..778cd7b
--- /dev/null
+++ b/line.c
@@ -0,0 +1,106 @@
+#include "git-compat-util.h"
+#include "line.h"
+
+/*
+ * Parse one item in the -L option
+ */
+const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
+		void *data, long lines, long begin, long *ret)
+{
+	char *term;
+	const char *line;
+	long num;
+	int reg_error;
+	regex_t regexp;
+	regmatch_t match[1];
+
+	/* Catch the '$' matcher, now it is used to match the last
+	 * line of the file. */
+	if (spec[0] == '$') {
+		*ret = lines;
+		return spec + 1;
+	}
+
+	/* Allow "-L <something>,+20" to mean starting at <something>
+	 * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+	 * <something>.
+	 */
+	if (begin != -1 && (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;
+	if (begin == -1)
+		begin = 1;
+	begin--; /* input is in human terms */
+	line = nth_line(data, 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++ < lines) {
+			nline = nth_line(data, 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);
+	}
+}
+
+int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
+		void *cb_data, long lines, long *begin, long *end)
+{
+	arg = parse_loc(arg, nth_line_cb, cb_data, lines, -1, begin);
+
+	if (*arg == ',') {
+		arg = parse_loc(arg+1, nth_line_cb, cb_data, lines, *begin+1, end);
+		if (*begin > *end) {
+			long tmp = *begin;
+			*begin = *end;
+			*end = tmp;
+		}
+	}
+
+	if (*arg)
+		return -1;
+
+	return 0;
+}
diff --git a/line.h b/line.h
new file mode 100644
index 0000000..5878c94
--- /dev/null
+++ b/line.h
@@ -0,0 +1,23 @@
+#ifndef LINE_H
+#define LINE_H
+
+/*
+ * Parse one item in an -L begin,end option w.r.t. the notional file
+ * object 'cb_data' consisting of 'lines' lines.
+ *
+ * The 'nth_line_cb' callback is used to determine the start of the
+ * line 'lno' inside the 'cb_data'.  The caller is expected to already
+ * have a suitable map at hand to make this a constant-time lookup.
+ *
+ * Returns 0 in case of success and -1 if there was an error.  The
+ * caller should print a usage message in the latter case.
+ */
+
+typedef const char *(*nth_line_fn_t)(void *data, long lno);
+
+extern int parse_range_arg(const char *arg,
+			   nth_line_fn_t nth_line_cb,
+			   void *cb_data, long lines,
+			   long *begin, long *end);
+
+#endif /* LINE_H */
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh
index 230143c..51d313e 100755
--- a/t/t8003-blame-corner-cases.sh
+++ b/t/t8003-blame-corner-cases.sh
@@ -175,6 +175,12 @@ test_expect_success 'blame -L with invalid end' '
 	grep "has only 2 lines" errors
 '
 
+test_expect_success 'blame -L parses end' '
+	git blame -L1,1 tres >out &&
+	cat out &&
+	test $(wc -l < out) -eq 1
+'
+
 test_expect_success 'indent of line numbers, nine lines' '
 	git blame nine_lines >actual &&
 	test $(grep -c "  " actual) = 0
-- 
1.7.3.3.807.g6ee1f

^ permalink raw reply related

* [PATCH v6.1 4/8] Implement line-history search (git log -L)
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Bo Yang
In-Reply-To: <cover.1292366984.git.trast@student.ethz.ch>

From: Bo Yang <struggleyb.nku@gmail.com>

'struct diff_line_range' is the main data structure to keep
track of the line ranges we are currently interested in. The
user starts digging from a line range, and after examining the
diff that affects that range by a commit, we can find a new
range that corresponds to this range. So, we will associate this
new range with the commit's parent commit.

There is one 'diff_line_range' for each file, and there are
multiple 'struct line_range' in each 'diff_line_range'. In this way,
we support multiple ranges.

Within 'struct line_range', there are multiple 'struct print_range'
which represent a diff hunk.

When going from a commit to its parents, we map the "interesting"
range of lines according to the change made.  For non-merge commits,
we just run map_range on the ranges, which works as follows:

1. Run diffcore_std to find out the pre/postimage for each file.
2. Run xdi_diff_hunks on each interesting set of pre/postimages.
3. The map_range_cb callback is invoked for each hunk by the diff
   engine, and we use it to calculate the pre-image range from the
   post-image range in the function map_lines.

For merge commits, we run map_range once for every parent.  After that
we use a take_range pass to eliminate all ranges that are identical.
If any ranges remain after that, then the merge is considered
non-trivial.

The algorithm that maps lines from post-image to pre-image is in the
function map_lines.

To correctly track the line ranges over several branches, we must make
sure that we have processed all children before reaching the commit
itself.  So we let the revision machinery topo-order the commits
before doing anything.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/blame-options.txt     |   19 +-
 Documentation/git-log.txt           |   18 +
 Documentation/line-range-format.txt |   18 +
 builtin/log.c                       |   73 ++-
 line.c                              | 1331 ++++++++++++++++++++++++++++++++++-
 line.h                              |   49 ++
 revision.c                          |    6 +
 revision.h                          |   11 +-
 t/t4301-log-line-single-history.sh  |  685 ++++++++++++++++++
 9 files changed, 2186 insertions(+), 24 deletions(-)
 create mode 100644 Documentation/line-range-format.txt
 create mode 100755 t/t4301-log-line-single-history.sh

diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 16e3c68..3526835 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -13,24 +13,7 @@
 	Annotate only the given line range.  <start> and <end> can take
 	one of these forms:
 
-	- number
-+
-If <start> or <end> is a number, it specifies an
-absolute line number (lines count from 1).
-+
-
-- /regex/
-+
-This form will use the first line matching the given
-POSIX regex.  If <end> is a regex, it will search
-starting at the line given by <start>.
-+
-
-- +offset or -offset
-+
-This is only valid for <end> and will specify a number
-of lines before or after the line given by <start>.
-+
+include::line-range-format.txt[]
 
 -l::
 	Show long rev (Default: off).
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index ff41784..7fcf6e7 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -66,6 +66,19 @@ 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>::
+	Trace the evolution of the line range given by "<start>,<end>"
+	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.
+
+<start> and <end> can take one of these forms:
+
+include::line-range-format.txt[]
+You can specify this option more than once.
+
+
 [\--] <path>...::
 	Show only commits that affect any of the specified paths. To
 	prevent confusion with options and branch names, paths may need
@@ -132,6 +145,11 @@ git log -p -m --first-parent::
 	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.
+
 
 Discussion
 ----------
diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
new file mode 100644
index 0000000..265bc23
--- /dev/null
+++ b/Documentation/line-range-format.txt
@@ -0,0 +1,18 @@
+- number
++
+If <start> or <end> is a number, it specifies an
+absolute line number (lines count from 1).
++
+
+- /regex/
++
+This form will use the first line matching the given
+POSIX regex.  If <end> is a regex, it will search
+starting at the line given by <start>.
++
+
+- +offset or -offset
++
+This is only valid for <end> and will specify a number
+of lines before or after the line given by <start>.
++
diff --git a/builtin/log.c b/builtin/log.c
index d8c6c28..342d4de 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -19,6 +19,7 @@
 #include "remote.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "line.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -28,10 +29,21 @@
 static const char *fmt_patch_subject_prefix = "PATCH";
 static const char *fmt_pretty;
 
-static const char * const builtin_log_usage =
+static char builtin_log_usage[] =
 	"git log [<options>] [<since>..<until>] [[--] <path>...]\n"
 	"   or: git show [options] <object>...";
 
+static const char *log_opt_usage[] = {
+	builtin_log_usage,
+	NULL
+};
+
+struct line_opt_callback_data {
+	struct rev_info *rev;
+	const char *prefix;
+	struct diff_line_range *ranges, *cur_range;
+};
+
 static int parse_decoration_style(const char *var, const char *value)
 {
 	switch (git_config_maybe_bool(var, value)) {
@@ -49,12 +61,53 @@ static int parse_decoration_style(const char *var, const char *value)
 	return -1;
 }
 
+static int log_line_range_callback(const struct option *option, const char *arg, int unset)
+{
+	struct line_opt_callback_data *data = option->value;
+	struct diff_line_range *r;
+	const char *name_start, *range_arg, *full_path;
+	const char *prefix = data->prefix;
+
+	if (!arg)
+		return -1;
+
+	name_start = skip_range_arg(arg);
+	if (!name_start || *name_start != ':')
+		die("-L argument '%s' not of the form start,end:file", arg);
+
+	range_arg = xstrndup(arg, name_start-arg);
+	name_start++;
+
+	full_path = prefix_path(prefix, prefix ? strlen(prefix) : 0,
+				name_start);
+
+	r = xmalloc(sizeof(struct diff_line_range));
+	diff_line_range_init(r);
+	if (data->cur_range)
+		data->cur_range->next = r;
+	else
+		data->ranges = r;
+	data->cur_range = r;
+	r->spec = alloc_filespec(full_path);
+	diff_line_range_append(r, range_arg);
+	data->rev->line_level_traverse = 1;
+	return 0;
+}
+
 static void cmd_log_init(int argc, const char **argv, const char *prefix,
 			 struct rev_info *rev, struct setup_revision_opt *opt)
 {
 	int i;
 	int decoration_given = 0;
 	struct userformat_want w;
+	static struct line_opt_callback_data line_cb = {0};
+
+	static const struct option options[] = {
+		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()
+	};
 
 	rev->abbrev = DEFAULT_ABBREV;
 	rev->commit_format = CMIT_FMT_DEFAULT;
@@ -75,6 +128,14 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 	 */
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(builtin_log_usage);
+
+	line_cb.rev = rev;
+	line_cb.prefix = prefix;
+
+	argc = parse_options(argc, argv, prefix, options, log_opt_usage,
+			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |
+			     PARSE_OPT_KEEP_UNKNOWN);
+
 	argc = setup_revisions(argc, argv, rev, opt);
 
 	memset(&w, 0, sizeof(w));
@@ -125,6 +186,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 		rev->show_decorations = 1;
 		load_ref_decorations(decoration_style);
 	}
+
+	/* Test whether line level history is asked for */
+	if (rev->line_level_traverse)
+		line_log_init(rev, line_cb.ranges);
+
 	setup_pager();
 }
 
@@ -509,7 +575,10 @@ int cmd_log(int argc, const char **argv, const char *prefix)
 	memset(&opt, 0, sizeof(opt));
 	opt.def = "HEAD";
 	cmd_log_init(argc, argv, prefix, &rev, &opt);
-	return cmd_log_walk(&rev);
+	if (rev.line_level_traverse)
+		return line_log_walk(&rev);
+	else
+		return cmd_log_walk(&rev);
 }
 
 /* format-patch */
diff --git a/line.c b/line.c
index 778cd7b..1ffcaba 100644
--- a/line.c
+++ b/line.c
@@ -1,6 +1,124 @@
 #include "git-compat-util.h"
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "diff.h"
+#include "commit.h"
+#include "decorate.h"
+#include "revision.h"
+#include "xdiff-interface.h"
+#include "strbuf.h"
+#include "log-tree.h"
 #include "line.h"
 
+struct print_range {
+	int start, end;		/* Line range of post-image */
+	int pstart, pend;	/* Line range of pre-image */
+	int line_added : 1;	/* whether this range is added */
+};
+
+struct print_pair {
+	int alloc, nr;
+	struct print_range *ranges;
+};
+
+struct line_range {
+	const char *arg;	/* The argument to specify this line range */
+	long start, end;	/* The interesting line range of current commit */
+	long pstart, pend;	/* The corresponding range of parent commit */
+	struct print_pair pair;
+			/* The changed lines inside this range */
+	unsigned int diff:1;
+};
+
+/*
+ * Eek-a-global
+ */
+
+static int limited;
+
+/*
+ * These could be in line.h but we put them here so the functions can
+ * be static.
+ */
+
+static struct line_range *diff_line_range_insert(struct diff_line_range *r,
+	       const char *arg, int start, int end);
+
+static void diff_line_range_clear(struct diff_line_range *r);
+
+static struct diff_line_range *diff_line_range_merge(
+	       struct diff_line_range *out,
+	       struct diff_line_range *other);
+
+static struct diff_line_range *diff_line_range_clone(struct diff_line_range *r);
+
+static struct diff_line_range *diff_line_range_clone_deeply(struct diff_line_range *r);
+
+static void add_line_range(struct rev_info *revs, struct commit *commit,
+	       struct diff_line_range *r);
+
+static struct diff_line_range *lookup_line_range(struct rev_info *revs,
+	       struct commit *commit);
+
+/*
+ * Data structure helpers
+ */
+
+static inline void print_range_init(struct print_range *r)
+{
+	r->start = r->end = 0;
+	r->pstart = r->pend = 0;
+	r->line_added = 0;
+}
+
+static inline void print_pair_init(struct print_pair *p)
+{
+	p->alloc = p->nr = 0;
+	p->ranges = NULL;
+}
+
+static inline void print_pair_grow(struct print_pair *p)
+{
+	p->nr++;
+	ALLOC_GROW(p->ranges, p->nr, p->alloc);
+}
+
+static inline void print_pair_clear(struct print_pair *p)
+{
+	p->alloc = p->nr = 0;
+	if (p->ranges)
+		free(p->ranges);
+	p->ranges = NULL;
+}
+
+static inline void line_range_init(struct line_range *r)
+{
+	r->arg = NULL;
+	r->start = r->end = 0;
+	r->pstart = r->pend = 0;
+	print_pair_init(&(r->pair));
+	r->copy_score = 0;
+	r->diff = 0;
+}
+
+static inline void line_range_clear(struct line_range *r)
+{
+	r->arg = NULL;
+	r->start = r->end = 0;
+	r->pstart = r->pend = 0;
+	print_pair_clear(&r->pair);
+	r->diff = 0;
+}
+
+static inline void diff_line_range_grow(struct diff_line_range *r)
+{
+	r->nr++;
+	ALLOC_GROW(r->ranges, r->nr, r->alloc);
+	line_range_init((r->ranges + r->nr - 1));
+}
+
 /*
  * Parse one item in the -L option
  */
@@ -17,7 +135,8 @@
 	/* Catch the '$' matcher, now it is used to match the last
 	 * line of the file. */
 	if (spec[0] == '$') {
-		*ret = lines;
+		if (ret)
+			*ret = lines;
 		return spec + 1;
 	}
 
@@ -28,6 +147,8 @@
 	if (begin != -1 && (spec[0] == '+' || spec[0] == '-')) {
 		num = strtol(spec + 1, &term, 10);
 		if (term != spec + 1) {
+			if (!ret)
+				return term;
 			if (spec[0] == '-')
 				num = 0 - num;
 			if (0 < num)
@@ -42,7 +163,8 @@
 	}
 	num = strtol(spec, &term, 10);
 	if (term != spec) {
-		*ret = num;
+		if (ret)
+			*ret = num;
 		return term;
 	}
 	if (spec[0] != '/')
@@ -56,6 +178,10 @@
 	if (*term != '/')
 		return spec;
 
+	/* in the scan-only case we are not interested in the regex */
+	if (!ret)
+		return term+1;
+
 	/* try [spec+1 .. term-1] as regexp */
 	*term = 0;
 	if (begin == -1)
@@ -104,3 +230,1204 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
 
 	return 0;
 }
+
+const char *skip_range_arg(const char *arg)
+{
+	arg = parse_loc(arg, NULL, NULL, 0, -1, 0);
+
+	if (*arg == ',')
+		arg = parse_loc(arg+1, NULL, NULL, 0, 0, 0);
+
+	return arg;
+}
+
+static void free_diff_line_ranges(struct diff_line_range *r)
+{
+	while (r) {
+		struct diff_line_range *next = r->next;
+		diff_line_range_clear(r);
+		free(r);
+		r = next;
+	}
+}
+
+static struct object *verify_commit(struct rev_info *revs)
+{
+	struct object *commit = NULL;
+	int found = -1;
+	int i;
+
+	for (i = 0; i < revs->pending.nr; i++) {
+		struct object *obj = revs->pending.objects[i].item;
+		if (obj->flags & UNINTERESTING)
+			continue;
+		while (obj->type == OBJ_TAG)
+			obj = deref_tag(obj, NULL, 0);
+		if (obj->type != OBJ_COMMIT)
+			die("Non commit %s?", revs->pending.objects[i].name);
+		if (commit)
+			die("More than one commit to dig from: %s and %s?",
+			    revs->pending.objects[i].name,
+				revs->pending.objects[found].name);
+		commit = obj;
+		found = i;
+	}
+
+	if (!commit)
+		die("No commit specified?");
+
+	return commit;
+}
+
+static void fill_blob_sha1(struct commit *commit, struct diff_line_range *r)
+{
+	unsigned mode;
+	unsigned char sha1[20];
+
+	while (r) {
+		if (get_tree_entry(commit->object.sha1, r->spec->path,
+			sha1, &mode))
+			goto error;
+		fill_filespec(r->spec, sha1, mode);
+		r = r->next;
+	}
+
+	return;
+error:
+	die("There is no path %s in the commit", r->spec->path);
+}
+
+static void fill_line_ends(struct diff_filespec *spec, long *lines,
+	unsigned long **line_ends)
+{
+	int num = 0, size = 50;
+	long cur = 0;
+	unsigned long *ends = NULL;
+	char *data = NULL;
+
+	if (diff_populate_filespec(spec, 0))
+		die("Cannot read blob %s", sha1_to_hex(spec->sha1));
+
+	ends = xmalloc(size * sizeof(*ends));
+	ends[cur++] = 0;
+	data = spec->data;
+	while (num < spec->size) {
+		if (data[num] == '\n' || num == spec->size - 1) {
+			ALLOC_GROW(ends, (cur + 1), size);
+			ends[cur++] = num;
+		}
+		num++;
+	}
+
+	/* shrink the array to fit the elements */
+	ends = xrealloc(ends, cur * sizeof(*ends));
+	*lines = cur;
+	*line_ends = ends;
+}
+
+struct nth_line_cb {
+	struct diff_filespec *spec;
+	long lines;
+	unsigned long *line_ends;
+};
+
+static const char *nth_line(void *data, long line)
+{
+	struct nth_line_cb *d = data;
+	assert(d && line < d->lines);
+	assert(d->spec && d->spec->data);
+
+	if (line == 0)
+		return (char *)d->spec->data;
+	else
+		return (char *)d->spec->data + d->line_ends[line] + 1;
+}
+
+static void parse_lines(struct commit *commit, struct diff_line_range *r)
+{
+	int i;
+	struct line_range *old_range = NULL;
+	long lines = 0;
+	unsigned long *ends = NULL;
+	struct nth_line_cb cb_data;
+
+	while (r) {
+		struct diff_filespec *spec = r->spec;
+		int num = r->nr;
+		assert(spec);
+		fill_blob_sha1(commit, r);
+		old_range = r->ranges;
+		r->ranges = NULL;
+		r->nr = r->alloc = 0;
+		fill_line_ends(spec, &lines, &ends);
+		cb_data.spec = spec;
+		cb_data.lines = lines;
+		cb_data.line_ends = ends;
+		for (i = 0; i < num; i++) {
+			long begin, end;
+			if (parse_range_arg(old_range[i].arg, nth_line, &cb_data,
+					    lines-1, &begin, &end))
+				die("malformed -L argument '%s'", old_range[i].arg);
+			diff_line_range_insert(r, old_range[i].arg, begin, end);
+		}
+
+		free(ends);
+		ends = NULL;
+
+		r = r->next;
+		free(old_range);
+	}
+}
+
+/*
+ * Insert a new line range into a diff_line_range struct, and keep the
+ * r->ranges sorted by their starting line number.
+ */
+static struct line_range *diff_line_range_insert(struct diff_line_range *r,
+		const char *arg, int start, int end)
+{
+	int i = 0;
+	struct line_range *rs = r->ranges;
+	int left_merge = 0, right_merge = 0;
+
+	assert(r);
+	assert(start <= end);
+
+	if (r->nr == 0 || rs[r->nr - 1].end < start - 1) {
+		int num = 0;
+		diff_line_range_grow(r);
+		rs = r->ranges;
+		num = r->nr - 1;
+		rs[num].arg = arg;
+		rs[num].start = start;
+		rs[num].end = end;
+		return rs + num;
+	}
+
+	for (; i < r->nr; i++) {
+		if (rs[i].end < start - 1)
+			continue;
+		if (rs[i].end == start - 1) {
+			rs[i].end = end;
+			right_merge = 1;
+			goto out;
+		}
+
+		assert(rs[i].end > start - 1);
+		if (rs[i].start <= start) {
+			if (rs[i].end < end) {
+				rs[i].end = end;
+				right_merge = 1;
+			}
+			goto out;
+		} else if (rs[i].start <= end + 1) {
+			rs[i].start = start;
+			left_merge = 1;
+			if (rs[i].end < end) {
+				rs[i].end = end;
+				right_merge = 1;
+			}
+			goto out;
+		} else {
+			int num = r->nr - i;
+			diff_line_range_grow(r);
+			rs = r->ranges;
+			memmove(rs + i + 1, rs + i, num * sizeof(struct line_range));
+			rs[i].arg = arg;
+			rs[i].start = start;
+			rs[i].end = end;
+			goto out;
+		}
+	}
+
+out:
+	assert(r->nr != i);
+	if (left_merge) {
+		int j = i;
+		for (; j > -1; j--) {
+			if (rs[j].end >= rs[i].start - 1)
+				if (rs[j].start < rs[i].start)
+					rs[i].start = rs[j].start;
+		}
+		memmove(rs + j + 1, rs + i, (r->nr - i) * sizeof(struct line_range));
+		r->nr -= i - j - 1;
+	}
+	if (right_merge) {
+		int j = i;
+		for (; j < r->nr; j++) {
+			if (rs[j].start <= rs[i].end + 1)
+				if (rs[j].end > rs[i].end)
+					rs[i].end = rs[j].end;
+		}
+		if (j < r->nr)
+			memmove(rs + i + 1, rs + j, (r->nr - j) * sizeof(struct line_range));
+		r->nr -= j - i - 1;
+	}
+	assert(r->nr);
+
+	return rs + i;
+}
+
+static void diff_line_range_clear(struct diff_line_range *r)
+{
+	int i = 0, zero = 0;
+
+	for (; i < r->nr; i++) {
+		struct line_range *rg = r->ranges + i;
+		line_range_clear(rg);
+	}
+
+	if (r->prev) {
+		zero = 0;
+		if (r->prev->count == 1)
+			zero = 1;
+		free_filespec(r->prev);
+		if (zero)
+			r->prev = NULL;
+	}
+	if (r->spec) {
+		zero = 0;
+		if (r->spec->count == 1)
+			zero = 1;
+		free_filespec(r->spec);
+		if (zero)
+			r->spec = NULL;
+	}
+
+	r->status = '\0';
+	r->alloc = r->nr = 0;
+
+	if (r->ranges)
+		free(r->ranges);
+	r->ranges = NULL;
+	r->next = NULL;
+}
+
+void diff_line_range_append(struct diff_line_range *r, const char *arg)
+{
+	diff_line_range_grow(r);
+	r->ranges[r->nr - 1].arg = arg;
+}
+
+static struct diff_line_range *diff_line_range_clone(struct diff_line_range *r)
+{
+	struct diff_line_range *ret = xmalloc(sizeof(*ret));
+	int i = 0;
+
+	assert(r);
+	diff_line_range_init(ret);
+	ret->ranges = xcalloc(r->nr, sizeof(struct line_range));
+	memcpy(ret->ranges, r->ranges, sizeof(struct line_range) * r->nr);
+
+	ret->alloc = ret->nr = r->nr;
+
+	for (; i < ret->nr; i++)
+		print_pair_init(&ret->ranges[i].pair);
+
+	ret->spec = r->spec;
+	assert(ret->spec);
+	ret->spec->count++;
+
+	return ret;
+}
+
+static struct diff_line_range *
+diff_line_range_clone_deeply(struct diff_line_range *r)
+{
+	struct diff_line_range *ret = NULL;
+	struct diff_line_range *tmp = NULL, *prev = NULL;
+
+	assert(r);
+	ret = tmp = prev = diff_line_range_clone(r);
+	r = r->next;
+	while (r) {
+		tmp = diff_line_range_clone(r);
+		prev->next = tmp;
+		prev = tmp;
+		r = r->next;
+	}
+
+	return ret;
+}
+
+static struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,
+		struct diff_line_range *other)
+{
+	struct diff_line_range *one = out, *two = other;
+	struct diff_line_range *pone = NULL;
+
+	while (one) {
+		struct diff_line_range *ptwo;
+		two = other;
+		ptwo = other;
+		while (two) {
+			if (!strcmp(one->spec->path, two->spec->path)) {
+				int i = 0;
+				for (; i < two->nr; i++) {
+					diff_line_range_insert(one, NULL,
+						two->ranges[i].start,
+						two->ranges[i].end);
+				}
+				if (two == other)
+					other = other->next;
+				else
+					ptwo->next = two->next;
+				diff_line_range_clear(two);
+				free(two);
+				two = NULL;
+
+				break;
+			}
+
+			ptwo = two;
+			two = two->next;
+		}
+
+		pone = one;
+		one = one->next;
+	}
+	pone->next = other;
+
+	return out;
+}
+
+static void add_line_range(struct rev_info *revs, struct commit *commit,
+		struct diff_line_range *r)
+{
+	struct diff_line_range *ret = NULL;
+
+	if (r) {
+		ret = lookup_decoration(&revs->line_range, &commit->object);
+		if (ret)
+			diff_line_range_merge(ret, r);
+		else
+			add_decoration(&revs->line_range, &commit->object, r);
+		commit->object.flags |= RANGE_UPDATE;
+	}
+}
+
+static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
+{
+	struct diff_line_range *r;
+	r = lookup_decoration(&revs->line_range, &commit->object);
+	if (!r)
+		return;
+	free_diff_line_ranges(r);
+	add_decoration(&revs->line_range, &commit->object, NULL);
+}
+
+static struct diff_line_range *lookup_line_range(struct rev_info *revs,
+		struct commit *commit)
+{
+	struct diff_line_range *ret = NULL;
+
+	ret = lookup_decoration(&revs->line_range, &commit->object);
+	return ret;
+}
+
+void line_log_init(struct rev_info *rev, struct diff_line_range *r)
+{
+	struct commit *commit = NULL;
+	struct diff_options *opt = &rev->diffopt;
+
+	commit = (struct commit *)verify_commit(rev);
+	parse_lines(commit, r);
+
+	add_line_range(rev, commit, r);
+	/*
+	 * Note we support -M/-C to detect file rename
+	 */
+	opt->nr_paths = 0;
+	diff_tree_release_paths(opt);
+}
+
+struct take_range_cb_data {
+	struct diff_line_range *interesting;	/* currently interesting ranges */
+	struct diff_line_range *range;
+		/* the ranges corresponds to the interesting ranges of parent commit */
+	long plno, tlno;
+		/* the last line number of diff hunk */
+	int diff;
+		/* whether there is some line changes between the current
+		 * commit and its parent */
+};
+
+#define SCALE_FACTOR 4
+/*
+ * [p_start, p_end] represents the pre-image of current diff hunk,
+ * [t_start, t_end] represents the post-image of the current diff hunk,
+ * [start, end] represents the currently interesting line range in
+ * post-image,
+ * [o_start, o_end] represents the original line range that coresponds
+ * to current line range.
+ */
+void map_lines(long p_start, long p_end, long t_start, long t_end,
+		long start, long end, long *o_start, long *o_end)
+{
+	/*
+	 * Normally, p_start should be less than p_end, so does the
+	 * t_start and t_end. But when the line range is added from
+	 * scratch, p_start will be greater than p_end. When the line
+	 * range is deleted, t_start will be greater than t_end.
+	 */
+	if (p_start > p_end) {
+		*o_start = *o_end = 0;
+		return;
+	}
+	/* A deletion */
+	if (t_start > t_end) {
+		*o_start = p_start;
+		*o_end = p_end;
+		return;
+	}
+
+	if (start == t_start && end == t_end) {
+		*o_start = p_start;
+		*o_end = p_end;
+		return;
+	}
+
+	/*
+	 * A heuristic for lines mapping:
+	 *
+	 * When the pre-image is no more than 1/SCALE_FACTOR of the post-image,
+	 * there is no effective way to find out which part of pre-image
+	 * corresponds to the currently interesting range of post-image.
+	 * And we are in the danger of tracking totally useless lines.
+	 * So, we just treat all the post-image lines as added from scratch.
+	 */
+	if (SCALE_FACTOR * (p_end - p_start + 1) < (t_end - t_start + 1)) {
+		*o_start = *o_end = 0;
+		return;
+	}
+
+	*o_start = p_start + start - t_start;
+	*o_end = p_end - (t_end - end);
+
+	if (*o_start > *o_end) {
+		int temp = *o_start;
+		*o_start = *o_end;
+		*o_end = temp;
+	}
+
+	if (*o_start < p_start)
+		*o_start = p_start;
+	if (*o_end > p_end)
+		*o_end = p_end;
+}
+
+/*
+ * When same == 1:
+ * [p_start, p_end] represents the diff hunk line range of pre-image,
+ * [t_start, t_end] represents the diff hunk line range of post-image.
+ * When same == 0, they represent a range of identical lines between
+ * two images.
+ *
+ * This function find out the corresponding line ranges of currently
+ * interesting ranges which this diff hunk touches.
+ */
+static void map_range(struct take_range_cb_data *data, int same,
+		long p_start, long p_end, long t_start, long t_end)
+{
+	struct line_range *ranges = data->interesting->ranges;
+	long takens, takene, start, end;
+	int i = 0, out = 0, added = 0;
+	long op_start = p_start, op_end = p_end, ot_start = t_start, ot_end = t_end;
+
+	for (; i < data->interesting->nr; i++) {
+		added = 0;
+		if (t_start > ranges[i].end)
+			continue;
+		if (t_end < ranges[i].start)
+			break;
+
+		if (t_start > ranges[i].start) {
+			start = t_start;
+			takens = p_start;
+		} else {
+			start = ranges[i].start;
+			takens = p_start + start - t_start;
+		}
+
+		if (t_end >= ranges[i].end) {
+			end = ranges[i].end;
+			takene = p_start + end - t_start;
+		} else {
+			end = t_end;
+			takene = p_end;
+			out = 1;
+		}
+
+		if (!same) {
+			struct print_pair *pair = &ranges[i].pair;
+			struct print_range *rr = NULL;
+			print_pair_grow(pair);
+			rr = pair->ranges + pair->nr - 1;
+			print_range_init(rr);
+			rr->start = start;
+			rr->end = end;
+			map_lines(op_start, op_end, ot_start, ot_end, start, end,
+					&takens, &takene);
+			if (takens == 0 && takene == 0) {
+				added = 1;
+				rr->line_added = 1;
+			}
+			rr->pstart = takens;
+			rr->pend = takene;
+			data->diff = 1;
+			data->interesting->diff = 1;
+			ranges[i].diff = 1;
+		}
+		if (added) {
+			/* Code movement/copy goes here */
+		} else {
+			struct line_range *added_range = diff_line_range_insert(data->range,
+					NULL, takens, takene);
+			assert(added_range);
+			ranges[i].pstart = added_range->start;
+			ranges[i].pend = added_range->end;
+		}
+
+		t_start = end + 1;
+		p_start = takene + 1;
+
+		if (out)
+			break;
+	}
+}
+
+/*
+ * [p_start, p_end] represents the line range of pre-image,
+ * [t_start, t_end] represents the line range of post-image,
+ * and they are identical lines.
+ *
+ * This function substracts out the identical lines between current
+ * commit and its parent, from currently interesting ranges.
+ */
+static void take_range(struct take_range_cb_data *data,
+		long p_start, long p_end, long t_start, long t_end)
+{
+	struct line_range *ranges = data->interesting->ranges;
+	long takens, takene, start, end;
+	int i = 0, out = 0, added = 0;
+
+	for (; i < data->interesting->nr; i++) {
+		added = 0;
+		if (t_start > ranges[i].end)
+			continue;
+		if (t_end < ranges[i].start)
+			break;
+
+		if (t_start > ranges[i].start) {
+			long tmp = ranges[i].end;
+			ranges[i].end = t_start - 1;
+			start = t_start;
+			takens = p_start;
+			if (t_end >= tmp) {
+				end = tmp;
+				takene = p_start + end - t_start;
+				p_start = takene + 1;
+				t_start = end + 1;
+			} else {
+				end = t_end;
+				takene = p_end;
+				diff_line_range_insert(data->interesting, NULL,
+					t_end + 1, tmp);
+				out = 1;
+			}
+		} else {
+			start = ranges[i].start;
+			takens = p_start + start - t_start;
+			if (t_end >= ranges[i].end) {
+				int num = data->interesting->nr - 1;
+				end = ranges[i].end;
+				takene = p_start + end - t_start;
+				t_start = end + 1;
+				p_start = takene + 1;
+				memmove(ranges + i, ranges + i + 1, (num - i) * sizeof(*ranges));
+				data->interesting->nr = num;
+				i--;
+			} else {
+				end = t_end;
+				takene = p_end;
+				ranges[i].start = t_end + 1;
+				out = 1;
+			}
+		}
+
+		diff_line_range_insert(data->range, NULL, takens, takene);
+
+		if (out)
+			break;
+	}
+}
+
+static void take_range_cb(void *data, long same, long p_next, long t_next)
+{
+	struct take_range_cb_data *d = data;
+	long p_start = d->plno + 1, t_start = d->tlno + 1;
+	long p_end = p_start + same - t_start, t_end = same;
+
+	/* If one file is added from scratch, we should not bother to call
+	 * take_range, since there is nothing to take
+	 */
+	if (t_end >= t_start)
+		take_range(d, p_start, p_end, t_start, t_end);
+	d->plno = p_next;
+	d->tlno = t_next;
+}
+
+static void map_range_cb(void *data, long same, long p_next, long t_next)
+{
+	struct take_range_cb_data *d = data;
+
+	long p_start = d->plno + 1;
+	long t_start = d->tlno + 1;
+	long p_end = same - t_start + p_start;
+	long t_end = same;
+
+	/* Firstly, take the unchanged lines from child */
+	if (t_end >= t_start)
+		map_range(d, 1, p_start, p_end, t_start, t_end);
+
+	/* find out which lines to print */
+	t_start = same + 1;
+	p_start = d->plno + t_start - d->tlno;
+	map_range(d, 0, p_start, p_next, t_start, t_next);
+
+	d->plno = p_next;
+	d->tlno = t_next;
+}
+
+static void load_tree_desc(struct tree_desc *desc, void **tree,
+		const unsigned char *sha1)
+{
+	unsigned long size;
+	*tree = read_object_with_reference(sha1, tree_type, &size, NULL);
+	if (!tree)
+		die("Unable to read tree (%s)", sha1_to_hex(sha1));
+	init_tree_desc(desc, *tree, size);
+}
+
+/*
+ * We support two kinds of operation in this function:
+ * 1. map == 0, take the same lines from the current commit and assign it
+ *              to parent;
+ * 2. map == 1, in addition to the same lines, we also map the changed lines
+ *              from the current commit to the parent according to the
+ *              diff output.
+ * take_range_cb and take_range are used to take same lines from current commit
+ * to parents.
+ * map_range_cb and map_range are used to map line ranges to the parent.
+ */
+static void assign_range_to_parent(struct rev_info *rev, struct commit *commit,
+		struct commit *parent, struct diff_line_range *range,
+		struct diff_options *opt, int map)
+{
+	struct diff_line_range *final_range = xmalloc(sizeof(*final_range));
+	struct diff_line_range *cur_range = final_range;
+	struct diff_line_range *prev_range = final_range;
+	struct diff_line_range *rg = NULL;
+	void *tree1 = NULL, *tree2 = NULL;
+	struct tree_desc desc1, desc2;
+	struct diff_queue_struct *queue;
+	struct take_range_cb_data cb_data = {NULL, cur_range, 0, 0};
+	xpparam_t xpp;
+	xdemitconf_t xecfg;
+	int i, diff = 0;
+	xdiff_emit_hunk_consume_fn fn = map ? map_range_cb : take_range_cb;
+
+	diff_line_range_init(cur_range);
+	memset(&xpp, 0, sizeof(xpp));
+	memset(&xecfg, 0, sizeof(xecfg));
+	xecfg.ctxlen = xecfg.interhunkctxlen = 0;
+
+	/*
+	 * Compose up two trees, for root commit, we make up a empty tree.
+	 */
+	assert(commit);
+	load_tree_desc(&desc2, &tree2, commit->tree->object.sha1);
+	if (parent) {
+		load_tree_desc(&desc1, &tree1, parent->tree->object.sha1);
+	} else {
+		init_tree_desc(&desc1, "", 0);
+	}
+
+	DIFF_QUEUE_CLEAR(&diff_queued_diff);
+	diff_tree(&desc1, &desc2, "", opt);
+	diffcore_std(opt);
+
+	queue = &diff_queued_diff;
+	for (i = 0; i < queue->nr; i++) {
+		struct diff_filepair *pair = queue->queue[i];
+		struct diff_line_range *rg = range;
+		mmfile_t file_parent, file_t;
+		assert(pair->two->path);
+		while (rg) {
+			assert(rg->spec->path);
+			if (!strcmp(rg->spec->path, pair->two->path))
+				break;
+			rg = rg->next;
+		}
+
+		if (!rg)
+			continue;
+		rg->touched = 1;
+		if (rg->nr == 0)
+			continue;
+
+		rg->status = pair->status;
+		assert(pair->two->sha1_valid);
+		diff_populate_filespec(pair->two, 0);
+		file_t.ptr = pair->two->data;
+		file_t.size = pair->two->size;
+
+		if (rg->prev)
+			free_filespec(rg->prev);
+		rg->prev = pair->one;
+		rg->prev->count++;
+		if (pair->one->sha1_valid) {
+			diff_populate_filespec(pair->one, 0);
+			file_parent.ptr = pair->one->data;
+			file_parent.size = pair->one->size;
+		} else {
+			file_parent.ptr = "";
+			file_parent.size = 0;
+		}
+
+		if (cur_range->nr != 0) {
+			struct diff_line_range *tmp = xmalloc(sizeof(*tmp));
+			cur_range->next = tmp;
+			prev_range = cur_range;
+			cur_range = tmp;
+		} else if (cur_range->spec)
+			diff_line_range_clear(cur_range);
+
+		diff_line_range_init(cur_range);
+		if (pair->one->sha1_valid) {
+			cur_range->spec = pair->one;
+			cur_range->spec->count++;
+		} else {
+			assert(is_null_sha1(pair->one->sha1));
+			cur_range->spec = pair->two;
+			cur_range->spec->count++;
+		}
+
+		cb_data.interesting = rg;
+		cb_data.range = cur_range;
+		cb_data.diff = 0;
+		cb_data.plno = cb_data.tlno = 0;
+		xdi_diff_hunks(&file_parent, &file_t, fn, &cb_data, &xpp, &xecfg);
+		if (cb_data.diff)
+			diff = 1;
+		/*
+		 * The remain part is the same part.
+		 * Instead of calculating the true line number of the two files,
+		 * use the biggest integer.
+		 */
+		if (map)
+			map_range(&cb_data, 1, cb_data.plno + 1,
+				  INT_MAX, cb_data.tlno + 1, INT_MAX);
+		else
+			take_range(&cb_data, cb_data.plno + 1,
+				   INT_MAX, cb_data.tlno + 1, INT_MAX);
+	}
+	opt->output_format = DIFF_FORMAT_NO_OUTPUT;
+	diff_flush(opt);
+
+	/*
+	 * Collect the untouched ranges, this comes from the files not changed
+	 * between two commit.
+	 */
+	rg = range;
+	while (rg) {
+		/* clear the touched one to make it usable in next round */
+		if (rg->touched) {
+			rg->touched = 0;
+		} else {
+			struct diff_line_range *untouched = diff_line_range_clone(rg);
+			if (prev_range == final_range && final_range->nr == 0) {
+				final_range = prev_range = untouched;
+			} else {
+				prev_range->next = untouched;
+				prev_range = untouched;
+			}
+		}
+		rg = rg->next;
+	}
+
+	if (cur_range->nr == 0) {
+		diff_line_range_clear(cur_range);
+		free(cur_range);
+		if (prev_range == cur_range)
+			final_range = NULL;
+		else
+			prev_range->next = NULL;
+	}
+
+	if (final_range) {
+		assert(parent);
+		assert(final_range->spec);
+		add_line_range(rev, parent, final_range);
+	}
+
+	/* and the ranges of current commit is updated */
+	commit->object.flags &= ~RANGE_UPDATE;
+	if (diff)
+		commit->object.flags |= NEED_PRINT;
+
+	if (tree1)
+		free(tree1);
+	if (tree2)
+		free(tree2);
+}
+
+static void diff_update_parent_range(struct rev_info *rev,
+		struct commit *commit)
+{
+	struct diff_line_range *r = lookup_line_range(rev, commit);
+	struct commit_list *parents = commit->parents;
+	struct commit *c = NULL;
+	if (parents) {
+		assert(!parents->next);
+		c = parents->item;
+	}
+
+	assign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);
+}
+
+static void assign_parents_range(struct rev_info *rev, struct commit *commit)
+{
+	struct commit_list *parents = commit->parents;
+	struct diff_line_range *r = lookup_line_range(rev, commit);
+	struct diff_line_range *evil = NULL, *range = NULL;
+	int nontrivial = 0;
+
+	/*
+	 * If we are in linear history, update range and flush the patch if
+	 * necessary
+	 */
+	if (!parents || !parents->next)
+		return diff_update_parent_range(rev, commit);
+
+	/*
+	 * Loop on the parents and assign the ranges to different
+	 * parents, if there is any range left, this commit must
+	 * be an evil merge.
+	 */
+	evil = diff_line_range_clone_deeply(r);
+	parents = commit->parents;
+	while (parents) {
+		struct commit *p = parents->item;
+		assign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);
+		assign_range_to_parent(rev, commit, p, evil, &rev->diffopt, 0);
+		parents = parents->next;
+	}
+
+	/*
+	 * yes, this must be an evil merge.
+	 */
+	range = evil;
+	while (range) {
+		if (range->nr) {
+			commit->object.flags |= NEED_PRINT | EVIL_MERGE;
+			nontrivial = 1;
+		}
+		range = range->next;
+	}
+
+	if (nontrivial)
+		add_decoration(&rev->nontrivial_merge, &commit->object, evil);
+	else
+		free_diff_line_ranges(evil);
+}
+
+struct line_chunk {
+	int lone, ltwo;
+	const char *one, *two;
+	const char *one_end, *two_end;
+	struct diff_line_range *range;
+};
+
+static void flush_lines(struct diff_options *opt, const char **ptr, const char *end,
+		int slno, int elno, int *lno, const char *color, const char heading)
+{
+	const char *p = *ptr;
+	struct strbuf buf = STRBUF_INIT;
+	const char *reset;
+
+	if (*color)
+		reset = diff_get_color_opt(opt, DIFF_RESET);
+	else
+		reset = "";
+
+	strbuf_addf(&buf, "%s%c", color, heading);
+	while (*ptr < end && *lno < slno) {
+		if (**ptr == '\n') {
+			(*lno)++;
+			if (*lno == slno) {
+				(*ptr)++;
+				break;
+			}
+		}
+		(*ptr)++;
+	}
+	assert(*ptr <= end);
+	p = *ptr;
+
+	while (*ptr < end && *lno <= elno) {
+		if (**ptr == '\n') {
+			fprintf(opt->file, "%s", buf.buf);
+			if (*ptr - p)
+				fwrite(p, *ptr - p, 1, opt->file);
+			fprintf(opt->file, "%s\n", reset);
+			p = *ptr + 1;
+			(*lno)++;
+		}
+		(*ptr)++;
+	}
+	if (*lno <= elno) {
+		fprintf(opt->file, "%s", buf.buf);
+		if (*ptr - p)
+			fwrite(p, *ptr - p, 1, opt->file);
+		fprintf(opt->file, "%s\n", reset);
+	}
+	strbuf_release(&buf);
+}
+
+static void diff_flush_range(struct diff_options *opt, struct line_chunk *chunk,
+		struct line_range *range)
+{
+	struct print_pair *pair = &range->pair;
+	const char *old = diff_get_color_opt(opt, DIFF_FILE_OLD);
+	const char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);
+	int i, cur = range->start;
+
+	for (i = 0; i < pair->nr; i++) {
+		struct print_range *pr = pair->ranges + i;
+		if (cur < pr->start)
+			flush_lines(opt, &chunk->two, chunk->two_end,
+				cur, pr->start - 1, &chunk->ltwo, "", ' ');
+
+		if (!pr->line_added)
+			flush_lines(opt, &chunk->one, chunk->one_end,
+				pr->pstart, pr->pend, &chunk->lone, old, '-');
+		flush_lines(opt, &chunk->two, chunk->two_end,
+			pr->start, pr->end, &chunk->ltwo, new, '+');
+
+		cur = pr->end + 1;
+	}
+
+	if (cur <= range->end) {
+		flush_lines(opt, &chunk->two, chunk->two_end,
+			cur, range->end, &chunk->ltwo, "", ' ');
+	}
+}
+
+static void diff_flush_chunks(struct diff_options *opt, struct line_chunk *chunk)
+{
+	struct diff_line_range *range = chunk->range;
+	const char *set = diff_get_color_opt(opt, DIFF_FRAGINFO);
+	const char *reset = diff_get_color_opt(opt, DIFF_RESET);
+	int i;
+
+	for (i = 0; i < range->nr; i++) {
+		struct line_range *r = range->ranges + i;
+		long lenp = r->pend - r->pstart + 1, pstart = r->pstart;
+		long len = r->end - r->start + 1;
+		if (pstart == 0)
+			lenp = 0;
+
+		fprintf(opt->file, "%s@@ -%ld,%ld +%ld,%ld @@%s\n",
+			set, pstart, lenp, r->start, len, reset);
+
+		diff_flush_range(opt, chunk, r);
+	}
+}
+
+static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *range)
+{
+	struct diff_options *opt = &rev->diffopt;
+	struct diff_filespec *one = range->prev, *two = range->spec;
+	struct diff_filepair p = {one, two, range->status, 0};
+	struct strbuf header = STRBUF_INIT, meta = STRBUF_INIT;
+	const char *a_prefix, *b_prefix;
+	const char *name_a, *name_b, *a_one, *b_two;
+	const char *lbl[2];
+	const char *set = diff_get_color_opt(opt, DIFF_METAINFO);
+	const char *reset = diff_get_color_opt(opt, DIFF_RESET);
+	struct line_chunk chunk;
+	int must_show_header;
+
+	/*
+	 * the ranges that touch no different file, in this case
+	 * the line number will not change, and of course we have
+	 * no sensible rang->pair since there is no diff run.
+	 */
+	if (!one)
+		return;
+
+	if (range->status == DIFF_STATUS_DELETED)
+		die("We are following an nonexistent file, interesting!");
+
+	name_a  = one->path;
+	name_b = two->path;
+	fill_metainfo(&meta, name_a, name_b, one, two, opt, &p, &must_show_header,
+			DIFF_OPT_TST(opt, COLOR_DIFF));
+
+	diff_set_mnemonic_prefix(opt, "a/", "b/");
+	if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
+		a_prefix = opt->b_prefix;
+		b_prefix = opt->a_prefix;
+	} else {
+		a_prefix = opt->a_prefix;
+		b_prefix = opt->b_prefix;
+	}
+
+	name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
+	name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
+
+	a_one = quote_two(a_prefix, name_a + (*name_a == '/'));
+	b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
+	lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
+	lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+	strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+	if (lbl[0][0] == '/') {
+		strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
+	} else if (lbl[1][0] == '/') {
+		strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+	} else if (one->mode != two->mode) {
+			strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
+			strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
+	}
+
+	fprintf(opt->file, "%s%s", header.buf, meta.buf);
+	strbuf_release(&meta);
+	strbuf_release(&header);
+	fprintf(opt->file, "%s--- %s%s\n", set, lbl[0], reset);
+	fprintf(opt->file, "%s+++ %s%s\n", set, lbl[1], reset);
+	free((void *)a_one);
+	free((void *)b_two);
+
+	chunk.one = one->data;
+	chunk.one_end = (const char *)one->data + one->size;
+	chunk.lone = 1;
+	chunk.two = two->data;
+	chunk.two_end = (const char *)two->data + two->size;
+	chunk.ltwo = 1;
+	chunk.range = range;
+	diff_flush_chunks(&rev->diffopt, &chunk);
+}
+
+static void flush_nontrivial_merge(struct rev_info *rev,
+		struct diff_line_range *range)
+{
+	struct diff_options *opt = &rev->diffopt;
+	const char *reset = diff_get_color_opt(opt, DIFF_RESET);
+	const char *frag = diff_get_color_opt(opt, DIFF_FRAGINFO);
+	const char *meta = diff_get_color_opt(opt, DIFF_METAINFO);
+	const char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);
+
+	fprintf(opt->file, "%s%s%s\n", meta, "nontrivial merge found", reset);
+
+	while (range) {
+		if (range->nr) {
+			int lno = 1;
+			const char *ptr = range->spec->data;
+			const char *end = range->spec->data + range->spec->size;
+			int i = 0;
+			fprintf(opt->file, "%s%s%s\n\n", meta, range->spec->path, reset);
+			for (; i < range->nr; i++) {
+				struct line_range *r = range->ranges + i;
+				fprintf(opt->file, "%s@@ %ld,%ld @@%s\n", frag, r->start,
+					r->end - r->start + 1, reset);
+				flush_lines(opt, &ptr, end, r->start, r->end,
+					&lno, new, ' ');
+			}
+			fprintf(opt->file, "\n");
+		}
+		range = range->next;
+	}
+}
+
+static void line_log_flush(struct rev_info *rev, struct commit *c)
+{
+	struct diff_line_range *range = lookup_line_range(rev, c);
+	struct diff_line_range *nontrivial = lookup_decoration(&rev->nontrivial_merge, &c->object);
+	struct log_info log;
+
+	if (!range)
+		return;
+
+	log.commit = c;
+	log.parent = NULL;
+	rev->loginfo = &log;
+	show_log(rev);
+	rev->loginfo = NULL;
+	/*
+	 * Add a new line after each commit message, of course we should
+	 * add --graph alignment later when the patches comes to master.
+	 */
+	fprintf(rev->diffopt.file, "\n");
+
+	if (c->object.flags & EVIL_MERGE)
+		return flush_nontrivial_merge(rev, nontrivial);
+
+	while (range) {
+		if (range->diff)
+			diff_flush_filepair(rev, range);
+		range = range->next;
+	}
+}
+
+int line_log_walk(struct rev_info *rev)
+{
+	struct commit *commit;
+	struct commit_list *list = NULL;
+	struct diff_line_range *r = NULL;
+
+	if (prepare_revision_walk(rev))
+		die("revision walk prepare failed");
+
+	/*
+	 * Note that -L automatically turns on --topo-order, so
+	 * rev->commits already holds all commits in the range.  The
+	 * first commit is our starting point.
+	 */
+	list = rev->commits;
+	if (list) {
+		list->item->object.flags |= RANGE_UPDATE;
+		list = list->next;
+	}
+	/* Clear the flags */
+	while (list) {
+		list->item->object.flags &= ~(RANGE_UPDATE | EVIL_MERGE | NEED_PRINT);
+		list = list->next;
+	}
+
+	list = rev->commits;
+	while (list) {
+		struct commit_list *need_free = list;
+		commit = list->item;
+
+		if (commit->object.flags & RANGE_UPDATE)
+			assign_parents_range(rev, commit);
+
+		if (commit->object.flags & NEED_PRINT)
+			line_log_flush(rev, commit);
+
+		clear_commit_line_range(rev, commit);
+
+		r = lookup_decoration(&rev->nontrivial_merge, &commit->object);
+		if (r) {
+			free_diff_line_ranges(r);
+			add_decoration(&rev->nontrivial_merge, &commit->object,
+				       NULL);
+		}
+
+		list = list->next;
+		free(need_free);
+	}
+
+	return 0;
+}
diff --git a/line.h b/line.h
index 5878c94..5f2931a 100644
--- a/line.h
+++ b/line.h
@@ -1,6 +1,8 @@
 #ifndef LINE_H
 #define LINE_H
 
+#include "diffcore.h"
+
 /*
  * Parse one item in an -L begin,end option w.r.t. the notional file
  * object 'cb_data' consisting of 'lines' lines.
@@ -20,4 +22,51 @@ extern int parse_range_arg(const char *arg,
 			   void *cb_data, long lines,
 			   long *begin, long *end);
 
+/*
+ * Scan past a range argument that could be parsed by
+ * 'parse_range_arg', to help the caller determine the start of the
+ * filename in '-L n,m:file' syntax.
+ *
+ * Returns a pointer to the first character after the 'n,m' part, or
+ * NULL in case the argument is obviously malformed.
+ */
+
+extern const char *skip_range_arg(const char *arg);
+
+struct rev_info;
+struct commit;
+struct diff_line_range;
+struct diff_options;
+
+struct line_range;
+
+struct diff_line_range {
+	struct diff_filespec *prev;
+	struct diff_filespec *spec;
+	char status;
+	int alloc;
+	int nr;
+	struct line_range *ranges;
+	unsigned int	touched:1,
+			diff:1;
+	struct diff_line_range *next;
+};
+
+static inline void diff_line_range_init(struct diff_line_range *r)
+{
+	r->prev = r->spec = NULL;
+	r->status = '\0';
+	r->alloc = r->nr = 0;
+	r->ranges = NULL;
+	r->next = NULL;
+	r->touched = 0;
+	r->diff = 0;
+}
+
+extern void diff_line_range_append(struct diff_line_range *r, const char *arg);
+
+extern void line_log_init(struct rev_info *rev, struct diff_line_range *r);
+
+extern int line_log_walk(struct rev_info *rev);
+
 #endif /* LINE_H */
diff --git a/revision.c b/revision.c
index 6465c45..369ec56 100644
--- a/revision.c
+++ b/revision.c
@@ -1662,6 +1662,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 	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;
+	}
+
 	if (diff_setup_done(&revs->diffopt) < 0)
 		die("diff_setup_done failed");
 
diff --git a/revision.h b/revision.h
index 8897368..585b15f 100644
--- a/revision.h
+++ b/revision.h
@@ -14,7 +14,10 @@
 #define CHILD_SHOWN	(1u<<6)
 #define ADDED		(1u<<7)	/* Parents already parsed and added? */
 #define SYMMETRIC_LEFT	(1u<<8)
-#define ALL_REV_FLAGS	((1u<<9)-1)
+#define RANGE_UPDATE	(1u<<9) /* for line level traverse */
+#define NEED_PRINT	(1u<<10)
+#define EVIL_MERGE	(1u<<11)
+#define ALL_REV_FLAGS	((1u<<12)-1)
 
 #define DECORATE_SHORT_REFS	1
 #define DECORATE_FULL_REFS	2
@@ -68,7 +71,8 @@ struct rev_info {
 			cherry_pick:1,
 			bisect:1,
 			ancestry_path:1,
-			first_parent_only:1;
+			first_parent_only:1,
+			line_level_traverse:1;
 
 	/* Diff flags */
 	unsigned int	diff:1,
@@ -137,6 +141,9 @@ struct rev_info {
 	/* commit counts */
 	int count_left;
 	int count_right;
+	/* line level range that we are chasing */
+	struct decoration line_range;
+	struct decoration nontrivial_merge;
 };
 
 #define REV_TREE_SAME		0
diff --git a/t/t4301-log-line-single-history.sh b/t/t4301-log-line-single-history.sh
new file mode 100755
index 0000000..59e9654
--- /dev/null
+++ b/t/t4301-log-line-single-history.sh
@@ -0,0 +1,685 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test git log -L with single line of history'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 0;
+	int b = 1;
+	int c;
+	c = a + b;
+}
+EOF
+
+cat >path1 <<\EOF
+void output()
+{
+	printf("hello world");
+}
+EOF
+
+test_expect_success 'add path0/path1 and commit.' '
+	git add path0 path1 &&
+	git commit -m "Base commit"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 10;
+	int b = 11;
+	int c;
+	c = a + b;
+}
+EOF
+
+cat >path1 <<\EOF
+void output()
+{
+	const char *str = "hello world!";
+	printf("%s", str);
+}
+EOF
+
+test_expect_success 'Change the 2,3 lines of path0 and path1.' '
+	git add path0 path1 &&
+	git commit -m "Change 2,3 lines of path0 and path1"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 10;
+	int b = 11;
+	int c;
+	c = 10 * (a + b);
+}
+EOF
+
+test_expect_success 'Change the 5th line of path0.' '
+	git add path0 &&
+	git commit -m "Change the 5th line of path0"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	int a = 10;
+	int b = 11;
+	printf("%d", a - b);
+}
+EOF
+
+test_expect_success 'Final change of path0.' '
+	git add path0 &&
+	git commit -m "Final change of path0"
+'
+
+sed 's/Q/ /g' >expected-path0 <<\EOF
+Final change of path0
+
+diff --git a/path0 b/path0
+index ccdf243..ccf8bcf 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,6 @@
+ void func()
+ {
+Q	int a = 10;
+Q	int b = 11;
+-	int c;
+-	c = 10 * (a + b);
++	printf("%d", a - b);
+ }
+
+Change the 5th line of path0
+
+diff --git a/path0 b/path0
+index b0eb888..ccdf243 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+Q	int a = 10;
+Q	int b = 11;
+Q	int c;
+-	c = a + b;
++	c = 10 * (a + b);
+ }
+
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+-	int a = 0;
+-	int b = 1;
++	int a = 10;
++	int b = 11;
+Q	int c;
+Q	c = a + b;
+ }
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +1,7 @@
++void func()
++{
++	int a = 0;
++	int b = 1;
++	int c;
++	c = a + b;
++}
+EOF
+
+cat >expected-path1 <<\EOF
+Change 2,3 lines of path0 and path1
+
+diff --git a/path1 b/path1
+index 52be2a5..cc54b12 100644
+--- a/path1
++++ b/path1
+@@ -1,4 +1,5 @@
+ void output()
+ {
+-	printf("hello world");
++	const char *str = "hello world!";
++	printf("%s", str);
+ }
+
+Base commit
+
+diff --git a/path1 b/path1
+new file mode 100644
+index 0000000..52be2a5
+--- /dev/null
++++ b/path1
+@@ -0,0 +1,4 @@
++void output()
++{
++	printf("hello world");
++}
+EOF
+
+sed 's/Q/ /g' >expected-pathall <<\EOF
+Final change of path0
+
+diff --git a/path0 b/path0
+index ccdf243..ccf8bcf 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,6 @@
+ void func()
+ {
+Q	int a = 10;
+Q	int b = 11;
+-	int c;
+-	c = 10 * (a + b);
++	printf("%d", a - b);
+ }
+
+Change the 5th line of path0
+
+diff --git a/path0 b/path0
+index b0eb888..ccdf243 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+Q	int a = 10;
+Q	int b = 11;
+Q	int c;
+-	c = a + b;
++	c = 10 * (a + b);
+ }
+
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+-	int a = 0;
+-	int b = 1;
++	int a = 10;
++	int b = 11;
+Q	int c;
+Q	c = a + b;
+ }
+diff --git a/path1 b/path1
+index 52be2a5..cc54b12 100644
+--- a/path1
++++ b/path1
+@@ -1,4 +1,5 @@
+ void output()
+ {
+-	printf("hello world");
++	const char *str = "hello world!";
++	printf("%s", str);
+ }
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +1,7 @@
++void func()
++{
++	int a = 0;
++	int b = 1;
++	int c;
++	c = a + b;
++}
+diff --git a/path1 b/path1
+new file mode 100644
+index 0000000..52be2a5
+--- /dev/null
++++ b/path1
+@@ -0,0 +1,4 @@
++void output()
++{
++	printf("hello world");
++}
+EOF
+
+cat >expected-linenum <<\EOF
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+-	int a = 0;
+-	int b = 1;
++	int a = 10;
++	int b = 11;
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +2,3 @@
++{
++	int a = 0;
++	int b = 1;
+EOF
+
+sed 's/Q/ /g' >expected-always <<\EOF
+Final change of path0
+
+diff --git a/path0 b/path0
+index ccdf243..ccf8bcf 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+Q	int a = 10;
+Q	int b = 11;
+
+Change the 5th line of path0
+
+diff --git a/path0 b/path0
+index b0eb888..ccdf243 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+Q	int a = 10;
+Q	int b = 11;
+
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+-	int a = 0;
+-	int b = 1;
++	int a = 10;
++	int b = 11;
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +2,3 @@
++{
++	int a = 0;
++	int b = 1;
+EOF
+
+test_expect_success 'Show the line level log of path0' '
+	git log --pretty=format:%s%n%b -L /func/,/^}/:path0 > current-path0
+'
+
+test_expect_success 'validate the path0 output.' '
+	test_cmp current-path0 expected-path0
+'
+
+test_expect_success 'Show the line level log of path1' '
+	git log --pretty=format:%s%n%b -L /output/,/^}/:path1 > current-path1
+'
+
+test_expect_success 'validate the path1 output.' '
+	test_cmp current-path1 expected-path1
+'
+
+test_expect_success 'Show the line level log of two files' '
+	git log --pretty=format:%s%n%b -L /func/,/^}/:path0 -L /output/,/^}/:path1 > current-pathall
+'
+
+test_expect_success 'validate the all path output.' '
+	test_cmp current-pathall expected-pathall
+'
+
+test_expect_success 'Test the line number argument' '
+	git log --pretty=format:%s%n%b -L 2,4:path0 > current-linenum
+'
+
+test_expect_success 'validate the line number output.' '
+	test_cmp current-linenum expected-linenum
+'
+test_expect_success 'Test the --full-line-diff option' '
+	git log --pretty=format:%s%n%b --full-line-diff -L 2,4:path0 > current-always
+'
+
+test_expect_success 'validate the --full-line-diff output.' '
+    test_cmp current-always expected-always
+'
+
+# Rerun all log with graph
+test_expect_success 'Show the line level log of path0 with --graph' '
+	git log --pretty=format:%s%n%b --graph -L /func/,/^}/:path0 > current-path0-graph
+'
+
+test_expect_success 'Show the line level log of path1 with --graph' '
+	git log --pretty=format:%s%n%b --graph -L /output/,/^}/:path1 > current-path1-graph
+'
+
+test_expect_success 'Show the line level log of two files with --graph' '
+	git log --pretty=format:%s%n%b --graph -L /func/,/^}/:path0 -L /output/,/^}/:path1 > current-pathall-graph
+'
+
+test_expect_success 'Test the line number argument with --graph' '
+	git log --pretty=format:%s%n%b --graph -L 2,4:path0 > current-linenum-graph
+'
+
+test_expect_success 'Test the --full-line-diff option with --graph option' '
+	git log --pretty=format:%s%n%b --full-line-diff --graph -L 2,4:path0 > current-always-graph
+'
+
+sed -e 's/Q/ /g' -e 's/#$//' > expected-path0-graph <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index ccdf243..ccf8bcf 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,6 @@
+|  void func()
+|  {
+|QQ	int a = 10;
+|QQ	int b = 11;
+| -	int c;
+| -	c = 10 * (a + b);
+| +	printf("%d", a - b);
+|  }
+|  #
+* Change the 5th line of path0
+| #
+| diff --git a/path0 b/path0
+| index b0eb888..ccdf243 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+|  void func()
+|  {
+|QQ	int a = 10;
+|QQ	int b = 11;
+|QQ	int c;
+| -	c = a + b;
+| +	c = 10 * (a + b);
+|  }
+|  #
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+|  void func()
+|  {
+| -	int a = 0;
+| -	int b = 1;
+| +	int a = 10;
+| +	int b = 11;
+|QQ	int c;
+|QQ	c = a + b;
+|  }
+|  #
+* Base commit
+  #
+  diff --git a/path0 b/path0
+  new file mode 100644
+  index 0000000..fb33939
+  --- /dev/null
+  +++ b/path0
+  @@ -0,0 +1,7 @@
+  +void func()
+  +{
+  +	int a = 0;
+  +	int b = 1;
+  +	int c;
+  +	c = a + b;
+  +}
+EOF
+
+sed 's/#$//' > expected-path1-graph <<\EOF
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path1 b/path1
+| index 52be2a5..cc54b12 100644
+| --- a/path1
+| +++ b/path1
+| @@ -1,4 +1,5 @@
+|  void output()
+|  {
+| -	printf("hello world");
+| +	const char *str = "hello world!";
+| +	printf("%s", str);
+|  }
+|  #
+* Base commit
+  #
+  diff --git a/path1 b/path1
+  new file mode 100644
+  index 0000000..52be2a5
+  --- /dev/null
+  +++ b/path1
+  @@ -0,0 +1,4 @@
+  +void output()
+  +{
+  +	printf("hello world");
+  +}
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' > expected-pathall-graph <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index ccdf243..ccf8bcf 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,6 @@
+|  void func()
+|  {
+|QQ	int a = 10;
+|QQ	int b = 11;
+| -	int c;
+| -	c = 10 * (a + b);
+| +	printf("%d", a - b);
+|  }
+|  #
+* Change the 5th line of path0
+| #
+| diff --git a/path0 b/path0
+| index b0eb888..ccdf243 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+|  void func()
+|  {
+|QQ	int a = 10;
+|QQ	int b = 11;
+|QQ	int c;
+| -	c = a + b;
+| +	c = 10 * (a + b);
+|  }
+|  #
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+|  void func()
+|  {
+| -	int a = 0;
+| -	int b = 1;
+| +	int a = 10;
+| +	int b = 11;
+|QQ	int c;
+|QQ	c = a + b;
+|  }
+| diff --git a/path1 b/path1
+| index 52be2a5..cc54b12 100644
+| --- a/path1
+| +++ b/path1
+| @@ -1,4 +1,5 @@
+|  void output()
+|  {
+| -	printf("hello world");
+| +	const char *str = "hello world!";
+| +	printf("%s", str);
+|  }
+|  #
+* Base commit
+  #
+  diff --git a/path0 b/path0
+  new file mode 100644
+  index 0000000..fb33939
+  --- /dev/null
+  +++ b/path0
+  @@ -0,0 +1,7 @@
+  +void func()
+  +{
+  +	int a = 0;
+  +	int b = 1;
+  +	int c;
+  +	c = a + b;
+  +}
+  diff --git a/path1 b/path1
+  new file mode 100644
+  index 0000000..52be2a5
+  --- /dev/null
+  +++ b/path1
+  @@ -0,0 +1,4 @@
+  +void output()
+  +{
+  +	printf("hello world");
+  +}
+EOF
+
+sed 's/#$//' > expected-linenum-graph <<\EOF
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+|  {
+| -	int a = 0;
+| -	int b = 1;
+| +	int a = 10;
+| +	int b = 11;
+|  #
+* Base commit
+  #
+  diff --git a/path0 b/path0
+  new file mode 100644
+  index 0000000..fb33939
+  --- /dev/null
+  +++ b/path0
+  @@ -0,0 +2,3 @@
+  +{
+  +	int a = 0;
+  +	int b = 1;
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' > expected-always-graph <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index ccdf243..ccf8bcf 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+|  {
+|QQ	int a = 10;
+|QQ	int b = 11;
+|  #
+* Change the 5th line of path0
+| #
+| diff --git a/path0 b/path0
+| index b0eb888..ccdf243 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+|  {
+|QQ	int a = 10;
+|QQ	int b = 11;
+|  #
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+|  {
+| -	int a = 0;
+| -	int b = 1;
+| +	int a = 10;
+| +	int b = 11;
+|  #
+* Base commit
+  #
+  diff --git a/path0 b/path0
+  new file mode 100644
+  index 0000000..fb33939
+  --- /dev/null
+  +++ b/path0
+  @@ -0,0 +2,3 @@
+  +{
+  +	int a = 0;
+  +	int b = 1;
+EOF
+
+test_expect_success 'validate the path0 output.' '
+	test_cmp current-path0-graph expected-path0-graph
+'
+
+test_expect_success 'validate the path1 output.' '
+	test_cmp current-path1-graph expected-path1-graph
+'
+
+test_expect_success 'validate the all path output.' '
+	test_cmp current-pathall-graph expected-pathall-graph
+'
+
+test_expect_success 'validate graph output' '
+	test_cmp current-linenum-graph expected-linenum-graph
+'
+
+test_expect_success 'validate --full-line-diff output' '
+	test_cmp current-always-graph expected-always-graph
+'
+
+test_done
-- 
1.7.3.3.807.g6ee1f

^ permalink raw reply related

* [PATCH v6.1 7/8] log -L: add --full-line-diff option
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Bo Yang
In-Reply-To: <cover.1292366984.git.trast@student.ethz.ch>

From: Bo Yang <struggleyb.nku@gmail.com>

Always print the interesting ranges even if the current
commit does not change any line of it.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-log.txt |    4 ++++
 builtin/log.c             |    8 +++++++-
 line.c                    |   22 ++++++++++++++++------
 revision.c                |    2 ++
 revision.h                |    3 ++-
 5 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 7fcf6e7..f5769bf 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -79,6 +79,10 @@ include::line-range-format.txt[]
 You can specify this option more than once.
 
 
+--full-line-diff::
+	Always print the interesting range even if the current commit
+	does not change any line of the range.
+
 [\--] <path>...::
 	Show only commits that affect any of the specified paths. To
 	prevent confusion with options and branch names, paths may need
diff --git a/builtin/log.c b/builtin/log.c
index 342d4de..fa57306 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -99,6 +99,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 {
 	int i;
 	int decoration_given = 0;
+	static int full_line_diff;
 	struct userformat_want w;
 	static struct line_opt_callback_data line_cb = {0};
 
@@ -106,6 +107,9 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 		OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
 			     "Process line range n,m in file, counting from 1",
 			     log_line_range_callback),
+		OPT_BOOLEAN(0, "full-line-diff", &full_line_diff,
+			    "Always print the interesting range even if the \
+			    current commit does not change any line of it"),
 		OPT_END()
 	};
 
@@ -188,8 +192,10 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 	}
 
 	/* Test whether line level history is asked for */
-	if (rev->line_level_traverse)
+	if (rev->line_level_traverse) {
 		line_log_init(rev, line_cb.ranges);
+		rev->full_line_diff = full_line_diff;
+	}
 
 	setup_pager();
 }
diff --git a/line.c b/line.c
index 1a9a947..742c17f 100644
--- a/line.c
+++ b/line.c
@@ -1370,10 +1370,18 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra
 	/*
 	 * the ranges that touch no different file, in this case
 	 * the line number will not change, and of course we have
-	 * no sensible rang->pair since there is no diff run.
+	 * no sensible range->pair since there is no diff run.
 	 */
-	if (!one)
+	if (!one) {
+		if (rev->full_line_diff) {
+			chunk.two = two->data;
+			chunk.two_end = (const char *)two->data + two->size;
+			chunk.ltwo = 1;
+			chunk.range = range;
+			diff_flush_chunks(&rev->diffopt, &chunk);
+		}
 		return;
+	}
 
 	if (range->status == DIFF_STATUS_DELETED)
 		die("We are following an nonexistent file, interesting!");
@@ -1495,7 +1503,8 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)
 	struct strbuf *msgbuf;
 
 	if (!range || !(c->object.flags & NONTRIVIAL_MERGE ||
-			c->object.flags & NEED_PRINT))
+			c->object.flags & NEED_PRINT ||
+			rev->full_line_diff))
 		return;
 
 	if (rev->graph)
@@ -1516,7 +1525,7 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)
 		flush_nontrivial_merge(rev, nontrivial);
 	else {
 		while (range) {
-			if (range->diff)
+			if (range->diff || (range->nr && rev->full_line_diff))
 				diff_flush_filepair(rev, range);
 			range = range->next;
 		}
@@ -1573,7 +1582,7 @@ int line_log_walk(struct rev_info *rev)
 	/* Clear the flags */
 	while (list) {
 		list->item->object.flags &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |
-						NEED_PRINT | EVIL_MERGE);
+				NEED_PRINT | EVIL_MERGE);
 		list = list->next;
 	}
 
@@ -1593,7 +1602,8 @@ int line_log_walk(struct rev_info *rev)
 		}
 
 		if (commit->object.flags & NEED_PRINT ||
-		    commit->object.flags & NONTRIVIAL_MERGE)
+		    commit->object.flags & NONTRIVIAL_MERGE ||
+		    rev->full_line_diff)
 			line_log_flush(rev, commit);
 
 		clear_commit_line_range(rev, commit);
diff --git a/revision.c b/revision.c
index fbebf2f..85a60d0 100644
--- a/revision.c
+++ b/revision.c
@@ -1912,6 +1912,8 @@ int prepare_revision_walk(struct rev_info *revs)
 			return -1;
 	if (revs->topo_order)
 		sort_in_topological_order(&revs->commits, revs->lifo);
+	if (revs->full_line_diff)
+		revs->dense = 0;
 	if (revs->simplify_merges)
 		simplify_merges(revs);
 	if (revs->children.name)
diff --git a/revision.h b/revision.h
index 6100904..29babf3 100644
--- a/revision.h
+++ b/revision.h
@@ -73,7 +73,8 @@ struct rev_info {
 			bisect:1,
 			ancestry_path:1,
 			first_parent_only:1,
-			line_level_traverse:1;
+			line_level_traverse:1,
+			full_line_diff:1;
 
 	/* Diff flags */
 	unsigned int	diff:1,
-- 
1.7.3.3.807.g6ee1f

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox