Git development
 help / color / mirror / Atom feed
* [PATCH] builtin/branch.c: Use ALLOC_GROW instead of alloc_nr and xrealloc.
From: Thiago Farina @ 2010-12-14  1:59 UTC (permalink / raw)
  To: git

Signed-off-by: Thiago Farina <tfransosi@gmail.com>
---
 builtin/branch.c |    6 +-----
 1 files changed, 1 insertions(+), 5 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index 807355a..7f34fad 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -314,11 +314,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
 	}
 
 	/* Resize buffer */
-	if (ref_list->index >= ref_list->alloc) {
-		ref_list->alloc = alloc_nr(ref_list->alloc);
-		ref_list->list = xrealloc(ref_list->list,
-				ref_list->alloc * sizeof(struct ref_item));
-	}
+	ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
 
 	/* Record the new item */
 	newitem = &(ref_list->list[ref_list->index++]);
-- 
1.7.3.2.343.g7d43d

^ permalink raw reply related

* [PATCH 1/8] Refactor parse_loc
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.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.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..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. */
+        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.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
+'
+
 test_expect_success 'indent of line numbers, nine lines' '
 	git blame nine_lines >actual &&
 	test $(grep -c "  " actual) = 0
-- 
1.7.3.3.811.g76615

^ permalink raw reply related

* [PATCH 6/8] log -L: add --graph prefix before output
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.git.trast@student.ethz.ch>

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

Makes the line level log output look good when used
with the '--graph' option.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 line.c |   66 ++++++++++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 48 insertions(+), 18 deletions(-)

diff --git a/line.c b/line.c
index e766607..0b297e4 100644
--- a/line.c
+++ b/line.c
@@ -1242,6 +1242,13 @@ static void flush_lines(struct diff_options *opt, const char **ptr, const char *
 	const char *p = *ptr;
 	struct strbuf buf = STRBUF_INIT;
 	const char *reset;
+	char *line_prefix = "";
+	struct strbuf *msgbuf;
+
+	if (opt && opt->output_prefix) {
+		msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+		line_prefix = msgbuf->buf;
+	}
 
 	if (*color)
 		reset = diff_get_color_opt(opt, DIFF_RESET);
@@ -1264,7 +1271,7 @@ static void flush_lines(struct diff_options *opt, const char **ptr, const char *
 
 	while (*ptr < end && *lno <= elno) {
 		if (**ptr == '\n') {
-			fprintf(opt->file, "%s", buf.buf);
+			fprintf(opt->file, "%s%s", line_prefix, buf.buf);
 			if (*ptr - p)
 				fwrite(p, *ptr - p, 1, opt->file);
 			fprintf(opt->file, "%s\n", reset);
@@ -1274,7 +1281,7 @@ static void flush_lines(struct diff_options *opt, const char **ptr, const char *
 		(*ptr)++;
 	}
 	if (*lno <= elno) {
-		fprintf(opt->file, "%s", buf.buf);
+		fprintf(opt->file, "%s%s", line_prefix, buf.buf);
 		if (*ptr - p)
 			fwrite(p, *ptr - p, 1, opt->file);
 		fprintf(opt->file, "%s\n", reset);
@@ -1316,8 +1323,15 @@ 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);
+	char *line_prefix = "";
+	struct strbuf *msgbuf;
 	int i;
 
+	if (opt && opt->output_prefix) {
+		msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+		line_prefix = msgbuf->buf;
+	}
+
 	for (i = 0; i < range->nr; i++) {
 		struct line_range *r = range->ranges + i;
 		long lenp = r->pend - r->pstart + 1, pstart = r->pstart;
@@ -1325,8 +1339,8 @@ static void diff_flush_chunks(struct diff_options *opt, struct line_chunk *chunk
 		if (pstart == 0)
 			lenp = 0;
 
-		fprintf(opt->file, "%s@@ -%ld,%ld +%ld,%ld @@%s\n",
-			set, pstart, lenp, r->start, len, reset);
+		fprintf(opt->file, "%s%s@@ -%ld,%ld +%ld,%ld @@%s\n",
+			line_prefix, set, pstart, lenp, r->start, len, reset);
 
 		diff_flush_range(opt, chunk, r);
 	}
@@ -1345,6 +1359,13 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra
 	const char *reset = diff_get_color_opt(opt, DIFF_RESET);
 	struct line_chunk chunk;
 	int must_show_header;
+	char *line_prefix = "";
+	struct strbuf *msgbuf;
+
+	if (opt && opt->output_prefix) {
+		msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+		line_prefix = msgbuf->buf;
+	}
 
 	/*
 	 * the ranges that touch no different file, in this case
@@ -1378,21 +1399,26 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra
 	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);
+	strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix,
+			set, a_one, b_two, reset);
 	if (lbl[0][0] == '/') {
-		strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
+		strbuf_addf(&header, "%s%snew file mode %06o%s\n",
+			line_prefix, set, two->mode, reset);
 	} else if (lbl[1][0] == '/') {
-		strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+		strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n",
+			line_prefix, 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);
+			strbuf_addf(&header, "%s%sold mode %06o%s\n",
+				line_prefix, set, one->mode, reset);
+			strbuf_addf(&header, "%s%snew mode %06o%s\n",
+				line_prefix, 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);
+	fprintf(opt->file, "%s%s--- %s%s\n", line_prefix, set, lbl[0], reset);
+	fprintf(opt->file, "%s%s+++ %s%s\n", line_prefix, set, lbl[1], reset);
 	free((void *)a_one);
 	free((void *)b_two);
 
@@ -1446,12 +1472,13 @@ static void flush_nontrivial_merge(struct rev_info *rev,
 				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,
+				fprintf(opt->file, "%s%s@@ %ld,%ld @@%s\n",
+					line_prefix, frag, r->start,
 					r->end - r->start + 1, reset);
 				flush_lines(opt, &ptr, end, r->start, r->end,
 					&lno, new, ' ');
 			}
-			fprintf(opt->file, "\n");
+			fprintf(opt->file, "%s\n", line_prefix);
 		}
 		range = range->next;
 	}
@@ -1464,6 +1491,8 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)
 							&c->object);
 	struct log_info log;
 	struct diff_options *opt = &rev->diffopt;
+	char *line_prefix = "";
+	struct strbuf *msgbuf;
 
 	if (!range || !(c->object.flags & NONTRIVIAL_MERGE ||
 			c->object.flags & NEED_PRINT))
@@ -1476,11 +1505,12 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)
 	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 (opt && opt->output_prefix) {
+		msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+		line_prefix = msgbuf->buf;
+	}
+	fprintf(rev->diffopt.file, "%s\n", line_prefix);
 
 	if (c->object.flags & NONTRIVIAL_MERGE)
 		flush_nontrivial_merge(rev, nontrivial);
-- 
1.7.3.3.811.g76615

^ permalink raw reply related

* [PATCH 3/8] Export rewrite_parents() for 'log -L'
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.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.811.g76615

^ permalink raw reply related

* [PATCH v6 0/8] git log -L, cleaned up and (hopefully) fixed
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang

This is (finally) a reroll of Bo's GSoC project.  It's still not meant
for final inclusion, see below, but I think it would be great if it
got some exposure to the masses to get feedback.

The main changes:

* It now uses -L 1,2:README option syntax.  Yay!  Hopefully this
  removes the main stumbling block.  It's also far less code.

* I squashed and rearranged the entire series to taste.  This does
  mean that the bulk of the code comes just in three commits (4, 5 and
  8).  I saw no real reason to split any finer -- the existing splits
  resulted in commits that did not work or sometimes even compile.  In
  fact I was half tempted to merge 5 and 4, too.

* -M and -C now actually work.

* As a result of the option syntax change and unrelated commits that
  since went into 'next', a few patches were superfluous and have been
  dropped.

* Various cleanup and refactorings where I saw the opportunity.

My todo/blockers for inclusion on this:

* There's got to be an obvious blunder or three considering the time.

* Any bugs found by testers :-)

* The tests are still in a weird form and lack the usual (for
  diff-based work) corner cases.

* git-blame should learn -L 1,2:README syntax so that they can be
  interchanged.  (Based on discussions at GitTogether.  Plans for the
  other way around have been dropped.)

Further directions:

* More refactorings would be nice, if possible.  There are about four
  types of ranges right now, handled in subtly different ways, leading
  to lots of code.

* Performance optimizations would also help :-)

* (This is *really* far out:) We had a thread where we discussed
  possibilities to make it walk forward as well as backward in
  history.  This will require some careful refactoring of the
  mechanisms, but would be very cool.  (-L n,m,rev:file works as a
  syntax, so we're not painting ourselves into a corner here).


Bo Yang (8):
  Refactor parse_loc
  Export three functions from diff.c
  Export rewrite_parents() for 'log -L'
  Implement line-history search (git log -L)
  log -L: support parent rewriting
  log -L: add --graph prefix before output
  log -L: add --full-line-diff option
  log -L: implement move/copy detection (-M/-C)

 Documentation/blame-options.txt     |   19 +-
 Documentation/git-log.txt           |   22 +
 Documentation/line-range-format.txt |   18 +
 Makefile                            |    2 +
 builtin/blame.c                     |   99 +--
 builtin/log.c                       |   79 ++-
 diff.c                              |    6 +-
 diff.h                              |   17 +
 line.c                              | 2153 +++++++++++++++++++++++++++++++++++
 line.h                              |   72 ++
 revision.c                          |   22 +-
 revision.h                          |   23 +-
 t/t4301-log-line-single-history.sh  |  685 +++++++++++
 t/t4302-log-line-merge-history.sh   |  174 +++
 t/t4303-log-line-move-detect.sh     |  238 ++++
 t/t4304-log-line-copy-detect.sh     |  220 ++++
 t/t8003-blame.sh                    |    6 +
 17 files changed, 3730 insertions(+), 125 deletions(-)
 create mode 100644 Documentation/line-range-format.txt
 create mode 100644 line.c
 create mode 100644 line.h
 create mode 100755 t/t4301-log-line-single-history.sh
 create mode 100755 t/t4302-log-line-merge-history.sh
 create mode 100755 t/t4303-log-line-move-detect.sh
 create mode 100755 t/t4304-log-line-copy-detect.sh

-- 
1.7.3.3.811.g76615

^ permalink raw reply

* [PATCH 8/8] log -L: implement move/copy detection (-M/-C)
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.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 3feca02..34788b2 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..d0dda19
--- /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"
+'
+
+cat >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()
+|  {
+|  	int a = 0;
+|  	int b = 1;
+|  	int c;
+|  	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|  	printf("hello world");
+|  	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
+
+cat >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()
+|  {
+|  	int a = 0;
+|  	int b = 1;
+|  	int c;
+|  	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|  	printf("hello world");
+|  	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!");
+|  }
+|  
+* Change some lines of path0
+| 
+| diff --git a/path0 b/path0
+| index f5e09df..cd42622 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,5 +2,5 @@
+|  {
+|  	int a = 0;
+|  	int b = 1;
+|  	int c;
+|  	c = a + b;
+| @@ -11,2 +11,5 @@
+| +	int d = 3;
+| +	int e = 5;
+|  	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..a4a5177
--- /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 '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()
+|  {
+|  	int a = 0;
+|  	int b = 1;
+|  	int c;
+|  	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|  	printf("hello world");
+|  	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 '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()
+|  {
+|  	int a = 0;
+|  	int b = 1;
+|  	int c;
+|  	c = a + b;
+| -	int d = 3;
+| -	int e = 5;
+|  	printf("hello world");
+|  	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!");
+|  }
+|  #
+* 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;
+|  	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.811.g76615

^ permalink raw reply related

* [PATCH 7/8] log -L: add --full-line-diff option
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.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 0b297e4..3feca02 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.811.g76615

^ permalink raw reply related

* [PATCH 5/8] log -L: support parent rewriting
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.git.trast@student.ethz.ch>

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

Walking forward through history (i.e., topologically earliest
commits first), we filter the parent list of every commit as
follows. Consider a parent P:
 - If P touches any of the interesting line ranges, we keep it.
 - If P is a merge and it takes all the interesting line ranges
   from one of its parents, P is rewritten to this parent, else
   we keep P.
 - Otherwise, P is rewritten to its (only) parent P^.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 line.c                            |  209 +++++++++++++++++++++++++++++++------
 revision.c                        |    1 +
 revision.h                        |    5 +-
 t/t4302-log-line-merge-history.sh |  174 ++++++++++++++++++++++++++++++
 4 files changed, 357 insertions(+), 32 deletions(-)
 create mode 100755 t/t4302-log-line-merge-history.sh

diff --git a/line.c b/line.c
index fe7609c..e766607 100644
--- a/line.c
+++ b/line.c
@@ -10,6 +10,7 @@
 #include "xdiff-interface.h"
 #include "strbuf.h"
 #include "log-tree.h"
+#include "graph.h"
 #include "line.h"
 
 struct print_range {
@@ -33,12 +34,6 @@ struct line_range {
 };
 
 /*
- * Eek-a-global
- */
-
-static int limited;
-
-/*
  * These could be in line.h but we put them here so the functions can
  * be static.
  */
@@ -596,14 +591,14 @@ static void add_line_range(struct rev_info *revs, struct commit *commit,
 {
 	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);
+	ret = lookup_decoration(&revs->line_range, &commit->object);
+	if (ret && r)
+		diff_line_range_merge(ret, r);
+	else
+		add_decoration(&revs->line_range, &commit->object, r);
+
+	if (r)
 		commit->object.flags |= RANGE_UPDATE;
-	}
 }
 
 static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
@@ -687,6 +682,22 @@ void map_lines(long p_start, long p_end, long t_start, long t_end,
 		return;
 	}
 
+	if (start == t_start) {
+		*o_start = p_start;
+		*o_end = p_start + (end - start);
+		if (*o_end > p_end)
+			*o_end = p_end;
+		return;
+	}
+
+	if (end == t_end) {
+		*o_start = p_end - (end - start);
+		if (*o_start < p_start)
+			*o_start = p_start;
+		*o_end = p_end;
+		return;
+	}
+
 	/*
 	 * A heuristic for lines mapping:
 	 *
@@ -920,7 +931,7 @@ static void load_tree_desc(struct tree_desc *desc, void **tree,
  * 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,
+static int 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)
 {
@@ -1065,10 +1076,22 @@ static void assign_range_to_parent(struct rev_info *rev, struct commit *commit,
 			prev_range->next = NULL;
 	}
 
+	if (!map)
+		goto out;
+
 	if (final_range) {
 		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 */
@@ -1076,10 +1099,13 @@ static void assign_range_to_parent(struct rev_info *rev, struct commit *commit,
 	if (diff)
 		commit->object.flags |= NEED_PRINT;
 
+out:
 	if (tree1)
 		free(tree1);
 	if (tree2)
 		free(tree2);
+
+	return diff;
 }
 
 static void diff_update_parent_range(struct rev_info *rev,
@@ -1096,13 +1122,21 @@ static void diff_update_parent_range(struct rev_info *rev,
 	assign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);
 }
 
+struct commit_state {
+	struct diff_line_range *range;
+	struct object obj;
+};
+
 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;
+	struct decoration parents_state;
+	struct commit_state *state = NULL;
 	int nontrivial = 0;
 
+	memset(&parents_state, 0, sizeof(parents_state));
 	/*
 	 * If we are in linear history, update range and flush the patch if
 	 * necessary
@@ -1119,23 +1153,76 @@ static void assign_parents_range(struct rev_info *rev, struct commit *commit)
 	parents = commit->parents;
 	while (parents) {
 		struct commit *p = parents->item;
-		assign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);
+		int diff = 0;
+		struct diff_line_range *origin_range = lookup_line_range(rev, p);
+		if (origin_range)
+			origin_range = diff_line_range_clone_deeply(origin_range);
+
+		state = xmalloc(sizeof(*state));
+		state->range = origin_range;
+		state->obj = p->object;
+		add_decoration(&parents_state, &p->object, state);
+		diff = assign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);
+		/* Since all the ranges comes from this parent, we can ignore others */
+		if (diff == 0) {
+			/* restore the state of parents before this one */
+			parents = commit->parents;
+			while (parents->item != p) {
+				struct commit_list *list = parents;
+				parents = parents->next;
+				clear_commit_line_range(rev, list->item);
+				state = lookup_decoration(&parents_state, &list->item->object);
+				add_decoration(&parents_state, &list->item->object, NULL);
+				add_line_range(rev, list->item, state->range);
+				list->item->object = state->obj;
+				free(state);
+				free(list);
+			}
+
+			commit->parents = parents;
+			parents = parents->next;
+			commit->parents->next = NULL;
+
+			/* free the non-use commit_list */
+			while (parents) {
+				struct commit_list *list = parents;
+				parents = parents->next;
+				free(list);
+			}
+			goto out;
+		}
+		/* take the ranges from 'commit', try to detect nontrivial merge */
 		assign_range_to_parent(rev, commit, p, evil, &rev->diffopt, 0);
 		parents = parents->next;
 	}
 
+	commit->object.flags |= NONTRIVIAL_MERGE;
 	/*
 	 * yes, this must be an evil merge.
 	 */
 	range = evil;
 	while (range) {
 		if (range->nr) {
-			commit->object.flags |= NEED_PRINT | EVIL_MERGE;
+			commit->object.flags |= EVIL_MERGE;
 			nontrivial = 1;
 		}
 		range = range->next;
 	}
 
+out:
+	/* Never print out any diff for a merge commit */
+	commit->object.flags &= ~NEED_PRINT;
+
+	parents = commit->parents;
+	while (parents) {
+		state = lookup_decoration(&parents_state, &parents->item->object);
+		if (state) {
+			free_diff_line_ranges(state->range);
+			free(state);
+		}
+		parents = parents->next;
+	}
+
 	if (nontrivial)
 		add_decoration(&rev->nontrivial_merge, &commit->object, evil);
 	else
@@ -1327,16 +1414,36 @@ static void flush_nontrivial_merge(struct rev_info *rev,
 	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);
+	char *line_prefix = "";
+	struct strbuf *msgbuf;
+	int evil = 0;
+	struct diff_line_range *r = range;
+
+	if (opt && opt->output_prefix) {
+		msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+		line_prefix = msgbuf->buf;
+	}
 
-	fprintf(opt->file, "%s%s%s\n", meta, "nontrivial merge found", reset);
+	while (r) {
+		if (r->nr)
+			evil = 1;
+		r = r->next;
+	}
+
+	if (!evil)
+		return;
+
+	fprintf(opt->file, "%s%s%s%s\n", line_prefix, 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;
+			const char *end = (const char *)range->spec->data + range->spec->size;
 			int i = 0;
-			fprintf(opt->file, "%s%s%s\n\n", meta, range->spec->path, reset);
+			fprintf(opt->file, "%s%s%s%s\n", line_prefix,
+				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,
@@ -1353,12 +1460,17 @@ static void flush_nontrivial_merge(struct rev_info *rev,
 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 diff_line_range *nontrivial = lookup_decoration(&rev->nontrivial_merge,
+							&c->object);
 	struct log_info log;
+	struct diff_options *opt = &rev->diffopt;
 
-	if (!range)
+	if (!range || !(c->object.flags & NONTRIVIAL_MERGE ||
+			c->object.flags & NEED_PRINT))
 		return;
 
+	if (rev->graph)
+		graph_update(rev->graph, c);
 	log.commit = c;
 	log.parent = NULL;
 	rev->loginfo = &log;
@@ -1370,13 +1482,42 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)
 	 */
 	fprintf(rev->diffopt.file, "\n");
 
-	if (c->object.flags & EVIL_MERGE)
-		return flush_nontrivial_merge(rev, nontrivial);
+	if (c->object.flags & NONTRIVIAL_MERGE)
+		flush_nontrivial_merge(rev, nontrivial);
+	else {
+		while (range) {
+			if (range->diff)
+				diff_flush_filepair(rev, range);
+			range = range->next;
+		}
+	}
 
-	while (range) {
-		if (range->diff)
-			diff_flush_filepair(rev, range);
-		range = range->next;
+	while (rev->graph && !graph_is_commit_finished(rev->graph)) {
+		struct strbuf sb;
+		strbuf_init(&sb, 0);
+		graph_next_line(rev->graph, &sb);
+		fputs(sb.buf, opt->file);
+	}
+}
+
+static enum rewrite_result rewrite_one(struct rev_info *rev, struct commit **pp)
+{
+	struct diff_line_range *r = NULL;
+	struct commit *p;
+	while (1) {
+		p = *pp;
+		if (p->object.flags & RANGE_UPDATE)
+			assign_parents_range(rev, p);
+		if (p->object.flags & NEED_PRINT ||
+		    p->object.flags & NONTRIVIAL_MERGE)
+			return rewrite_one_ok;
+		if (!p->parents)
+			return rewrite_one_noparents;
+
+		r = lookup_line_range(rev, p);
+		if (!r)
+			return rewrite_one_noparents;
+		*pp = p->parents->item;
 	}
 }
 
@@ -1401,7 +1542,8 @@ int line_log_walk(struct rev_info *rev)
 	}
 	/* Clear the flags */
 	while (list) {
-		list->item->object.flags &= ~(RANGE_UPDATE | EVIL_MERGE | NEED_PRINT);
+		list->item->object.flags &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |
+						NEED_PRINT | EVIL_MERGE);
 		list = list->next;
 	}
 
@@ -1413,7 +1555,15 @@ int line_log_walk(struct rev_info *rev)
 		if (commit->object.flags & RANGE_UPDATE)
 			assign_parents_range(rev, commit);
 
-		if (commit->object.flags & NEED_PRINT)
+		if (commit->object.flags & NEED_PRINT ||
+		    commit->object.flags & NONTRIVIAL_MERGE) {
+			if (rewrite_parents(rev, commit, rewrite_one))
+				die("Can't rewrite parent for commit %s",
+					sha1_to_hex(commit->object.sha1));
+		}
+
+		if (commit->object.flags & NEED_PRINT ||
+		    commit->object.flags & NONTRIVIAL_MERGE)
 			line_log_flush(rev, commit);
 
 		clear_commit_line_range(rev, commit);
@@ -1431,4 +1581,3 @@ int line_log_walk(struct rev_info *rev)
 
 	return 0;
 }
-
diff --git a/revision.c b/revision.c
index 369ec56..fbebf2f 100644
--- a/revision.c
+++ b/revision.c
@@ -13,6 +13,7 @@
 #include "decorate.h"
 #include "log-tree.h"
 #include "string-list.h"
+#include "line.h"
 
 volatile show_early_output_fn_t show_early_output;
 
diff --git a/revision.h b/revision.h
index 585b15f..6100904 100644
--- a/revision.h
+++ b/revision.h
@@ -16,8 +16,9 @@
 #define SYMMETRIC_LEFT	(1u<<8)
 #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 NONTRIVIAL_MERGE	(1u<<11)
+#define EVIL_MERGE	(1u<<12)
+#define ALL_REV_FLAGS	((1u<<13)-1)
 
 #define DECORATE_SHORT_REFS	1
 #define DECORATE_FULL_REFS	2
diff --git a/t/t4302-log-line-merge-history.sh b/t/t4302-log-line-merge-history.sh
new file mode 100755
index 0000000..0c086e0
--- /dev/null
+++ b/t/t4302-log-line-merge-history.sh
@@ -0,0 +1,174 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test git log -L with merge commit'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+cat >path0 <<\EOF
+void func()
+{
+	printf("hello");
+}
+EOF
+
+test_expect_success 'Add path0 and commit.' '
+	git add path0 &&
+	git commit -m "Base commit"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	printf("hello earth");
+}
+EOF
+
+test_expect_success 'Change path0 in master.' '
+	git add path0 &&
+	git commit -m "Change path0 in master"
+'
+
+test_expect_success 'Make a new branch from the base commit' '
+	git checkout -b feature master^
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	print("hello moon");
+}
+EOF
+
+test_expect_success 'Change path0 in feature.' '
+	git add path0 &&
+	git commit -m "Change path0 in feature"
+'
+
+test_expect_success 'Merge the master to feature' '
+	! git merge master
+'
+
+cat >path0 <<\EOF
+void func()
+{
+	printf("hello earth and moon");
+}
+EOF
+
+test_expect_success 'Resolve the conflict' '
+	git add path0 &&
+	git commit -m "Merge two branches"
+'
+
+test_expect_success 'Show the line level log of path0' '
+	git log --pretty=format:%s%n%b -L /func/,/^}/:path0 > current
+'
+
+cat >expected <<\EOF
+Merge two branches
+
+nontrivial merge found
+path0
+@@ 3,1 @@
+ 	printf("hello earth and moon");
+
+
+Change path0 in master
+
+diff --git a/path0 b/path0
+index 56aeee5..11e66c5 100644
+--- a/path0
++++ b/path0
+@@ -1,4 +1,4 @@
+ void func()
+ {
+-	printf("hello");
++	printf("hello earth");
+ }
+
+Change path0 in feature
+
+diff --git a/path0 b/path0
+index 56aeee5..258fced 100644
+--- a/path0
++++ b/path0
+@@ -1,4 +1,4 @@
+ void func()
+ {
+-	printf("hello");
++	print("hello moon");
+ }
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..56aeee5
+--- /dev/null
++++ b/path0
+@@ -0,0 +1,4 @@
++void func()
++{
++	printf("hello");
++}
+EOF
+
+sed 's/#$//' > expected-graph <<\EOF
+*   Merge two branches
+|\  #
+| | #
+| | nontrivial merge found
+| | path0
+| | @@ 3,1 @@
+| |  	printf("hello earth and moon");
+| | #
+| |   #
+| * Change path0 in master
+| | #
+| | diff --git a/path0 b/path0
+| | index 56aeee5..11e66c5 100644
+| | --- a/path0
+| | +++ b/path0
+| | @@ -3,1 +3,1 @@
+| | -	printf("hello");
+| | +	printf("hello earth");
+| |   #
+* | Change path0 in feature
+|/  #
+|   #
+|   diff --git a/path0 b/path0
+|   index 56aeee5..258fced 100644
+|   --- a/path0
+|   +++ b/path0
+|   @@ -3,1 +3,1 @@
+|   -	printf("hello");
+|   +	print("hello moon");
+|  #
+* Base commit
+  #
+  diff --git a/path0 b/path0
+  new file mode 100644
+  index 0000000..56aeee5
+  --- /dev/null
+  +++ b/path0
+  @@ -0,0 +3,1 @@
+  +	printf("hello");
+EOF
+
+test_expect_success 'Show the line log of the 2 line of path0 with graph' '
+	git log --pretty=format:%s%n%b --graph -L 3,+1:path0 > current-graph
+'
+
+test_expect_success 'validate the output.' '
+	test_cmp current expected
+'
+
+test_expect_success 'validate the graph output.' '
+	test_cmp current-graph expected-graph
+'
+
+test_done
-- 
1.7.3.3.811.g76615

^ permalink raw reply related

* [PATCH 4/8] Implement line-history search (git log -L)
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.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                              | 1332 ++++++++++++++++++++++++++++++++++-
 line.h                              |   49 ++
 revision.c                          |    6 +
 revision.h                          |   11 +-
 t/t4301-log-line-single-history.sh  |  685 ++++++++++++++++++
 9 files changed, 2187 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 29898ec..fe7609c 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,1205 @@ 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..d7ae3e0
--- /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"
+'
+
+cat >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()
+ {
+ 	int a = 10;
+ 	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()
+ {
+ 	int a = 10;
+ 	int b = 11;
+ 	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;
+ 	int c;
+ 	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
+
+cat >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()
+ {
+ 	int a = 10;
+ 	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()
+ {
+ 	int a = 10;
+ 	int b = 11;
+ 	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;
+ 	int c;
+ 	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
+
+cat >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 @@
+ {
+ 	int a = 10;
+ 	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 @@
+ {
+ 	int a = 10;
+ 	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 '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()
+|  {
+|  	int a = 10;
+|  	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()
+|  {
+|  	int a = 10;
+|  	int b = 11;
+|  	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;
+|  	int c;
+|  	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 '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()
+|  {
+|  	int a = 10;
+|  	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()
+|  {
+|  	int a = 10;
+|  	int b = 11;
+|  	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;
+|  	int c;
+|  	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 '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 @@
+|  {
+|  	int a = 10;
+|  	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 @@
+|  {
+|  	int a = 10;
+|  	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.811.g76615

^ permalink raw reply related

* [PATCH 2/8] Export three functions from diff.c
From: Thomas Rast @ 2010-12-14  2:03 UTC (permalink / raw)
  To: git; +Cc: Bo Yang
In-Reply-To: <cover.1292291624.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 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);
@@ -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.811.g76615

^ permalink raw reply related

* git-fixup-assigner.perl -- automatically decide where to "fixup!"
From: Thomas Rast @ 2010-12-14  2:09 UTC (permalink / raw)
  To: git

While cleaning up the 'log -L' series I gathered a large number of
little fixups, and decided it would be smart if git could
automatically figure out where to put them.

It works like this:

* Split the diff by hunk.  I'm using -U1 here for finer splits, but it
  could be tunable.

* For each hunk, run blame to find out which commit's lines were
  affected.

* Group the hunks by this commit, and output them with a suitable
  command to make a fixup.

My git-fixup is

  $ g config alias.fixup
  !sh -c 'r=$1; git commit -m"fixup! $(git log -1 --pretty=%s $r)"' -

so that is "suitable".

You would run it with the changes unstaged in your tree as

  ./git-fixup-assigner.perl > fixups

and can then review with 'less fixups', or run 'sh fixups' to commit
them.

It's certainly not perfect, notably the detection logic should ignore
context, but it got the job done.

--- 8< ---
#!/usr/bin/perl

use warnings;
use strict;

sub parse_hunk_header {
        my ($line) = @_;
        my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
            $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
        $o_cnt = 1 unless defined $o_cnt;
        $n_cnt = 1 unless defined $n_cnt;
        return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
}

sub find_commit {
	my ($file, $begin, $end) = @_;
	my $blame;
	open($blame, '-|', 'git', '--no-pager', 'blame', 'HEAD', "-L$begin,$end", $file) or die;
	my %candidate;
	while (<$blame>) {
		$candidate{$1} += 1 if /^([0-9a-f]+)/;
	}
	close $blame or die;
	my @sorted = sort { $candidate{$b} <=> $candidate{$a} } keys %candidate;
	if (1 < scalar @sorted) {
		print STDERR "ambiguous split $file:$begin..$end\n";
		foreach my $c (@sorted) {
			print STDERR "\t$candidate{$c}\t$c\n";
		}
	}
	return $sorted[0];
}

my $diff;
open($diff, '-|', 'git', '--no-pager', 'diff', '-U1') or die;

my %by_commit;
my @cur_hunk = ();
my $cur_commit;
my ($filename, $prefilename, $postfilename);

while (<$diff>) {
        if (m{^diff --git ./(.*) ./\1$}) {
		if (@cur_hunk) {
			push @{$by_commit{$cur_commit}{$filename}}, @cur_hunk;
			@cur_hunk = ();
		}
		$filename = $1;
                $prefilename = "./" . $1;
                $postfilename = "./" . $1;
	} elsif (m{^index}) {
		# ignore
        } elsif (m{^new file}) {
		$prefilename = '/dev/null';
        } elsif (m{^delete file}) {
		$postfilename = '/dev/null';
        } elsif (m{^--- $prefilename$}) {
        } elsif (m{^\+\+\+ $postfilename$}) {
        } elsif (m{^@@ }) {
		if (@cur_hunk) {
			push @{$by_commit{$cur_commit}{$filename}}, @cur_hunk;
			@cur_hunk = ();
		}
		push @cur_hunk, $_;
		die "I don't handle this diff" if ($prefilename ne $postfilename);
                my ($o_ofs, $o_cnt, $n_ofs, $n_cnt)
                        = parse_hunk_header($_);
                my $o_end = $o_ofs + $o_cnt - 1;
		$cur_commit = find_commit($filename, $o_ofs, $o_end);
        } elsif (m{^[-+ \\]}) {
		push @cur_hunk, $_;
	} else {
		die "unhandled diff line: '$_'";
	}
}

close $diff or die;

if (@cur_hunk) {
	push @{$by_commit{$cur_commit}{$filename}}, @cur_hunk;
	@cur_hunk = ();
}

print "#!/bin/sh\n\n";

foreach my $commit (keys %by_commit) {
	print "git apply --cached <<EOF\n";
	foreach my $filename (keys %{$by_commit{$commit}}) {
		print "diff --git a/$filename b/$filename\n";
		print "--- a/$filename\n";
		print "+++ b/$filename\n";
		print @{$by_commit{$commit}{$filename}};
	}
	print "EOF\n\n";
	print "git fixup $commit\n\n";
}
--- >8 ---

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

^ permalink raw reply

* Re: What's cooking in git.git (Dec 2010, #04; Mon, 13)
From: Junio C Hamano @ 2010-12-14  2:20 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git
In-Reply-To: <AANLkTikGneq+zeN5Z5XrUseX4Arevft67U7pV+h+5Tr+@mail.gmail.com>

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Mon, Dec 13, 2010 at 09:34, Junio C Hamano <gitster@pobox.com> wrote:
>
>> Needs a bit more minor work to get the basic code structure right.
>
> And I'm still not sure (see earlier replies to "What's Cooking" posts)
> what needs to be done to make it better.

One open question was why you do not want to move 'LIB_OBJS += gettext.o'
away from the LIB_OBJS section down to the configuration evaluation
section, i.e., why gettext.o would be different from block-sha1/sha1.o.

(sorry for a delayed and short response --- have been firefighting
elsewhere).

^ permalink raw reply

* Re: [PATCH 09/19] tree-diff.c: reserve space in "base" for pathname concatenation
From: Nguyen Thai Ngoc Duy @ 2010-12-14  5:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vlj3t35ol.fsf@alter.siamese.dyndns.org>

2010/12/14 Junio C Hamano <gitster@pobox.com>:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> This patch make sure that "base" parameter is writable. The callees
>> are free to modify it as long as base remains the same before
>> entering and after leaving the callee.
>>
>> This avoids quite a bit of malloc and memcpy().
>>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>
> I like what I see in this patch in general, but there is nothing that
> guarantees that you are "reserving" enough space.  Doesn't the buffer
> overflow with deep enough trees?

All paths should not exceed PATH_MAX, right? That's my assumption. If
it's wrong, then, well.. we need another way.
-- 
Duy

^ permalink raw reply

* Re: [PATCH 04/19] Convert struct diff_options to use struct pathspec
From: Nguyen Thai Ngoc Duy @ 2010-12-14  5:02 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vhbeh1oru.fsf@alter.siamese.dyndns.org>

2010/12/14 Junio C Hamano <gitster@pobox.com>:
> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
>
>> diff --git a/diff-lib.c b/diff-lib.c
>> index 392ce2b..3b809f2 100644
>> --- a/diff-lib.c
>> +++ b/diff-lib.c
>> @@ -501,7 +501,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
>>       active_nr = dst - active_cache;
>>
>>       init_revisions(&revs, NULL);
>> -     revs.prune_data = opt->paths;
>> +     revs.prune_data = opt->pathspec.raw;
>>       tree = parse_tree_indirect(tree_sha1);
>>       if (!tree)
>>               die("bad tree object %s", sha1_to_hex(tree_sha1));
>
> Hopefully the prune_data will become opt->pathspec not "raw" and use your
> generalied/unified pathspec matching code in later patches in the series,
> yes?

Yes. But that needs closer look. "prune_data" is void* and casted in
many places. Compiler won't catch mistyping for us.
-- 
Duy

^ permalink raw reply

* Re: [PATCH 15/19] pathspec: add match_pathspec_depth()
From: Nguyen Thai Ngoc Duy @ 2010-12-14  5:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vzks9zd3f.fsf@alter.siamese.dyndns.org>

2010/12/14 Junio C Hamano <gitster@pobox.com>:
> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
>
>> match_pathspec_depth() is similar to match_pathspec() except that it
>> can take depth limit.
>>
>> In long term, match_pathspec() should be removed in favor of this
>> function.
>
> Hmm, this strongly suggests that match_pathspec() should take "const
> struct pathspec *" which already contains the necessary information and
> more, including the depth limit, no?

Good idea. Thanks!

>> +int match_pathspec_depth(const char **pathspec, int max_depth,
>> +                      const char *name, int namelen,
>> +                      int prefix, char *seen)
>> +{
>> +     int i, retval = 0;
>> +
>> +     if (!pathspec) {
>> +             if (max_depth == -1)
>> +                     return MATCHED_RECURSIVELY;
>> +
>> +             if (within_depth(name, namelen, 0, max_depth))
>> +                     return MATCHED_EXACTLY;
>
> Why the difference between _RECURSIVELY and _EXACTLY here?  If you have a
> five-level deep project and give max-depth of 1000, shouldn't you get the
> same result as you run the same command with unlimited depth?

But if max-depth is 5 and the project is 1000-level deep, it should
return _EXACTLY, not _RECURSIVELY, right?
-- 
Duy

^ permalink raw reply

* Re: [PATCH 09/19] tree-diff.c: reserve space in "base" for pathname concatenation
From: Junio C Hamano @ 2010-12-14  5:32 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy; +Cc: git
In-Reply-To: <AANLkTi=5QgkBd434=Z7MCA_ZNZCXVyXJYh_fd1A+B2ue@mail.gmail.com>

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

> All paths should not exceed PATH_MAX, right?

Your PATH_MAX may be a lot shorter than the PATH_MAX on the system I
created my trees on that you are reading.  Besides, you can create
arbitrarily deep tree structure without busting the limit on the
filesystem by writing tree objects by hand.

In other words, all paths shouldn't exceed PATH_MAX, but the responsiblity
to verify that is _yours_.

^ permalink raw reply

* Re: Revert-style merge/Working tree-only checkout?
From: Konstantin Khomoutov @ 2010-12-14  5:46 UTC (permalink / raw)
  To: Yuriy Romanenko; +Cc: Git Mailing List
In-Reply-To: <AANLkTi=ioX25aqXg-yWDA0oXBTATkFe+J25g-dB7-psS@mail.gmail.com>

On Mon, Dec 13, 2010 at 03:30:00PM -0800, Yuriy Romanenko wrote:

> I am somewhat new to Git and I keep running into having to accomplish
> a certain task and reading through the documentation I can't seem to
> find any way of doing this easily.
> 
> The problem is when branches diverge and I want to sync a branch to
> another branch with full overwrite, but maintain history and maintain
> separate branches.
> 
> For example, say there is a branch "master" and I create a branch "b1"
> from master at some point. After this, there are 5 commits
> (C1,C2,C3,C4,C5) to master and
> 17 commits to b1 (let's call them cb1, cb2, cb3, ..., cb17). Say I
> want to create an 18-th commit to "b1" that makes it identical to the
> C5 (current) state of master. Essentially a single commit wipe of
> changes cb1 -> cb17 as well as application of C1->C5. So far I have
> found one way of accomplishing this, but it is difficult, error prone,
> slow and I just plain don't like it. I feel like there should be an
> easier way.
> 
> What I currently do:
> 
> $ rm -rf *
> $ git checkout -f master
> $ tar -cvzf /tmp/master.tar.gz *
> $ git checkout b1
> $ rm -rf *
> $ tar -xvzf /tmp/master.tar.gz
> $ git add
> $ git commit -a
> $ git merge master

I think this method would work better instead:

    $ git checkout b1
(2) $ git read-tree --reset master
(3) $ git checkout-index --all --force
    $ git commit -m "Bring b1 to the state of master"
(4) $ git merge --no-ff master

In this sequence, (2) makes the index contain the same state as
the tip of the master branch (you can also use more direct name,
master^{tree}, if you like), then (3) throws away any changes appeared
in the work tree after resetting the index. As the last step, you record
explicit merge to indicate that you converged histories back.

> I've considered doing something like the following
> 
> $ git checkout b1
> $ git revert b1~17..b1
> $ git merge master
> 
> but it also seems wrong, and requires me to count the submits by hand,
To not count commits by hand, you could use `git merge-base HEAD master`
to find the last commit b1 and master shared just before diverging.

> which seems silly --> I'm not actually reverting anything. I don't
> know if this would even work.
This is questionable -- like any VCS, Git was supposedly designed to
track evolving history, and you want to somehow make it track
"devolving" history at times. So, if we would opt not to play powerful
tricks with Git but rather go a "normal" VCS way, you would naturally
have to revert your changes to explicitly bring b1 to master.

^ permalink raw reply

* Re: Referring to other repositories
From: Konstantin Khomoutov @ 2010-12-14  5:58 UTC (permalink / raw)
  To: Maaartin; +Cc: git
In-Reply-To: <loom.20101214T011043-268@post.gmane.org>

On Tue, Dec 14, 2010 at 01:03:20AM +0000, Maaartin wrote:

> I'm using multiple repositories corresponding to different projects. Currently, 
> all the projects reside in different subdirectories of the same directory, like
> work/A
> work/B
> work/C
> There are some dependencies like B and C depending on A.
[...]
> I know about git submodule, however IIUIC it needs the other project to become 
> a subdirectory of the master, but I can hardly make A to subdirectory of both B 
> and C (I'm a poor Windows user, so no symlinks).
[...]
> So it looks like using submodules is no option, but I hope you tell me that I'm 
> wrong.
Windows supports directory-only symlinks on NTFS >= 3.0 (starting with
Windows 2000, that is), see [1], and it seems that since Vista there are
true symlinks (did not have a chance to use them yet).
I'm not sure if msysgit will play nicely with them, but at least you
could try.

[...]

1. http://en.wikipedia.org/wiki/NTFS_junction_point

^ permalink raw reply

* Re: [PATCH 09/19] tree-diff.c: reserve space in "base" for pathname concatenation
From: Nguyen Thai Ngoc Duy @ 2010-12-14  7:10 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vvd2wvs0m.fsf@alter.siamese.dyndns.org>

On Tue, Dec 14, 2010 at 12:32 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
>
>> All paths should not exceed PATH_MAX, right?
>
> Your PATH_MAX may be a lot shorter than the PATH_MAX on the system I
> created my trees on that you are reading.  Besides, you can create
> arbitrarily deep tree structure without busting the limit on the
> filesystem by writing tree objects by hand.
>
> In other words, all paths shouldn't exceed PATH_MAX, but the responsiblity
> to verify that is _yours_.

OK. I can check and die if any path exceeds PATH_MAX.
-- 
Duy

^ permalink raw reply

* Re: What's cooking in git.git (topics)
From: Johannes Sixt @ 2010-12-14  7:23 UTC (permalink / raw)
  To: Yaroslav Halchenko; +Cc: Junio C Hamano, git
In-Reply-To: <20101213214628.GA13447@onerussian.com>

Am 12/13/2010 22:46, schrieb Yaroslav Halchenko:
> On Mon, 13 Dec 2010, Junio C Hamano wrote:
>> But for such a use case, "git read-tree -m -u 0.2" would work just as
>> well, and discussion ended there ;-)
> 
> hm -- read-tree sounded like yet another unknown to me feature of GIT I
> was trying desperately to discover ;)  unfortunately it doesn't produce a merge
> for me :-/ -- just a simple commit with the state taken from the other tree:

How about:

  git merge --no-commit -s ours 0.2
  git read-tree -m -u 0.2
  git commit -m "Reset to 0.2"

-- Hannes

^ permalink raw reply

* Re: [PATCH 09/19] tree-diff.c: reserve space in "base" for pathname concatenation
From: Johannes Sixt @ 2010-12-14  7:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Nguyen Thai Ngoc Duy, git
In-Reply-To: <7vvd2wvs0m.fsf@alter.siamese.dyndns.org>

Am 12/14/2010 6:32, schrieb Junio C Hamano:
> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
> 
>> All paths should not exceed PATH_MAX, right?
> 
> Your PATH_MAX may be a lot shorter than the PATH_MAX on the system I
> created my trees on that you are reading.

And that is not just gray theory: On Windows, PATH_MAX is whopping 260
characters!

-- Hannes

^ permalink raw reply

* Re: [PATCH 09/19] tree-diff.c: reserve space in "base" for pathname concatenation
From: Nguyen Thai Ngoc Duy @ 2010-12-14  7:43 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: Junio C Hamano, git
In-Reply-To: <4D071DAA.3070400@viscovery.net>

On Tue, Dec 14, 2010 at 2:32 PM, Johannes Sixt <j.sixt@viscovery.net> wrote:
> Am 12/14/2010 6:32, schrieb Junio C Hamano:
>> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
>>
>>> All paths should not exceed PATH_MAX, right?
>>
>> Your PATH_MAX may be a lot shorter than the PATH_MAX on the system I
>> created my trees on that you are reading.
>
> And that is not just gray theory: On Windows, PATH_MAX is whopping 260
> characters!

Does that mean Windows won't accept any path longer than that, or
PATH_MAX is defined just for fun?
-- 
Duy

^ permalink raw reply

* Re: [PATCH 09/19] tree-diff.c: reserve space in "base" for pathname concatenation
From: Johannes Sixt @ 2010-12-14  8:21 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy; +Cc: Junio C Hamano, git
In-Reply-To: <AANLkTimzmq8gUpXUZ801Q+pua_mZMHTJ6wYt_CwcTr3v@mail.gmail.com>

Am 12/14/2010 8:43, schrieb Nguyen Thai Ngoc Duy:
> On Tue, Dec 14, 2010 at 2:32 PM, Johannes Sixt <j.sixt@viscovery.net> wrote:
>> Am 12/14/2010 6:32, schrieb Junio C Hamano:
>>> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
>>>
>>>> All paths should not exceed PATH_MAX, right?
>>>
>>> Your PATH_MAX may be a lot shorter than the PATH_MAX on the system I
>>> created my trees on that you are reading.
>>
>> And that is not just gray theory: On Windows, PATH_MAX is whopping 260
>> characters!
> 
> Does that mean Windows won't accept any path longer than that, or
> PATH_MAX is defined just for fun?

It is the limit for path names that we can have without MAJOR extra
efforts. It is not a limit implied by the file system.

-- Hannes

^ permalink raw reply

* Re: Please pull gitk.git master branch
From: Paul Mackerras @ 2010-12-14  9:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Alexandre Erwin Ittner, git
In-Reply-To: <7vy67txs7p.fsf@alter.siamese.dyndns.org>

On Mon, Dec 13, 2010 at 01:45:14PM -0800, Junio C Hamano wrote:
> Paul Mackerras <paulus@samba.org> writes:
> > If I apply this patch in my repo, is that going to cause problems in
> > yours?  Or, since the commit that adds this file is the head commit, I
> > could just rewind it and reapply with the lowercase name.  Do you see
> > problems with that?
> 
> Either way is fine by me, as the merge I pushed out last night is only on
> 'pu' that is advertised as unstable.

OK, I re-did the top commit using the lowercase name.  Please do a
git pull -f to get the new version of that commit.

Thanks,
Paul.

^ permalink raw reply

* Re: how to create a diff in old file new file format (for code reviews)
From: Michael J Gruber @ 2010-12-14  9:17 UTC (permalink / raw)
  To: aerosmith; +Cc: git
In-Reply-To: <1292285257962-5832810.post@n2.nabble.com>

aerosmith venit, vidit, dixit 14.12.2010 01:07:
> 
> Hi,
> 
> I am trying to create a diff such that the original file (entire file) is
> saved something like file1.h.old and the new modified file as file1.h.new. I
> have read the various options for git-diff* tools but could not find one
> such utility. All I get is the removals and additions as a diff. Does anyone
> know how to create one with the help the available git utils? The only
> method that I can think of is to do everything manually. Any help w.r.t.
> this is really appreciated. Thanks in advance.

You could script around this e.g. with an external diff-helper. The
easiest way is to reuse difftool. For example,

git difftool -y -x echo <revexpression>

will give you pairs of names of temporary files for old/new, where
<revexpression> is what you would give to "git diff" to specify what to
diff.

With the patch I'm sending in a minute, the helper you specify with "-x"
can also access the basename easily, so that you could use "-x oldnew"
with a script "oldnew" containing

#!/bin/sh
cp "$1" "$BASE".old
cp "$2" "$BASE".new

Even without the patch, you could use

git difftool -y -x 'cp "$LOCAL" "$BASE".old; cp "$REMOTE" "$BASE.new";
#' <revexpression>

(all on one line) directly. But this requires insider knowledge and may
break some day.

Cheers,
Michael

^ permalink raw reply


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