* [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed
[not found] <7vhbegroj2.fsf@alter.siamese.dyndns.org>
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 1/8] Refactor parse_loc Thomas Rast
` (7 more replies)
0 siblings, 8 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano
Junio C Hamano wrote:
>
> This patch set has too many whitespace errors, and worse yet, cannot be
> applied with "git am --whitespace=fix" because it has test vectors that
> depend on having "SP followed by HT" (context lines of diff output) and
> traling SP (graph output).
>
> Could you please straighten that out first?
>
> My preference is to protect offending whitespaces like this:
>
> sed -e 's/Q/ /g' -e 's/Z$//' >expected-no-M <<\EOF
[...]
> which has an added benefit of making them more visible.
Sorry for the mess. I stuck to the s/#$// style I already had in some
places for now, but used the Qs and ran the rest through
whitespace=fix. It now applies cleanly to master.
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-corner-cases.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.807.g6ee1f
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v6.1 1/8] Refactor parse_loc
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 2/8] Export three functions from diff.c Thomas Rast
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
From: Bo Yang <struggleyb.nku@gmail.com>
We want to use the same style of -L n,m argument for 'git log -L' as
for git-blame. Refactor the argument parsing of the range arguments
from builtin/blame.c to the (new) file that will hold the 'git log -L'
logic.
To accommodate different data structures in blame and log -L, the file
contents are abstracted away; parse_range_arg takes a callback that it
uses to get the contents of a line of the (notional) file.
The new test is for a case that made me pause during debugging: the
'blame -L with invalid end' test was the only one that noticed an
outright failure to parse the end *at all*. So make a more explicit
test for that.
Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Makefile | 2 +
builtin/blame.c | 99 +++-----------------------------------
line.c | 106 +++++++++++++++++++++++++++++++++++++++++
line.h | 23 +++++++++
t/t8003-blame-corner-cases.sh | 6 ++
5 files changed, 145 insertions(+), 91 deletions(-)
create mode 100644 line.c
create mode 100644 line.h
diff --git a/Makefile b/Makefile
index 57d9c65..b015c61 100644
--- a/Makefile
+++ b/Makefile
@@ -519,6 +519,7 @@ LIB_H += grep.h
LIB_H += hash.h
LIB_H += help.h
LIB_H += levenshtein.h
+LIB_H += line.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.h
@@ -606,6 +607,7 @@ LIB_OBJS += help.o
LIB_OBJS += hex.o
LIB_OBJS += ident.o
LIB_OBJS += levenshtein.o
+LIB_OBJS += line.o
LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
LIB_OBJS += lockfile.o
diff --git a/builtin/blame.c b/builtin/blame.c
index aa30ec5..5eeddcb 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -21,6 +21,7 @@
#include "parse-options.h"
#include "utf8.h"
#include "userdiff.h"
+#include "line.h"
static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
@@ -551,11 +552,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
dst->score = 0;
}
-static const char *nth_line(struct scoreboard *sb, int lno)
+static const char *nth_line(struct scoreboard *sb, long lno)
{
return sb->final_buf + sb->lineno[lno];
}
+static const char *nth_line_cb(void *data, long lno)
+{
+ return nth_line((struct scoreboard *)data, lno);
+}
+
/*
* It is known that lines between tlno to same came from parent, and e
* has an overlap with that range. it also is known that parent's
@@ -1925,83 +1931,6 @@ static unsigned parse_score(const char *arg)
}
/*
- * Parsing of (comma separated) one item in the -L option
- */
-static const char *parse_loc(const char *spec,
- struct scoreboard *sb, long lno,
- long begin, long *ret)
-{
- char *term;
- const char *line;
- long num;
- int reg_error;
- regex_t regexp;
- regmatch_t match[1];
-
- /* Allow "-L <something>,+20" to mean starting at <something>
- * for 20 lines, or "-L <something>,-5" for 5 lines ending at
- * <something>.
- */
- if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
- num = strtol(spec + 1, &term, 10);
- if (term != spec + 1) {
- if (spec[0] == '-')
- num = 0 - num;
- if (0 < num)
- *ret = begin + num - 2;
- else if (!num)
- *ret = begin;
- else
- *ret = begin + num;
- return term;
- }
- return spec;
- }
- num = strtol(spec, &term, 10);
- if (term != spec) {
- *ret = num;
- return term;
- }
- if (spec[0] != '/')
- return spec;
-
- /* it could be a regexp of form /.../ */
- for (term = (char *) spec + 1; *term && *term != '/'; term++) {
- if (*term == '\\')
- term++;
- }
- if (*term != '/')
- return spec;
-
- /* try [spec+1 .. term-1] as regexp */
- *term = 0;
- begin--; /* input is in human terms */
- line = nth_line(sb, begin);
-
- if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
- !(reg_error = regexec(®exp, 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(®exp);
- *term++ = '/';
- return term;
- }
- else {
- char errbuf[1024];
- regerror(reg_error, ®exp, errbuf, 1024);
- die("-L parameter '%s': %s", spec + 1, errbuf);
- }
-}
-
-/*
* Parsing of -L option
*/
static void prepare_blame_range(struct scoreboard *sb,
@@ -2009,15 +1938,7 @@ static void prepare_blame_range(struct scoreboard *sb,
long lno,
long *bottom, long *top)
{
- const char *term;
-
- term = parse_loc(bottomtop, sb, lno, 1, bottom);
- if (*term == ',') {
- term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
- if (*term)
- usage(blame_usage);
- }
- if (*term)
+ if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top))
usage(blame_usage);
}
@@ -2504,10 +2425,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
bottom = top = 0;
if (bottomtop)
prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
- if (bottom && top && top < bottom) {
- long tmp;
- tmp = top; top = bottom; bottom = tmp;
- }
if (bottom < 1)
bottom = 1;
if (top < 1)
diff --git a/line.c b/line.c
new file mode 100644
index 0000000..778cd7b
--- /dev/null
+++ b/line.c
@@ -0,0 +1,106 @@
+#include "git-compat-util.h"
+#include "line.h"
+
+/*
+ * Parse one item in the -L option
+ */
+const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
+ void *data, long lines, long begin, long *ret)
+{
+ char *term;
+ const char *line;
+ long num;
+ int reg_error;
+ regex_t regexp;
+ regmatch_t match[1];
+
+ /* Catch the '$' matcher, now it is used to match the last
+ * line of the file. */
+ if (spec[0] == '$') {
+ *ret = lines;
+ return spec + 1;
+ }
+
+ /* Allow "-L <something>,+20" to mean starting at <something>
+ * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+ * <something>.
+ */
+ if (begin != -1 && (spec[0] == '+' || spec[0] == '-')) {
+ num = strtol(spec + 1, &term, 10);
+ if (term != spec + 1) {
+ if (spec[0] == '-')
+ num = 0 - num;
+ if (0 < num)
+ *ret = begin + num - 2;
+ else if (!num)
+ *ret = begin;
+ else
+ *ret = begin + num;
+ return term;
+ }
+ return spec;
+ }
+ num = strtol(spec, &term, 10);
+ if (term != spec) {
+ *ret = num;
+ return term;
+ }
+ if (spec[0] != '/')
+ return spec;
+
+ /* it could be a regexp of form /.../ */
+ for (term = (char *) spec + 1; *term && *term != '/'; term++) {
+ if (*term == '\\')
+ term++;
+ }
+ if (*term != '/')
+ return spec;
+
+ /* try [spec+1 .. term-1] as regexp */
+ *term = 0;
+ if (begin == -1)
+ begin = 1;
+ begin--; /* input is in human terms */
+ line = nth_line(data, begin);
+
+ if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
+ !(reg_error = regexec(®exp, 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(®exp);
+ *term++ = '/';
+ return term;
+ } else {
+ char errbuf[1024];
+ regerror(reg_error, ®exp, errbuf, 1024);
+ die("-L parameter '%s': %s", spec + 1, errbuf);
+ }
+}
+
+int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
+ void *cb_data, long lines, long *begin, long *end)
+{
+ arg = parse_loc(arg, nth_line_cb, cb_data, lines, -1, begin);
+
+ if (*arg == ',') {
+ arg = parse_loc(arg+1, nth_line_cb, cb_data, lines, *begin+1, end);
+ if (*begin > *end) {
+ long tmp = *begin;
+ *begin = *end;
+ *end = tmp;
+ }
+ }
+
+ if (*arg)
+ return -1;
+
+ return 0;
+}
diff --git a/line.h b/line.h
new file mode 100644
index 0000000..5878c94
--- /dev/null
+++ b/line.h
@@ -0,0 +1,23 @@
+#ifndef LINE_H
+#define LINE_H
+
+/*
+ * Parse one item in an -L begin,end option w.r.t. the notional file
+ * object 'cb_data' consisting of 'lines' lines.
+ *
+ * The 'nth_line_cb' callback is used to determine the start of the
+ * line 'lno' inside the 'cb_data'. The caller is expected to already
+ * have a suitable map at hand to make this a constant-time lookup.
+ *
+ * Returns 0 in case of success and -1 if there was an error. The
+ * caller should print a usage message in the latter case.
+ */
+
+typedef const char *(*nth_line_fn_t)(void *data, long lno);
+
+extern int parse_range_arg(const char *arg,
+ nth_line_fn_t nth_line_cb,
+ void *cb_data, long lines,
+ long *begin, long *end);
+
+#endif /* LINE_H */
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh
index 230143c..51d313e 100755
--- a/t/t8003-blame-corner-cases.sh
+++ b/t/t8003-blame-corner-cases.sh
@@ -175,6 +175,12 @@ test_expect_success 'blame -L with invalid end' '
grep "has only 2 lines" errors
'
+test_expect_success 'blame -L parses end' '
+ git blame -L1,1 tres >out &&
+ cat out &&
+ test $(wc -l < out) -eq 1
+'
+
test_expect_success 'indent of line numbers, nine lines' '
git blame nine_lines >actual &&
test $(grep -c " " actual) = 0
--
1.7.3.3.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6.1 2/8] Export three functions from diff.c
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 1/8] Refactor parse_loc Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 3/8] Export rewrite_parents() for 'log -L' Thomas Rast
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
From: Bo Yang <struggleyb.nku@gmail.com>
Use fill_metainfo to fill the line level diff meta data,
emit_line to print out a line and quote_two to quote
paths.
Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
diff.c | 6 +++---
diff.h | 17 +++++++++++++++++
2 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/diff.c b/diff.c
index 0a43869..a16ce69 100644
--- a/diff.c
+++ b/diff.c
@@ -151,7 +151,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return git_color_default_config(var, value, cb);
}
-static char *quote_two(const char *one, const char *two)
+char *quote_two(const char *one, const char *two)
{
int need_one = quote_c_style(one, NULL, NULL, 1);
int need_two = quote_c_style(two, NULL, NULL, 1);
@@ -332,7 +332,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
fputc('\n', file);
}
-static void emit_line(struct diff_options *o, const char *set, const char *reset,
+void emit_line(struct diff_options *o, const char *set, const char *reset,
const char *line, int len)
{
emit_line_0(o, set, reset, line[0], line+1, len-1);
@@ -2583,7 +2583,7 @@ static int similarity_index(struct diff_filepair *p)
return p->score * 100 / MAX_SCORE;
}
-static void fill_metainfo(struct strbuf *msg,
+void fill_metainfo(struct strbuf *msg,
const char *name,
const char *other,
struct diff_filespec *one,
diff --git a/diff.h b/diff.h
index 0083d92..165f368 100644
--- a/diff.h
+++ b/diff.h
@@ -12,6 +12,7 @@ struct diff_queue_struct;
struct strbuf;
struct diff_filespec;
struct userdiff_driver;
+struct diff_filepair;
typedef void (*change_fn_t)(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
@@ -317,4 +318,20 @@ extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
extern int parse_rename_score(const char **cp_p);
+/* some output functions line.c need */
+extern void fill_metainfo(struct strbuf *msg,
+ const char *name,
+ const char *other,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ struct diff_options *o,
+ struct diff_filepair *p,
+ int *must_show_header,
+ int use_color);
+
+extern void emit_line(struct diff_options *o, const char *set, const char *reset,
+ const char *line, int len);
+
+extern char *quote_two(const char *one, const char *two);
+
#endif /* DIFF_H */
--
1.7.3.3.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6.1 3/8] Export rewrite_parents() for 'log -L'
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 1/8] Refactor parse_loc Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 2/8] Export three functions from diff.c Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 4/8] Implement line-history search (git log -L) Thomas Rast
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
From: Bo Yang <struggleyb.nku@gmail.com>
The function rewrite_one is used to rewrite a single
parent of the current commit, and is used by rewrite_parents
to rewrite all the parents.
Decouple the dependence between them by making rewrite_one
a callback function that is passed to rewrite_parents. Then
export rewrite_parents for reuse by the line history browser.
We will use this function in line.c.
Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
revision.c | 13 ++++---------
revision.h | 10 ++++++++++
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/revision.c b/revision.c
index ded8812..6465c45 100644
--- a/revision.c
+++ b/revision.c
@@ -1912,12 +1912,6 @@ int prepare_revision_walk(struct rev_info *revs)
return 0;
}
-enum rewrite_result {
- rewrite_one_ok,
- rewrite_one_noparents,
- rewrite_one_error
-};
-
static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
{
struct commit_list *cache = NULL;
@@ -1939,12 +1933,13 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
}
}
-static int rewrite_parents(struct rev_info *revs, struct commit *commit)
+int rewrite_parents(struct rev_info *revs, struct commit *commit,
+ rewrite_parent_fn_t rewrite_parent)
{
struct commit_list **pp = &commit->parents;
while (*pp) {
struct commit_list *parent = *pp;
- switch (rewrite_one(revs, &parent->item)) {
+ switch (rewrite_parent(revs, &parent->item)) {
case rewrite_one_ok:
break;
case rewrite_one_noparents:
@@ -2012,7 +2007,7 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
if (action == commit_show &&
!revs->show_all &&
revs->prune && revs->dense && want_ancestry(revs)) {
- if (rewrite_parents(revs, commit) < 0)
+ if (rewrite_parents(revs, commit, rewrite_one) < 0)
return commit_error;
}
return action;
diff --git a/revision.h b/revision.h
index 05659c6..8897368 100644
--- a/revision.h
+++ b/revision.h
@@ -193,4 +193,14 @@ enum commit_action {
extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);
extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
+enum rewrite_result {
+ rewrite_one_ok,
+ rewrite_one_noparents,
+ rewrite_one_error
+};
+
+typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp);
+
+extern int rewrite_parents(struct rev_info *revs, struct commit *commit,
+ rewrite_parent_fn_t rewrite_parent);
#endif
--
1.7.3.3.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6.1 4/8] Implement line-history search (git log -L)
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
` (2 preceding siblings ...)
2010-12-14 22:54 ` [PATCH v6.1 3/8] Export rewrite_parents() for 'log -L' Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-15 0:20 ` Junio C Hamano
2010-12-14 22:54 ` [PATCH v6.1 5/8] log -L: support parent rewriting Thomas Rast
` (3 subsequent siblings)
7 siblings, 1 reply; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
From: Bo Yang <struggleyb.nku@gmail.com>
'struct diff_line_range' is the main data structure to keep
track of the line ranges we are currently interested in. The
user starts digging from a line range, and after examining the
diff that affects that range by a commit, we can find a new
range that corresponds to this range. So, we will associate this
new range with the commit's parent commit.
There is one 'diff_line_range' for each file, and there are
multiple 'struct line_range' in each 'diff_line_range'. In this way,
we support multiple ranges.
Within 'struct line_range', there are multiple 'struct print_range'
which represent a diff hunk.
When going from a commit to its parents, we map the "interesting"
range of lines according to the change made. For non-merge commits,
we just run map_range on the ranges, which works as follows:
1. Run diffcore_std to find out the pre/postimage for each file.
2. Run xdi_diff_hunks on each interesting set of pre/postimages.
3. The map_range_cb callback is invoked for each hunk by the diff
engine, and we use it to calculate the pre-image range from the
post-image range in the function map_lines.
For merge commits, we run map_range once for every parent. After that
we use a take_range pass to eliminate all ranges that are identical.
If any ranges remain after that, then the merge is considered
non-trivial.
The algorithm that maps lines from post-image to pre-image is in the
function map_lines.
To correctly track the line ranges over several branches, we must make
sure that we have processed all children before reaching the commit
itself. So we let the revision machinery topo-order the commits
before doing anything.
Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/blame-options.txt | 19 +-
Documentation/git-log.txt | 18 +
Documentation/line-range-format.txt | 18 +
builtin/log.c | 73 ++-
line.c | 1331 ++++++++++++++++++++++++++++++++++-
line.h | 49 ++
revision.c | 6 +
revision.h | 11 +-
t/t4301-log-line-single-history.sh | 685 ++++++++++++++++++
9 files changed, 2186 insertions(+), 24 deletions(-)
create mode 100644 Documentation/line-range-format.txt
create mode 100755 t/t4301-log-line-single-history.sh
diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 16e3c68..3526835 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -13,24 +13,7 @@
Annotate only the given line range. <start> and <end> can take
one of these forms:
- - number
-+
-If <start> or <end> is a number, it specifies an
-absolute line number (lines count from 1).
-+
-
-- /regex/
-+
-This form will use the first line matching the given
-POSIX regex. If <end> is a regex, it will search
-starting at the line given by <start>.
-+
-
-- +offset or -offset
-+
-This is only valid for <end> and will specify a number
-of lines before or after the line given by <start>.
-+
+include::line-range-format.txt[]
-l::
Show long rev (Default: off).
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index ff41784..7fcf6e7 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -66,6 +66,19 @@ produced by --stat etc.
Note that only message is considered, if also a diff is shown
its size is not included.
+-L <start>,<end>:<file>::
+ Trace the evolution of the line range given by "<start>,<end>"
+ within the <file>. You may not give any pathspec limiters.
+ This is currently limited to a walk starting from a single
+ revision, i.e., you may only give zero or one positive
+ revision arguments.
+
+<start> and <end> can take one of these forms:
+
+include::line-range-format.txt[]
+You can specify this option more than once.
+
+
[\--] <path>...::
Show only commits that affect any of the specified paths. To
prevent confusion with options and branch names, paths may need
@@ -132,6 +145,11 @@ git log -p -m --first-parent::
This makes sense only when following a strict policy of merging all
topic branches when staying on a single integration branch.
+git log -L '/int main/',/^}/:main.c::
+
+ Shows how the function `main()` in the file 'main.c' evolved
+ over time.
+
Discussion
----------
diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
new file mode 100644
index 0000000..265bc23
--- /dev/null
+++ b/Documentation/line-range-format.txt
@@ -0,0 +1,18 @@
+- number
++
+If <start> or <end> is a number, it specifies an
+absolute line number (lines count from 1).
++
+
+- /regex/
++
+This form will use the first line matching the given
+POSIX regex. If <end> is a regex, it will search
+starting at the line given by <start>.
++
+
+- +offset or -offset
++
+This is only valid for <end> and will specify a number
+of lines before or after the line given by <start>.
++
diff --git a/builtin/log.c b/builtin/log.c
index d8c6c28..342d4de 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -19,6 +19,7 @@
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
+#include "line.h"
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
@@ -28,10 +29,21 @@
static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty;
-static const char * const builtin_log_usage =
+static char builtin_log_usage[] =
"git log [<options>] [<since>..<until>] [[--] <path>...]\n"
" or: git show [options] <object>...";
+static const char *log_opt_usage[] = {
+ builtin_log_usage,
+ NULL
+};
+
+struct line_opt_callback_data {
+ struct rev_info *rev;
+ const char *prefix;
+ struct diff_line_range *ranges, *cur_range;
+};
+
static int parse_decoration_style(const char *var, const char *value)
{
switch (git_config_maybe_bool(var, value)) {
@@ -49,12 +61,53 @@ static int parse_decoration_style(const char *var, const char *value)
return -1;
}
+static int log_line_range_callback(const struct option *option, const char *arg, int unset)
+{
+ struct line_opt_callback_data *data = option->value;
+ struct diff_line_range *r;
+ const char *name_start, *range_arg, *full_path;
+ const char *prefix = data->prefix;
+
+ if (!arg)
+ return -1;
+
+ name_start = skip_range_arg(arg);
+ if (!name_start || *name_start != ':')
+ die("-L argument '%s' not of the form start,end:file", arg);
+
+ range_arg = xstrndup(arg, name_start-arg);
+ name_start++;
+
+ full_path = prefix_path(prefix, prefix ? strlen(prefix) : 0,
+ name_start);
+
+ r = xmalloc(sizeof(struct diff_line_range));
+ diff_line_range_init(r);
+ if (data->cur_range)
+ data->cur_range->next = r;
+ else
+ data->ranges = r;
+ data->cur_range = r;
+ r->spec = alloc_filespec(full_path);
+ diff_line_range_append(r, range_arg);
+ data->rev->line_level_traverse = 1;
+ return 0;
+}
+
static void cmd_log_init(int argc, const char **argv, const char *prefix,
struct rev_info *rev, struct setup_revision_opt *opt)
{
int i;
int decoration_given = 0;
struct userformat_want w;
+ static struct line_opt_callback_data line_cb = {0};
+
+ static const struct option options[] = {
+ OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
+ "Process line range n,m in file, counting from 1",
+ log_line_range_callback),
+ OPT_END()
+ };
rev->abbrev = DEFAULT_ABBREV;
rev->commit_format = CMIT_FMT_DEFAULT;
@@ -75,6 +128,14 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
*/
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(builtin_log_usage);
+
+ line_cb.rev = rev;
+ line_cb.prefix = prefix;
+
+ argc = parse_options(argc, argv, prefix, options, log_opt_usage,
+ PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |
+ PARSE_OPT_KEEP_UNKNOWN);
+
argc = setup_revisions(argc, argv, rev, opt);
memset(&w, 0, sizeof(w));
@@ -125,6 +186,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
rev->show_decorations = 1;
load_ref_decorations(decoration_style);
}
+
+ /* Test whether line level history is asked for */
+ if (rev->line_level_traverse)
+ line_log_init(rev, line_cb.ranges);
+
setup_pager();
}
@@ -509,7 +575,10 @@ int cmd_log(int argc, const char **argv, const char *prefix)
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
cmd_log_init(argc, argv, prefix, &rev, &opt);
- return cmd_log_walk(&rev);
+ if (rev.line_level_traverse)
+ return line_log_walk(&rev);
+ else
+ return cmd_log_walk(&rev);
}
/* format-patch */
diff --git a/line.c b/line.c
index 778cd7b..1ffcaba 100644
--- a/line.c
+++ b/line.c
@@ -1,6 +1,124 @@
#include "git-compat-util.h"
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "diff.h"
+#include "commit.h"
+#include "decorate.h"
+#include "revision.h"
+#include "xdiff-interface.h"
+#include "strbuf.h"
+#include "log-tree.h"
#include "line.h"
+struct print_range {
+ int start, end; /* Line range of post-image */
+ int pstart, pend; /* Line range of pre-image */
+ int line_added : 1; /* whether this range is added */
+};
+
+struct print_pair {
+ int alloc, nr;
+ struct print_range *ranges;
+};
+
+struct line_range {
+ const char *arg; /* The argument to specify this line range */
+ long start, end; /* The interesting line range of current commit */
+ long pstart, pend; /* The corresponding range of parent commit */
+ struct print_pair pair;
+ /* The changed lines inside this range */
+ unsigned int diff:1;
+};
+
+/*
+ * Eek-a-global
+ */
+
+static int limited;
+
+/*
+ * These could be in line.h but we put them here so the functions can
+ * be static.
+ */
+
+static struct line_range *diff_line_range_insert(struct diff_line_range *r,
+ const char *arg, int start, int end);
+
+static void diff_line_range_clear(struct diff_line_range *r);
+
+static struct diff_line_range *diff_line_range_merge(
+ struct diff_line_range *out,
+ struct diff_line_range *other);
+
+static struct diff_line_range *diff_line_range_clone(struct diff_line_range *r);
+
+static struct diff_line_range *diff_line_range_clone_deeply(struct diff_line_range *r);
+
+static void add_line_range(struct rev_info *revs, struct commit *commit,
+ struct diff_line_range *r);
+
+static struct diff_line_range *lookup_line_range(struct rev_info *revs,
+ struct commit *commit);
+
+/*
+ * Data structure helpers
+ */
+
+static inline void print_range_init(struct print_range *r)
+{
+ r->start = r->end = 0;
+ r->pstart = r->pend = 0;
+ r->line_added = 0;
+}
+
+static inline void print_pair_init(struct print_pair *p)
+{
+ p->alloc = p->nr = 0;
+ p->ranges = NULL;
+}
+
+static inline void print_pair_grow(struct print_pair *p)
+{
+ p->nr++;
+ ALLOC_GROW(p->ranges, p->nr, p->alloc);
+}
+
+static inline void print_pair_clear(struct print_pair *p)
+{
+ p->alloc = p->nr = 0;
+ if (p->ranges)
+ free(p->ranges);
+ p->ranges = NULL;
+}
+
+static inline void line_range_init(struct line_range *r)
+{
+ r->arg = NULL;
+ r->start = r->end = 0;
+ r->pstart = r->pend = 0;
+ print_pair_init(&(r->pair));
+ r->copy_score = 0;
+ r->diff = 0;
+}
+
+static inline void line_range_clear(struct line_range *r)
+{
+ r->arg = NULL;
+ r->start = r->end = 0;
+ r->pstart = r->pend = 0;
+ print_pair_clear(&r->pair);
+ r->diff = 0;
+}
+
+static inline void diff_line_range_grow(struct diff_line_range *r)
+{
+ r->nr++;
+ ALLOC_GROW(r->ranges, r->nr, r->alloc);
+ line_range_init((r->ranges + r->nr - 1));
+}
+
/*
* Parse one item in the -L option
*/
@@ -17,7 +135,8 @@
/* Catch the '$' matcher, now it is used to match the last
* line of the file. */
if (spec[0] == '$') {
- *ret = lines;
+ if (ret)
+ *ret = lines;
return spec + 1;
}
@@ -28,6 +147,8 @@
if (begin != -1 && (spec[0] == '+' || spec[0] == '-')) {
num = strtol(spec + 1, &term, 10);
if (term != spec + 1) {
+ if (!ret)
+ return term;
if (spec[0] == '-')
num = 0 - num;
if (0 < num)
@@ -42,7 +163,8 @@
}
num = strtol(spec, &term, 10);
if (term != spec) {
- *ret = num;
+ if (ret)
+ *ret = num;
return term;
}
if (spec[0] != '/')
@@ -56,6 +178,10 @@
if (*term != '/')
return spec;
+ /* in the scan-only case we are not interested in the regex */
+ if (!ret)
+ return term+1;
+
/* try [spec+1 .. term-1] as regexp */
*term = 0;
if (begin == -1)
@@ -104,3 +230,1204 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
return 0;
}
+
+const char *skip_range_arg(const char *arg)
+{
+ arg = parse_loc(arg, NULL, NULL, 0, -1, 0);
+
+ if (*arg == ',')
+ arg = parse_loc(arg+1, NULL, NULL, 0, 0, 0);
+
+ return arg;
+}
+
+static void free_diff_line_ranges(struct diff_line_range *r)
+{
+ while (r) {
+ struct diff_line_range *next = r->next;
+ diff_line_range_clear(r);
+ free(r);
+ r = next;
+ }
+}
+
+static struct object *verify_commit(struct rev_info *revs)
+{
+ struct object *commit = NULL;
+ int found = -1;
+ int i;
+
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (commit)
+ die("More than one commit to dig from: %s and %s?",
+ revs->pending.objects[i].name,
+ revs->pending.objects[found].name);
+ commit = obj;
+ found = i;
+ }
+
+ if (!commit)
+ die("No commit specified?");
+
+ return commit;
+}
+
+static void fill_blob_sha1(struct commit *commit, struct diff_line_range *r)
+{
+ unsigned mode;
+ unsigned char sha1[20];
+
+ while (r) {
+ if (get_tree_entry(commit->object.sha1, r->spec->path,
+ sha1, &mode))
+ goto error;
+ fill_filespec(r->spec, sha1, mode);
+ r = r->next;
+ }
+
+ return;
+error:
+ die("There is no path %s in the commit", r->spec->path);
+}
+
+static void fill_line_ends(struct diff_filespec *spec, long *lines,
+ unsigned long **line_ends)
+{
+ int num = 0, size = 50;
+ long cur = 0;
+ unsigned long *ends = NULL;
+ char *data = NULL;
+
+ if (diff_populate_filespec(spec, 0))
+ die("Cannot read blob %s", sha1_to_hex(spec->sha1));
+
+ ends = xmalloc(size * sizeof(*ends));
+ ends[cur++] = 0;
+ data = spec->data;
+ while (num < spec->size) {
+ if (data[num] == '\n' || num == spec->size - 1) {
+ ALLOC_GROW(ends, (cur + 1), size);
+ ends[cur++] = num;
+ }
+ num++;
+ }
+
+ /* shrink the array to fit the elements */
+ ends = xrealloc(ends, cur * sizeof(*ends));
+ *lines = cur;
+ *line_ends = ends;
+}
+
+struct nth_line_cb {
+ struct diff_filespec *spec;
+ long lines;
+ unsigned long *line_ends;
+};
+
+static const char *nth_line(void *data, long line)
+{
+ struct nth_line_cb *d = data;
+ assert(d && line < d->lines);
+ assert(d->spec && d->spec->data);
+
+ if (line == 0)
+ return (char *)d->spec->data;
+ else
+ return (char *)d->spec->data + d->line_ends[line] + 1;
+}
+
+static void parse_lines(struct commit *commit, struct diff_line_range *r)
+{
+ int i;
+ struct line_range *old_range = NULL;
+ long lines = 0;
+ unsigned long *ends = NULL;
+ struct nth_line_cb cb_data;
+
+ while (r) {
+ struct diff_filespec *spec = r->spec;
+ int num = r->nr;
+ assert(spec);
+ fill_blob_sha1(commit, r);
+ old_range = r->ranges;
+ r->ranges = NULL;
+ r->nr = r->alloc = 0;
+ fill_line_ends(spec, &lines, &ends);
+ cb_data.spec = spec;
+ cb_data.lines = lines;
+ cb_data.line_ends = ends;
+ for (i = 0; i < num; i++) {
+ long begin, end;
+ if (parse_range_arg(old_range[i].arg, nth_line, &cb_data,
+ lines-1, &begin, &end))
+ die("malformed -L argument '%s'", old_range[i].arg);
+ diff_line_range_insert(r, old_range[i].arg, begin, end);
+ }
+
+ free(ends);
+ ends = NULL;
+
+ r = r->next;
+ free(old_range);
+ }
+}
+
+/*
+ * Insert a new line range into a diff_line_range struct, and keep the
+ * r->ranges sorted by their starting line number.
+ */
+static struct line_range *diff_line_range_insert(struct diff_line_range *r,
+ const char *arg, int start, int end)
+{
+ int i = 0;
+ struct line_range *rs = r->ranges;
+ int left_merge = 0, right_merge = 0;
+
+ assert(r);
+ assert(start <= end);
+
+ if (r->nr == 0 || rs[r->nr - 1].end < start - 1) {
+ int num = 0;
+ diff_line_range_grow(r);
+ rs = r->ranges;
+ num = r->nr - 1;
+ rs[num].arg = arg;
+ rs[num].start = start;
+ rs[num].end = end;
+ return rs + num;
+ }
+
+ for (; i < r->nr; i++) {
+ if (rs[i].end < start - 1)
+ continue;
+ if (rs[i].end == start - 1) {
+ rs[i].end = end;
+ right_merge = 1;
+ goto out;
+ }
+
+ assert(rs[i].end > start - 1);
+ if (rs[i].start <= start) {
+ if (rs[i].end < end) {
+ rs[i].end = end;
+ right_merge = 1;
+ }
+ goto out;
+ } else if (rs[i].start <= end + 1) {
+ rs[i].start = start;
+ left_merge = 1;
+ if (rs[i].end < end) {
+ rs[i].end = end;
+ right_merge = 1;
+ }
+ goto out;
+ } else {
+ int num = r->nr - i;
+ diff_line_range_grow(r);
+ rs = r->ranges;
+ memmove(rs + i + 1, rs + i, num * sizeof(struct line_range));
+ rs[i].arg = arg;
+ rs[i].start = start;
+ rs[i].end = end;
+ goto out;
+ }
+ }
+
+out:
+ assert(r->nr != i);
+ if (left_merge) {
+ int j = i;
+ for (; j > -1; j--) {
+ if (rs[j].end >= rs[i].start - 1)
+ if (rs[j].start < rs[i].start)
+ rs[i].start = rs[j].start;
+ }
+ memmove(rs + j + 1, rs + i, (r->nr - i) * sizeof(struct line_range));
+ r->nr -= i - j - 1;
+ }
+ if (right_merge) {
+ int j = i;
+ for (; j < r->nr; j++) {
+ if (rs[j].start <= rs[i].end + 1)
+ if (rs[j].end > rs[i].end)
+ rs[i].end = rs[j].end;
+ }
+ if (j < r->nr)
+ memmove(rs + i + 1, rs + j, (r->nr - j) * sizeof(struct line_range));
+ r->nr -= j - i - 1;
+ }
+ assert(r->nr);
+
+ return rs + i;
+}
+
+static void diff_line_range_clear(struct diff_line_range *r)
+{
+ int i = 0, zero = 0;
+
+ for (; i < r->nr; i++) {
+ struct line_range *rg = r->ranges + i;
+ line_range_clear(rg);
+ }
+
+ if (r->prev) {
+ zero = 0;
+ if (r->prev->count == 1)
+ zero = 1;
+ free_filespec(r->prev);
+ if (zero)
+ r->prev = NULL;
+ }
+ if (r->spec) {
+ zero = 0;
+ if (r->spec->count == 1)
+ zero = 1;
+ free_filespec(r->spec);
+ if (zero)
+ r->spec = NULL;
+ }
+
+ r->status = '\0';
+ r->alloc = r->nr = 0;
+
+ if (r->ranges)
+ free(r->ranges);
+ r->ranges = NULL;
+ r->next = NULL;
+}
+
+void diff_line_range_append(struct diff_line_range *r, const char *arg)
+{
+ diff_line_range_grow(r);
+ r->ranges[r->nr - 1].arg = arg;
+}
+
+static struct diff_line_range *diff_line_range_clone(struct diff_line_range *r)
+{
+ struct diff_line_range *ret = xmalloc(sizeof(*ret));
+ int i = 0;
+
+ assert(r);
+ diff_line_range_init(ret);
+ ret->ranges = xcalloc(r->nr, sizeof(struct line_range));
+ memcpy(ret->ranges, r->ranges, sizeof(struct line_range) * r->nr);
+
+ ret->alloc = ret->nr = r->nr;
+
+ for (; i < ret->nr; i++)
+ print_pair_init(&ret->ranges[i].pair);
+
+ ret->spec = r->spec;
+ assert(ret->spec);
+ ret->spec->count++;
+
+ return ret;
+}
+
+static struct diff_line_range *
+diff_line_range_clone_deeply(struct diff_line_range *r)
+{
+ struct diff_line_range *ret = NULL;
+ struct diff_line_range *tmp = NULL, *prev = NULL;
+
+ assert(r);
+ ret = tmp = prev = diff_line_range_clone(r);
+ r = r->next;
+ while (r) {
+ tmp = diff_line_range_clone(r);
+ prev->next = tmp;
+ prev = tmp;
+ r = r->next;
+ }
+
+ return ret;
+}
+
+static struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,
+ struct diff_line_range *other)
+{
+ struct diff_line_range *one = out, *two = other;
+ struct diff_line_range *pone = NULL;
+
+ while (one) {
+ struct diff_line_range *ptwo;
+ two = other;
+ ptwo = other;
+ while (two) {
+ if (!strcmp(one->spec->path, two->spec->path)) {
+ int i = 0;
+ for (; i < two->nr; i++) {
+ diff_line_range_insert(one, NULL,
+ two->ranges[i].start,
+ two->ranges[i].end);
+ }
+ if (two == other)
+ other = other->next;
+ else
+ ptwo->next = two->next;
+ diff_line_range_clear(two);
+ free(two);
+ two = NULL;
+
+ break;
+ }
+
+ ptwo = two;
+ two = two->next;
+ }
+
+ pone = one;
+ one = one->next;
+ }
+ pone->next = other;
+
+ return out;
+}
+
+static void add_line_range(struct rev_info *revs, struct commit *commit,
+ struct diff_line_range *r)
+{
+ struct diff_line_range *ret = NULL;
+
+ if (r) {
+ ret = lookup_decoration(&revs->line_range, &commit->object);
+ if (ret)
+ diff_line_range_merge(ret, r);
+ else
+ add_decoration(&revs->line_range, &commit->object, r);
+ commit->object.flags |= RANGE_UPDATE;
+ }
+}
+
+static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
+{
+ struct diff_line_range *r;
+ r = lookup_decoration(&revs->line_range, &commit->object);
+ if (!r)
+ return;
+ free_diff_line_ranges(r);
+ add_decoration(&revs->line_range, &commit->object, NULL);
+}
+
+static struct diff_line_range *lookup_line_range(struct rev_info *revs,
+ struct commit *commit)
+{
+ struct diff_line_range *ret = NULL;
+
+ ret = lookup_decoration(&revs->line_range, &commit->object);
+ return ret;
+}
+
+void line_log_init(struct rev_info *rev, struct diff_line_range *r)
+{
+ struct commit *commit = NULL;
+ struct diff_options *opt = &rev->diffopt;
+
+ commit = (struct commit *)verify_commit(rev);
+ parse_lines(commit, r);
+
+ add_line_range(rev, commit, r);
+ /*
+ * Note we support -M/-C to detect file rename
+ */
+ opt->nr_paths = 0;
+ diff_tree_release_paths(opt);
+}
+
+struct take_range_cb_data {
+ struct diff_line_range *interesting; /* currently interesting ranges */
+ struct diff_line_range *range;
+ /* the ranges corresponds to the interesting ranges of parent commit */
+ long plno, tlno;
+ /* the last line number of diff hunk */
+ int diff;
+ /* whether there is some line changes between the current
+ * commit and its parent */
+};
+
+#define SCALE_FACTOR 4
+/*
+ * [p_start, p_end] represents the pre-image of current diff hunk,
+ * [t_start, t_end] represents the post-image of the current diff hunk,
+ * [start, end] represents the currently interesting line range in
+ * post-image,
+ * [o_start, o_end] represents the original line range that coresponds
+ * to current line range.
+ */
+void map_lines(long p_start, long p_end, long t_start, long t_end,
+ long start, long end, long *o_start, long *o_end)
+{
+ /*
+ * Normally, p_start should be less than p_end, so does the
+ * t_start and t_end. But when the line range is added from
+ * scratch, p_start will be greater than p_end. When the line
+ * range is deleted, t_start will be greater than t_end.
+ */
+ if (p_start > p_end) {
+ *o_start = *o_end = 0;
+ return;
+ }
+ /* A deletion */
+ if (t_start > t_end) {
+ *o_start = p_start;
+ *o_end = p_end;
+ return;
+ }
+
+ if (start == t_start && end == t_end) {
+ *o_start = p_start;
+ *o_end = p_end;
+ return;
+ }
+
+ /*
+ * A heuristic for lines mapping:
+ *
+ * When the pre-image is no more than 1/SCALE_FACTOR of the post-image,
+ * there is no effective way to find out which part of pre-image
+ * corresponds to the currently interesting range of post-image.
+ * And we are in the danger of tracking totally useless lines.
+ * So, we just treat all the post-image lines as added from scratch.
+ */
+ if (SCALE_FACTOR * (p_end - p_start + 1) < (t_end - t_start + 1)) {
+ *o_start = *o_end = 0;
+ return;
+ }
+
+ *o_start = p_start + start - t_start;
+ *o_end = p_end - (t_end - end);
+
+ if (*o_start > *o_end) {
+ int temp = *o_start;
+ *o_start = *o_end;
+ *o_end = temp;
+ }
+
+ if (*o_start < p_start)
+ *o_start = p_start;
+ if (*o_end > p_end)
+ *o_end = p_end;
+}
+
+/*
+ * When same == 1:
+ * [p_start, p_end] represents the diff hunk line range of pre-image,
+ * [t_start, t_end] represents the diff hunk line range of post-image.
+ * When same == 0, they represent a range of identical lines between
+ * two images.
+ *
+ * This function find out the corresponding line ranges of currently
+ * interesting ranges which this diff hunk touches.
+ */
+static void map_range(struct take_range_cb_data *data, int same,
+ long p_start, long p_end, long t_start, long t_end)
+{
+ struct line_range *ranges = data->interesting->ranges;
+ long takens, takene, start, end;
+ int i = 0, out = 0, added = 0;
+ long op_start = p_start, op_end = p_end, ot_start = t_start, ot_end = t_end;
+
+ for (; i < data->interesting->nr; i++) {
+ added = 0;
+ if (t_start > ranges[i].end)
+ continue;
+ if (t_end < ranges[i].start)
+ break;
+
+ if (t_start > ranges[i].start) {
+ start = t_start;
+ takens = p_start;
+ } else {
+ start = ranges[i].start;
+ takens = p_start + start - t_start;
+ }
+
+ if (t_end >= ranges[i].end) {
+ end = ranges[i].end;
+ takene = p_start + end - t_start;
+ } else {
+ end = t_end;
+ takene = p_end;
+ out = 1;
+ }
+
+ if (!same) {
+ struct print_pair *pair = &ranges[i].pair;
+ struct print_range *rr = NULL;
+ print_pair_grow(pair);
+ rr = pair->ranges + pair->nr - 1;
+ print_range_init(rr);
+ rr->start = start;
+ rr->end = end;
+ map_lines(op_start, op_end, ot_start, ot_end, start, end,
+ &takens, &takene);
+ if (takens == 0 && takene == 0) {
+ added = 1;
+ rr->line_added = 1;
+ }
+ rr->pstart = takens;
+ rr->pend = takene;
+ data->diff = 1;
+ data->interesting->diff = 1;
+ ranges[i].diff = 1;
+ }
+ if (added) {
+ /* Code movement/copy goes here */
+ } else {
+ struct line_range *added_range = diff_line_range_insert(data->range,
+ NULL, takens, takene);
+ assert(added_range);
+ ranges[i].pstart = added_range->start;
+ ranges[i].pend = added_range->end;
+ }
+
+ t_start = end + 1;
+ p_start = takene + 1;
+
+ if (out)
+ break;
+ }
+}
+
+/*
+ * [p_start, p_end] represents the line range of pre-image,
+ * [t_start, t_end] represents the line range of post-image,
+ * and they are identical lines.
+ *
+ * This function substracts out the identical lines between current
+ * commit and its parent, from currently interesting ranges.
+ */
+static void take_range(struct take_range_cb_data *data,
+ long p_start, long p_end, long t_start, long t_end)
+{
+ struct line_range *ranges = data->interesting->ranges;
+ long takens, takene, start, end;
+ int i = 0, out = 0, added = 0;
+
+ for (; i < data->interesting->nr; i++) {
+ added = 0;
+ if (t_start > ranges[i].end)
+ continue;
+ if (t_end < ranges[i].start)
+ break;
+
+ if (t_start > ranges[i].start) {
+ long tmp = ranges[i].end;
+ ranges[i].end = t_start - 1;
+ start = t_start;
+ takens = p_start;
+ if (t_end >= tmp) {
+ end = tmp;
+ takene = p_start + end - t_start;
+ p_start = takene + 1;
+ t_start = end + 1;
+ } else {
+ end = t_end;
+ takene = p_end;
+ diff_line_range_insert(data->interesting, NULL,
+ t_end + 1, tmp);
+ out = 1;
+ }
+ } else {
+ start = ranges[i].start;
+ takens = p_start + start - t_start;
+ if (t_end >= ranges[i].end) {
+ int num = data->interesting->nr - 1;
+ end = ranges[i].end;
+ takene = p_start + end - t_start;
+ t_start = end + 1;
+ p_start = takene + 1;
+ memmove(ranges + i, ranges + i + 1, (num - i) * sizeof(*ranges));
+ data->interesting->nr = num;
+ i--;
+ } else {
+ end = t_end;
+ takene = p_end;
+ ranges[i].start = t_end + 1;
+ out = 1;
+ }
+ }
+
+ diff_line_range_insert(data->range, NULL, takens, takene);
+
+ if (out)
+ break;
+ }
+}
+
+static void take_range_cb(void *data, long same, long p_next, long t_next)
+{
+ struct take_range_cb_data *d = data;
+ long p_start = d->plno + 1, t_start = d->tlno + 1;
+ long p_end = p_start + same - t_start, t_end = same;
+
+ /* If one file is added from scratch, we should not bother to call
+ * take_range, since there is nothing to take
+ */
+ if (t_end >= t_start)
+ take_range(d, p_start, p_end, t_start, t_end);
+ d->plno = p_next;
+ d->tlno = t_next;
+}
+
+static void map_range_cb(void *data, long same, long p_next, long t_next)
+{
+ struct take_range_cb_data *d = data;
+
+ long p_start = d->plno + 1;
+ long t_start = d->tlno + 1;
+ long p_end = same - t_start + p_start;
+ long t_end = same;
+
+ /* Firstly, take the unchanged lines from child */
+ if (t_end >= t_start)
+ map_range(d, 1, p_start, p_end, t_start, t_end);
+
+ /* find out which lines to print */
+ t_start = same + 1;
+ p_start = d->plno + t_start - d->tlno;
+ map_range(d, 0, p_start, p_next, t_start, t_next);
+
+ d->plno = p_next;
+ d->tlno = t_next;
+}
+
+static void load_tree_desc(struct tree_desc *desc, void **tree,
+ const unsigned char *sha1)
+{
+ unsigned long size;
+ *tree = read_object_with_reference(sha1, tree_type, &size, NULL);
+ if (!tree)
+ die("Unable to read tree (%s)", sha1_to_hex(sha1));
+ init_tree_desc(desc, *tree, size);
+}
+
+/*
+ * We support two kinds of operation in this function:
+ * 1. map == 0, take the same lines from the current commit and assign it
+ * to parent;
+ * 2. map == 1, in addition to the same lines, we also map the changed lines
+ * from the current commit to the parent according to the
+ * diff output.
+ * take_range_cb and take_range are used to take same lines from current commit
+ * to parents.
+ * map_range_cb and map_range are used to map line ranges to the parent.
+ */
+static void assign_range_to_parent(struct rev_info *rev, struct commit *commit,
+ struct commit *parent, struct diff_line_range *range,
+ struct diff_options *opt, int map)
+{
+ struct diff_line_range *final_range = xmalloc(sizeof(*final_range));
+ struct diff_line_range *cur_range = final_range;
+ struct diff_line_range *prev_range = final_range;
+ struct diff_line_range *rg = NULL;
+ void *tree1 = NULL, *tree2 = NULL;
+ struct tree_desc desc1, desc2;
+ struct diff_queue_struct *queue;
+ struct take_range_cb_data cb_data = {NULL, cur_range, 0, 0};
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ int i, diff = 0;
+ xdiff_emit_hunk_consume_fn fn = map ? map_range_cb : take_range_cb;
+
+ diff_line_range_init(cur_range);
+ memset(&xpp, 0, sizeof(xpp));
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = xecfg.interhunkctxlen = 0;
+
+ /*
+ * Compose up two trees, for root commit, we make up a empty tree.
+ */
+ assert(commit);
+ load_tree_desc(&desc2, &tree2, commit->tree->object.sha1);
+ if (parent) {
+ load_tree_desc(&desc1, &tree1, parent->tree->object.sha1);
+ } else {
+ init_tree_desc(&desc1, "", 0);
+ }
+
+ DIFF_QUEUE_CLEAR(&diff_queued_diff);
+ diff_tree(&desc1, &desc2, "", opt);
+ diffcore_std(opt);
+
+ queue = &diff_queued_diff;
+ for (i = 0; i < queue->nr; i++) {
+ struct diff_filepair *pair = queue->queue[i];
+ struct diff_line_range *rg = range;
+ mmfile_t file_parent, file_t;
+ assert(pair->two->path);
+ while (rg) {
+ assert(rg->spec->path);
+ if (!strcmp(rg->spec->path, pair->two->path))
+ break;
+ rg = rg->next;
+ }
+
+ if (!rg)
+ continue;
+ rg->touched = 1;
+ if (rg->nr == 0)
+ continue;
+
+ rg->status = pair->status;
+ assert(pair->two->sha1_valid);
+ diff_populate_filespec(pair->two, 0);
+ file_t.ptr = pair->two->data;
+ file_t.size = pair->two->size;
+
+ if (rg->prev)
+ free_filespec(rg->prev);
+ rg->prev = pair->one;
+ rg->prev->count++;
+ if (pair->one->sha1_valid) {
+ diff_populate_filespec(pair->one, 0);
+ file_parent.ptr = pair->one->data;
+ file_parent.size = pair->one->size;
+ } else {
+ file_parent.ptr = "";
+ file_parent.size = 0;
+ }
+
+ if (cur_range->nr != 0) {
+ struct diff_line_range *tmp = xmalloc(sizeof(*tmp));
+ cur_range->next = tmp;
+ prev_range = cur_range;
+ cur_range = tmp;
+ } else if (cur_range->spec)
+ diff_line_range_clear(cur_range);
+
+ diff_line_range_init(cur_range);
+ if (pair->one->sha1_valid) {
+ cur_range->spec = pair->one;
+ cur_range->spec->count++;
+ } else {
+ assert(is_null_sha1(pair->one->sha1));
+ cur_range->spec = pair->two;
+ cur_range->spec->count++;
+ }
+
+ cb_data.interesting = rg;
+ cb_data.range = cur_range;
+ cb_data.diff = 0;
+ cb_data.plno = cb_data.tlno = 0;
+ xdi_diff_hunks(&file_parent, &file_t, fn, &cb_data, &xpp, &xecfg);
+ if (cb_data.diff)
+ diff = 1;
+ /*
+ * The remain part is the same part.
+ * Instead of calculating the true line number of the two files,
+ * use the biggest integer.
+ */
+ if (map)
+ map_range(&cb_data, 1, cb_data.plno + 1,
+ INT_MAX, cb_data.tlno + 1, INT_MAX);
+ else
+ take_range(&cb_data, cb_data.plno + 1,
+ INT_MAX, cb_data.tlno + 1, INT_MAX);
+ }
+ opt->output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_flush(opt);
+
+ /*
+ * Collect the untouched ranges, this comes from the files not changed
+ * between two commit.
+ */
+ rg = range;
+ while (rg) {
+ /* clear the touched one to make it usable in next round */
+ if (rg->touched) {
+ rg->touched = 0;
+ } else {
+ struct diff_line_range *untouched = diff_line_range_clone(rg);
+ if (prev_range == final_range && final_range->nr == 0) {
+ final_range = prev_range = untouched;
+ } else {
+ prev_range->next = untouched;
+ prev_range = untouched;
+ }
+ }
+ rg = rg->next;
+ }
+
+ if (cur_range->nr == 0) {
+ diff_line_range_clear(cur_range);
+ free(cur_range);
+ if (prev_range == cur_range)
+ final_range = NULL;
+ else
+ prev_range->next = NULL;
+ }
+
+ if (final_range) {
+ assert(parent);
+ assert(final_range->spec);
+ add_line_range(rev, parent, final_range);
+ }
+
+ /* and the ranges of current commit is updated */
+ commit->object.flags &= ~RANGE_UPDATE;
+ if (diff)
+ commit->object.flags |= NEED_PRINT;
+
+ if (tree1)
+ free(tree1);
+ if (tree2)
+ free(tree2);
+}
+
+static void diff_update_parent_range(struct rev_info *rev,
+ struct commit *commit)
+{
+ struct diff_line_range *r = lookup_line_range(rev, commit);
+ struct commit_list *parents = commit->parents;
+ struct commit *c = NULL;
+ if (parents) {
+ assert(!parents->next);
+ c = parents->item;
+ }
+
+ assign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);
+}
+
+static void assign_parents_range(struct rev_info *rev, struct commit *commit)
+{
+ struct commit_list *parents = commit->parents;
+ struct diff_line_range *r = lookup_line_range(rev, commit);
+ struct diff_line_range *evil = NULL, *range = NULL;
+ int nontrivial = 0;
+
+ /*
+ * If we are in linear history, update range and flush the patch if
+ * necessary
+ */
+ if (!parents || !parents->next)
+ return diff_update_parent_range(rev, commit);
+
+ /*
+ * Loop on the parents and assign the ranges to different
+ * parents, if there is any range left, this commit must
+ * be an evil merge.
+ */
+ evil = diff_line_range_clone_deeply(r);
+ parents = commit->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ assign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);
+ assign_range_to_parent(rev, commit, p, evil, &rev->diffopt, 0);
+ parents = parents->next;
+ }
+
+ /*
+ * yes, this must be an evil merge.
+ */
+ range = evil;
+ while (range) {
+ if (range->nr) {
+ commit->object.flags |= NEED_PRINT | EVIL_MERGE;
+ nontrivial = 1;
+ }
+ range = range->next;
+ }
+
+ if (nontrivial)
+ add_decoration(&rev->nontrivial_merge, &commit->object, evil);
+ else
+ free_diff_line_ranges(evil);
+}
+
+struct line_chunk {
+ int lone, ltwo;
+ const char *one, *two;
+ const char *one_end, *two_end;
+ struct diff_line_range *range;
+};
+
+static void flush_lines(struct diff_options *opt, const char **ptr, const char *end,
+ int slno, int elno, int *lno, const char *color, const char heading)
+{
+ const char *p = *ptr;
+ struct strbuf buf = STRBUF_INIT;
+ const char *reset;
+
+ if (*color)
+ reset = diff_get_color_opt(opt, DIFF_RESET);
+ else
+ reset = "";
+
+ strbuf_addf(&buf, "%s%c", color, heading);
+ while (*ptr < end && *lno < slno) {
+ if (**ptr == '\n') {
+ (*lno)++;
+ if (*lno == slno) {
+ (*ptr)++;
+ break;
+ }
+ }
+ (*ptr)++;
+ }
+ assert(*ptr <= end);
+ p = *ptr;
+
+ while (*ptr < end && *lno <= elno) {
+ if (**ptr == '\n') {
+ fprintf(opt->file, "%s", buf.buf);
+ if (*ptr - p)
+ fwrite(p, *ptr - p, 1, opt->file);
+ fprintf(opt->file, "%s\n", reset);
+ p = *ptr + 1;
+ (*lno)++;
+ }
+ (*ptr)++;
+ }
+ if (*lno <= elno) {
+ fprintf(opt->file, "%s", buf.buf);
+ if (*ptr - p)
+ fwrite(p, *ptr - p, 1, opt->file);
+ fprintf(opt->file, "%s\n", reset);
+ }
+ strbuf_release(&buf);
+}
+
+static void diff_flush_range(struct diff_options *opt, struct line_chunk *chunk,
+ struct line_range *range)
+{
+ struct print_pair *pair = &range->pair;
+ const char *old = diff_get_color_opt(opt, DIFF_FILE_OLD);
+ const char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);
+ int i, cur = range->start;
+
+ for (i = 0; i < pair->nr; i++) {
+ struct print_range *pr = pair->ranges + i;
+ if (cur < pr->start)
+ flush_lines(opt, &chunk->two, chunk->two_end,
+ cur, pr->start - 1, &chunk->ltwo, "", ' ');
+
+ if (!pr->line_added)
+ flush_lines(opt, &chunk->one, chunk->one_end,
+ pr->pstart, pr->pend, &chunk->lone, old, '-');
+ flush_lines(opt, &chunk->two, chunk->two_end,
+ pr->start, pr->end, &chunk->ltwo, new, '+');
+
+ cur = pr->end + 1;
+ }
+
+ if (cur <= range->end) {
+ flush_lines(opt, &chunk->two, chunk->two_end,
+ cur, range->end, &chunk->ltwo, "", ' ');
+ }
+}
+
+static void diff_flush_chunks(struct diff_options *opt, struct line_chunk *chunk)
+{
+ struct diff_line_range *range = chunk->range;
+ const char *set = diff_get_color_opt(opt, DIFF_FRAGINFO);
+ const char *reset = diff_get_color_opt(opt, DIFF_RESET);
+ int i;
+
+ for (i = 0; i < range->nr; i++) {
+ struct line_range *r = range->ranges + i;
+ long lenp = r->pend - r->pstart + 1, pstart = r->pstart;
+ long len = r->end - r->start + 1;
+ if (pstart == 0)
+ lenp = 0;
+
+ fprintf(opt->file, "%s@@ -%ld,%ld +%ld,%ld @@%s\n",
+ set, pstart, lenp, r->start, len, reset);
+
+ diff_flush_range(opt, chunk, r);
+ }
+}
+
+static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *range)
+{
+ struct diff_options *opt = &rev->diffopt;
+ struct diff_filespec *one = range->prev, *two = range->spec;
+ struct diff_filepair p = {one, two, range->status, 0};
+ struct strbuf header = STRBUF_INIT, meta = STRBUF_INIT;
+ const char *a_prefix, *b_prefix;
+ const char *name_a, *name_b, *a_one, *b_two;
+ const char *lbl[2];
+ const char *set = diff_get_color_opt(opt, DIFF_METAINFO);
+ const char *reset = diff_get_color_opt(opt, DIFF_RESET);
+ struct line_chunk chunk;
+ int must_show_header;
+
+ /*
+ * the ranges that touch no different file, in this case
+ * the line number will not change, and of course we have
+ * no sensible rang->pair since there is no diff run.
+ */
+ if (!one)
+ return;
+
+ if (range->status == DIFF_STATUS_DELETED)
+ die("We are following an nonexistent file, interesting!");
+
+ name_a = one->path;
+ name_b = two->path;
+ fill_metainfo(&meta, name_a, name_b, one, two, opt, &p, &must_show_header,
+ DIFF_OPT_TST(opt, COLOR_DIFF));
+
+ diff_set_mnemonic_prefix(opt, "a/", "b/");
+ if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
+ a_prefix = opt->b_prefix;
+ b_prefix = opt->a_prefix;
+ } else {
+ a_prefix = opt->a_prefix;
+ b_prefix = opt->b_prefix;
+ }
+
+ name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
+ name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
+
+ a_one = quote_two(a_prefix, name_a + (*name_a == '/'));
+ b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
+ lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
+ lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+ strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+ if (lbl[0][0] == '/') {
+ strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
+ } else if (lbl[1][0] == '/') {
+ strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+ } else if (one->mode != two->mode) {
+ strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
+ strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
+ }
+
+ fprintf(opt->file, "%s%s", header.buf, meta.buf);
+ strbuf_release(&meta);
+ strbuf_release(&header);
+ fprintf(opt->file, "%s--- %s%s\n", set, lbl[0], reset);
+ fprintf(opt->file, "%s+++ %s%s\n", set, lbl[1], reset);
+ free((void *)a_one);
+ free((void *)b_two);
+
+ chunk.one = one->data;
+ chunk.one_end = (const char *)one->data + one->size;
+ chunk.lone = 1;
+ chunk.two = two->data;
+ chunk.two_end = (const char *)two->data + two->size;
+ chunk.ltwo = 1;
+ chunk.range = range;
+ diff_flush_chunks(&rev->diffopt, &chunk);
+}
+
+static void flush_nontrivial_merge(struct rev_info *rev,
+ struct diff_line_range *range)
+{
+ struct diff_options *opt = &rev->diffopt;
+ const char *reset = diff_get_color_opt(opt, DIFF_RESET);
+ const char *frag = diff_get_color_opt(opt, DIFF_FRAGINFO);
+ const char *meta = diff_get_color_opt(opt, DIFF_METAINFO);
+ const char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);
+
+ fprintf(opt->file, "%s%s%s\n", meta, "nontrivial merge found", reset);
+
+ while (range) {
+ if (range->nr) {
+ int lno = 1;
+ const char *ptr = range->spec->data;
+ const char *end = range->spec->data + range->spec->size;
+ int i = 0;
+ fprintf(opt->file, "%s%s%s\n\n", meta, range->spec->path, reset);
+ for (; i < range->nr; i++) {
+ struct line_range *r = range->ranges + i;
+ fprintf(opt->file, "%s@@ %ld,%ld @@%s\n", frag, r->start,
+ r->end - r->start + 1, reset);
+ flush_lines(opt, &ptr, end, r->start, r->end,
+ &lno, new, ' ');
+ }
+ fprintf(opt->file, "\n");
+ }
+ range = range->next;
+ }
+}
+
+static void line_log_flush(struct rev_info *rev, struct commit *c)
+{
+ struct diff_line_range *range = lookup_line_range(rev, c);
+ struct diff_line_range *nontrivial = lookup_decoration(&rev->nontrivial_merge, &c->object);
+ struct log_info log;
+
+ if (!range)
+ return;
+
+ log.commit = c;
+ log.parent = NULL;
+ rev->loginfo = &log;
+ show_log(rev);
+ rev->loginfo = NULL;
+ /*
+ * Add a new line after each commit message, of course we should
+ * add --graph alignment later when the patches comes to master.
+ */
+ fprintf(rev->diffopt.file, "\n");
+
+ if (c->object.flags & EVIL_MERGE)
+ return flush_nontrivial_merge(rev, nontrivial);
+
+ while (range) {
+ if (range->diff)
+ diff_flush_filepair(rev, range);
+ range = range->next;
+ }
+}
+
+int line_log_walk(struct rev_info *rev)
+{
+ struct commit *commit;
+ struct commit_list *list = NULL;
+ struct diff_line_range *r = NULL;
+
+ if (prepare_revision_walk(rev))
+ die("revision walk prepare failed");
+
+ /*
+ * Note that -L automatically turns on --topo-order, so
+ * rev->commits already holds all commits in the range. The
+ * first commit is our starting point.
+ */
+ list = rev->commits;
+ if (list) {
+ list->item->object.flags |= RANGE_UPDATE;
+ list = list->next;
+ }
+ /* Clear the flags */
+ while (list) {
+ list->item->object.flags &= ~(RANGE_UPDATE | EVIL_MERGE | NEED_PRINT);
+ list = list->next;
+ }
+
+ list = rev->commits;
+ while (list) {
+ struct commit_list *need_free = list;
+ commit = list->item;
+
+ if (commit->object.flags & RANGE_UPDATE)
+ assign_parents_range(rev, commit);
+
+ if (commit->object.flags & NEED_PRINT)
+ line_log_flush(rev, commit);
+
+ clear_commit_line_range(rev, commit);
+
+ r = lookup_decoration(&rev->nontrivial_merge, &commit->object);
+ if (r) {
+ free_diff_line_ranges(r);
+ add_decoration(&rev->nontrivial_merge, &commit->object,
+ NULL);
+ }
+
+ list = list->next;
+ free(need_free);
+ }
+
+ return 0;
+}
diff --git a/line.h b/line.h
index 5878c94..5f2931a 100644
--- a/line.h
+++ b/line.h
@@ -1,6 +1,8 @@
#ifndef LINE_H
#define LINE_H
+#include "diffcore.h"
+
/*
* Parse one item in an -L begin,end option w.r.t. the notional file
* object 'cb_data' consisting of 'lines' lines.
@@ -20,4 +22,51 @@ extern int parse_range_arg(const char *arg,
void *cb_data, long lines,
long *begin, long *end);
+/*
+ * Scan past a range argument that could be parsed by
+ * 'parse_range_arg', to help the caller determine the start of the
+ * filename in '-L n,m:file' syntax.
+ *
+ * Returns a pointer to the first character after the 'n,m' part, or
+ * NULL in case the argument is obviously malformed.
+ */
+
+extern const char *skip_range_arg(const char *arg);
+
+struct rev_info;
+struct commit;
+struct diff_line_range;
+struct diff_options;
+
+struct line_range;
+
+struct diff_line_range {
+ struct diff_filespec *prev;
+ struct diff_filespec *spec;
+ char status;
+ int alloc;
+ int nr;
+ struct line_range *ranges;
+ unsigned int touched:1,
+ diff:1;
+ struct diff_line_range *next;
+};
+
+static inline void diff_line_range_init(struct diff_line_range *r)
+{
+ r->prev = r->spec = NULL;
+ r->status = '\0';
+ r->alloc = r->nr = 0;
+ r->ranges = NULL;
+ r->next = NULL;
+ r->touched = 0;
+ r->diff = 0;
+}
+
+extern void diff_line_range_append(struct diff_line_range *r, const char *arg);
+
+extern void line_log_init(struct rev_info *rev, struct diff_line_range *r);
+
+extern int line_log_walk(struct rev_info *rev);
+
#endif /* LINE_H */
diff --git a/revision.c b/revision.c
index 6465c45..369ec56 100644
--- a/revision.c
+++ b/revision.c
@@ -1662,6 +1662,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (revs->combine_merges)
revs->ignore_merges = 0;
revs->diffopt.abbrev = revs->abbrev;
+
+ if (revs->line_level_traverse) {
+ revs->limited = 1;
+ revs->topo_order = 1;
+ }
+
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
diff --git a/revision.h b/revision.h
index 8897368..585b15f 100644
--- a/revision.h
+++ b/revision.h
@@ -14,7 +14,10 @@
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
-#define ALL_REV_FLAGS ((1u<<9)-1)
+#define RANGE_UPDATE (1u<<9) /* for line level traverse */
+#define NEED_PRINT (1u<<10)
+#define EVIL_MERGE (1u<<11)
+#define ALL_REV_FLAGS ((1u<<12)-1)
#define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2
@@ -68,7 +71,8 @@ struct rev_info {
cherry_pick:1,
bisect:1,
ancestry_path:1,
- first_parent_only:1;
+ first_parent_only:1,
+ line_level_traverse:1;
/* Diff flags */
unsigned int diff:1,
@@ -137,6 +141,9 @@ struct rev_info {
/* commit counts */
int count_left;
int count_right;
+ /* line level range that we are chasing */
+ struct decoration line_range;
+ struct decoration nontrivial_merge;
};
#define REV_TREE_SAME 0
diff --git a/t/t4301-log-line-single-history.sh b/t/t4301-log-line-single-history.sh
new file mode 100755
index 0000000..59e9654
--- /dev/null
+++ b/t/t4301-log-line-single-history.sh
@@ -0,0 +1,685 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test git log -L with single line of history'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+}
+EOF
+
+cat >path1 <<\EOF
+void output()
+{
+ printf("hello world");
+}
+EOF
+
+test_expect_success 'add path0/path1 and commit.' '
+ git add path0 path1 &&
+ git commit -m "Base commit"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 10;
+ int b = 11;
+ int c;
+ c = a + b;
+}
+EOF
+
+cat >path1 <<\EOF
+void output()
+{
+ const char *str = "hello world!";
+ printf("%s", str);
+}
+EOF
+
+test_expect_success 'Change the 2,3 lines of path0 and path1.' '
+ git add path0 path1 &&
+ git commit -m "Change 2,3 lines of path0 and path1"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 10;
+ int b = 11;
+ int c;
+ c = 10 * (a + b);
+}
+EOF
+
+test_expect_success 'Change the 5th line of path0.' '
+ git add path0 &&
+ git commit -m "Change the 5th line of path0"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 10;
+ int b = 11;
+ printf("%d", a - b);
+}
+EOF
+
+test_expect_success 'Final change of path0.' '
+ git add path0 &&
+ git commit -m "Final change of path0"
+'
+
+sed 's/Q/ /g' >expected-path0 <<\EOF
+Final change of path0
+
+diff --git a/path0 b/path0
+index ccdf243..ccf8bcf 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,6 @@
+ void func()
+ {
+Q int a = 10;
+Q int b = 11;
+- int c;
+- c = 10 * (a + b);
++ printf("%d", a - b);
+ }
+
+Change the 5th line of path0
+
+diff --git a/path0 b/path0
+index b0eb888..ccdf243 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+Q int a = 10;
+Q int b = 11;
+Q int c;
+- c = a + b;
++ c = 10 * (a + b);
+ }
+
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+- int a = 0;
+- int b = 1;
++ int a = 10;
++ int b = 11;
+Q int c;
+Q c = a + b;
+ }
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +1,7 @@
++void func()
++{
++ int a = 0;
++ int b = 1;
++ int c;
++ c = a + b;
++}
+EOF
+
+cat >expected-path1 <<\EOF
+Change 2,3 lines of path0 and path1
+
+diff --git a/path1 b/path1
+index 52be2a5..cc54b12 100644
+--- a/path1
++++ b/path1
+@@ -1,4 +1,5 @@
+ void output()
+ {
+- printf("hello world");
++ const char *str = "hello world!";
++ printf("%s", str);
+ }
+
+Base commit
+
+diff --git a/path1 b/path1
+new file mode 100644
+index 0000000..52be2a5
+--- /dev/null
++++ b/path1
+@@ -0,0 +1,4 @@
++void output()
++{
++ printf("hello world");
++}
+EOF
+
+sed 's/Q/ /g' >expected-pathall <<\EOF
+Final change of path0
+
+diff --git a/path0 b/path0
+index ccdf243..ccf8bcf 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,6 @@
+ void func()
+ {
+Q int a = 10;
+Q int b = 11;
+- int c;
+- c = 10 * (a + b);
++ printf("%d", a - b);
+ }
+
+Change the 5th line of path0
+
+diff --git a/path0 b/path0
+index b0eb888..ccdf243 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+Q int a = 10;
+Q int b = 11;
+Q int c;
+- c = a + b;
++ c = 10 * (a + b);
+ }
+
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -1,7 +1,7 @@
+ void func()
+ {
+- int a = 0;
+- int b = 1;
++ int a = 10;
++ int b = 11;
+Q int c;
+Q c = a + b;
+ }
+diff --git a/path1 b/path1
+index 52be2a5..cc54b12 100644
+--- a/path1
++++ b/path1
+@@ -1,4 +1,5 @@
+ void output()
+ {
+- printf("hello world");
++ const char *str = "hello world!";
++ printf("%s", str);
+ }
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +1,7 @@
++void func()
++{
++ int a = 0;
++ int b = 1;
++ int c;
++ c = a + b;
++}
+diff --git a/path1 b/path1
+new file mode 100644
+index 0000000..52be2a5
+--- /dev/null
++++ b/path1
+@@ -0,0 +1,4 @@
++void output()
++{
++ printf("hello world");
++}
+EOF
+
+cat >expected-linenum <<\EOF
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+- int a = 0;
+- int b = 1;
++ int a = 10;
++ int b = 11;
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +2,3 @@
++{
++ int a = 0;
++ int b = 1;
+EOF
+
+sed 's/Q/ /g' >expected-always <<\EOF
+Final change of path0
+
+diff --git a/path0 b/path0
+index ccdf243..ccf8bcf 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+Q int a = 10;
+Q int b = 11;
+
+Change the 5th line of path0
+
+diff --git a/path0 b/path0
+index b0eb888..ccdf243 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+Q int a = 10;
+Q int b = 11;
+
+Change 2,3 lines of path0 and path1
+
+diff --git a/path0 b/path0
+index fb33939..b0eb888 100644
+--- a/path0
++++ b/path0
+@@ -2,3 +2,3 @@
+ {
+- int a = 0;
+- int b = 1;
++ int a = 10;
++ int b = 11;
+
+Base commit
+
+diff --git a/path0 b/path0
+new file mode 100644
+index 0000000..fb33939
+--- /dev/null
++++ b/path0
+@@ -0,0 +2,3 @@
++{
++ int a = 0;
++ int b = 1;
+EOF
+
+test_expect_success 'Show the line level log of path0' '
+ git log --pretty=format:%s%n%b -L /func/,/^}/:path0 > current-path0
+'
+
+test_expect_success 'validate the path0 output.' '
+ test_cmp current-path0 expected-path0
+'
+
+test_expect_success 'Show the line level log of path1' '
+ git log --pretty=format:%s%n%b -L /output/,/^}/:path1 > current-path1
+'
+
+test_expect_success 'validate the path1 output.' '
+ test_cmp current-path1 expected-path1
+'
+
+test_expect_success 'Show the line level log of two files' '
+ git log --pretty=format:%s%n%b -L /func/,/^}/:path0 -L /output/,/^}/:path1 > current-pathall
+'
+
+test_expect_success 'validate the all path output.' '
+ test_cmp current-pathall expected-pathall
+'
+
+test_expect_success 'Test the line number argument' '
+ git log --pretty=format:%s%n%b -L 2,4:path0 > current-linenum
+'
+
+test_expect_success 'validate the line number output.' '
+ test_cmp current-linenum expected-linenum
+'
+test_expect_success 'Test the --full-line-diff option' '
+ git log --pretty=format:%s%n%b --full-line-diff -L 2,4:path0 > current-always
+'
+
+test_expect_success 'validate the --full-line-diff output.' '
+ test_cmp current-always expected-always
+'
+
+# Rerun all log with graph
+test_expect_success 'Show the line level log of path0 with --graph' '
+ git log --pretty=format:%s%n%b --graph -L /func/,/^}/:path0 > current-path0-graph
+'
+
+test_expect_success 'Show the line level log of path1 with --graph' '
+ git log --pretty=format:%s%n%b --graph -L /output/,/^}/:path1 > current-path1-graph
+'
+
+test_expect_success 'Show the line level log of two files with --graph' '
+ git log --pretty=format:%s%n%b --graph -L /func/,/^}/:path0 -L /output/,/^}/:path1 > current-pathall-graph
+'
+
+test_expect_success 'Test the line number argument with --graph' '
+ git log --pretty=format:%s%n%b --graph -L 2,4:path0 > current-linenum-graph
+'
+
+test_expect_success 'Test the --full-line-diff option with --graph option' '
+ git log --pretty=format:%s%n%b --full-line-diff --graph -L 2,4:path0 > current-always-graph
+'
+
+sed -e 's/Q/ /g' -e 's/#$//' > expected-path0-graph <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index ccdf243..ccf8bcf 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,6 @@
+| void func()
+| {
+|QQ int a = 10;
+|QQ int b = 11;
+| - int c;
+| - c = 10 * (a + b);
+| + printf("%d", a - b);
+| }
+| #
+* Change the 5th line of path0
+| #
+| diff --git a/path0 b/path0
+| index b0eb888..ccdf243 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+| void func()
+| {
+|QQ int a = 10;
+|QQ int b = 11;
+|QQ int c;
+| - c = a + b;
+| + c = 10 * (a + b);
+| }
+| #
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+| void func()
+| {
+| - int a = 0;
+| - int b = 1;
+| + int a = 10;
+| + int b = 11;
+|QQ int c;
+|QQ c = a + b;
+| }
+| #
+* Base commit
+ #
+ diff --git a/path0 b/path0
+ new file mode 100644
+ index 0000000..fb33939
+ --- /dev/null
+ +++ b/path0
+ @@ -0,0 +1,7 @@
+ +void func()
+ +{
+ + int a = 0;
+ + int b = 1;
+ + int c;
+ + c = a + b;
+ +}
+EOF
+
+sed 's/#$//' > expected-path1-graph <<\EOF
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path1 b/path1
+| index 52be2a5..cc54b12 100644
+| --- a/path1
+| +++ b/path1
+| @@ -1,4 +1,5 @@
+| void output()
+| {
+| - printf("hello world");
+| + const char *str = "hello world!";
+| + printf("%s", str);
+| }
+| #
+* Base commit
+ #
+ diff --git a/path1 b/path1
+ new file mode 100644
+ index 0000000..52be2a5
+ --- /dev/null
+ +++ b/path1
+ @@ -0,0 +1,4 @@
+ +void output()
+ +{
+ + printf("hello world");
+ +}
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' > expected-pathall-graph <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index ccdf243..ccf8bcf 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,6 @@
+| void func()
+| {
+|QQ int a = 10;
+|QQ int b = 11;
+| - int c;
+| - c = 10 * (a + b);
+| + printf("%d", a - b);
+| }
+| #
+* Change the 5th line of path0
+| #
+| diff --git a/path0 b/path0
+| index b0eb888..ccdf243 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+| void func()
+| {
+|QQ int a = 10;
+|QQ int b = 11;
+|QQ int c;
+| - c = a + b;
+| + c = 10 * (a + b);
+| }
+| #
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -1,7 +1,7 @@
+| void func()
+| {
+| - int a = 0;
+| - int b = 1;
+| + int a = 10;
+| + int b = 11;
+|QQ int c;
+|QQ c = a + b;
+| }
+| diff --git a/path1 b/path1
+| index 52be2a5..cc54b12 100644
+| --- a/path1
+| +++ b/path1
+| @@ -1,4 +1,5 @@
+| void output()
+| {
+| - printf("hello world");
+| + const char *str = "hello world!";
+| + printf("%s", str);
+| }
+| #
+* Base commit
+ #
+ diff --git a/path0 b/path0
+ new file mode 100644
+ index 0000000..fb33939
+ --- /dev/null
+ +++ b/path0
+ @@ -0,0 +1,7 @@
+ +void func()
+ +{
+ + int a = 0;
+ + int b = 1;
+ + int c;
+ + c = a + b;
+ +}
+ diff --git a/path1 b/path1
+ new file mode 100644
+ index 0000000..52be2a5
+ --- /dev/null
+ +++ b/path1
+ @@ -0,0 +1,4 @@
+ +void output()
+ +{
+ + printf("hello world");
+ +}
+EOF
+
+sed 's/#$//' > expected-linenum-graph <<\EOF
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+| {
+| - int a = 0;
+| - int b = 1;
+| + int a = 10;
+| + int b = 11;
+| #
+* Base commit
+ #
+ diff --git a/path0 b/path0
+ new file mode 100644
+ index 0000000..fb33939
+ --- /dev/null
+ +++ b/path0
+ @@ -0,0 +2,3 @@
+ +{
+ + int a = 0;
+ + int b = 1;
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' > expected-always-graph <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index ccdf243..ccf8bcf 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+| {
+|QQ int a = 10;
+|QQ int b = 11;
+| #
+* Change the 5th line of path0
+| #
+| diff --git a/path0 b/path0
+| index b0eb888..ccdf243 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+| {
+|QQ int a = 10;
+|QQ int b = 11;
+| #
+* Change 2,3 lines of path0 and path1
+| #
+| diff --git a/path0 b/path0
+| index fb33939..b0eb888 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,3 +2,3 @@
+| {
+| - int a = 0;
+| - int b = 1;
+| + int a = 10;
+| + int b = 11;
+| #
+* Base commit
+ #
+ diff --git a/path0 b/path0
+ new file mode 100644
+ index 0000000..fb33939
+ --- /dev/null
+ +++ b/path0
+ @@ -0,0 +2,3 @@
+ +{
+ + int a = 0;
+ + int b = 1;
+EOF
+
+test_expect_success 'validate the path0 output.' '
+ test_cmp current-path0-graph expected-path0-graph
+'
+
+test_expect_success 'validate the path1 output.' '
+ test_cmp current-path1-graph expected-path1-graph
+'
+
+test_expect_success 'validate the all path output.' '
+ test_cmp current-pathall-graph expected-pathall-graph
+'
+
+test_expect_success 'validate graph output' '
+ test_cmp current-linenum-graph expected-linenum-graph
+'
+
+test_expect_success 'validate --full-line-diff output' '
+ test_cmp current-always-graph expected-always-graph
+'
+
+test_done
--
1.7.3.3.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6.1 5/8] log -L: support parent rewriting
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
` (3 preceding siblings ...)
2010-12-14 22:54 ` [PATCH v6.1 4/8] Implement line-history search (git log -L) Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 6/8] log -L: add --graph prefix before output Thomas Rast
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
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 | 208 +++++++++++++++++++++++++++++++-----
revision.c | 1 +
revision.h | 5 +-
t/t4302-log-line-merge-history.sh | 174 +++++++++++++++++++++++++++++++
4 files changed, 357 insertions(+), 31 deletions(-)
create mode 100755 t/t4302-log-line-merge-history.sh
diff --git a/line.c b/line.c
index 1ffcaba..10870a5 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;
+ }
+
+ while (r) {
+ if (r->nr)
+ evil = 1;
+ r = r->next;
+ }
+
+ if (!evil)
+ return;
- fprintf(opt->file, "%s%s%s\n", meta, "nontrivial merge found", reset);
+ 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);
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..0c8d023
--- /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
+'
+
+sed 's/Q/ /g' >expected <<\EOF
+Merge two branches
+
+nontrivial merge found
+path0
+@@ 3,1 @@
+Q 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 -e 's/Q/ /g' -e 's/#$//' > expected-graph <<\EOF
+* Merge two branches
+|\ #
+| | #
+| | nontrivial merge found
+| | path0
+| | @@ 3,1 @@
+| |QQ 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.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6.1 6/8] log -L: add --graph prefix before output
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
` (4 preceding siblings ...)
2010-12-14 22:54 ` [PATCH v6.1 5/8] log -L: support parent rewriting Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 7/8] log -L: add --full-line-diff option Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 8/8] log -L: implement move/copy detection (-M/-C) Thomas Rast
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
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 10870a5..1a9a947 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.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6.1 7/8] log -L: add --full-line-diff option
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
` (5 preceding siblings ...)
2010-12-14 22:54 ` [PATCH v6.1 6/8] log -L: add --graph prefix before output Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 8/8] log -L: implement move/copy detection (-M/-C) Thomas Rast
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
From: Bo Yang <struggleyb.nku@gmail.com>
Always print the interesting ranges even if the current
commit does not change any line of it.
Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-log.txt | 4 ++++
builtin/log.c | 8 +++++++-
line.c | 22 ++++++++++++++++------
revision.c | 2 ++
revision.h | 3 ++-
5 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 7fcf6e7..f5769bf 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -79,6 +79,10 @@ include::line-range-format.txt[]
You can specify this option more than once.
+--full-line-diff::
+ Always print the interesting range even if the current commit
+ does not change any line of the range.
+
[\--] <path>...::
Show only commits that affect any of the specified paths. To
prevent confusion with options and branch names, paths may need
diff --git a/builtin/log.c b/builtin/log.c
index 342d4de..fa57306 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -99,6 +99,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
{
int i;
int decoration_given = 0;
+ static int full_line_diff;
struct userformat_want w;
static struct line_opt_callback_data line_cb = {0};
@@ -106,6 +107,9 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
"Process line range n,m in file, counting from 1",
log_line_range_callback),
+ OPT_BOOLEAN(0, "full-line-diff", &full_line_diff,
+ "Always print the interesting range even if the \
+ current commit does not change any line of it"),
OPT_END()
};
@@ -188,8 +192,10 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
}
/* Test whether line level history is asked for */
- if (rev->line_level_traverse)
+ if (rev->line_level_traverse) {
line_log_init(rev, line_cb.ranges);
+ rev->full_line_diff = full_line_diff;
+ }
setup_pager();
}
diff --git a/line.c b/line.c
index 1a9a947..742c17f 100644
--- a/line.c
+++ b/line.c
@@ -1370,10 +1370,18 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra
/*
* the ranges that touch no different file, in this case
* the line number will not change, and of course we have
- * no sensible rang->pair since there is no diff run.
+ * no sensible range->pair since there is no diff run.
*/
- if (!one)
+ if (!one) {
+ if (rev->full_line_diff) {
+ chunk.two = two->data;
+ chunk.two_end = (const char *)two->data + two->size;
+ chunk.ltwo = 1;
+ chunk.range = range;
+ diff_flush_chunks(&rev->diffopt, &chunk);
+ }
return;
+ }
if (range->status == DIFF_STATUS_DELETED)
die("We are following an nonexistent file, interesting!");
@@ -1495,7 +1503,8 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)
struct strbuf *msgbuf;
if (!range || !(c->object.flags & NONTRIVIAL_MERGE ||
- c->object.flags & NEED_PRINT))
+ c->object.flags & NEED_PRINT ||
+ rev->full_line_diff))
return;
if (rev->graph)
@@ -1516,7 +1525,7 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)
flush_nontrivial_merge(rev, nontrivial);
else {
while (range) {
- if (range->diff)
+ if (range->diff || (range->nr && rev->full_line_diff))
diff_flush_filepair(rev, range);
range = range->next;
}
@@ -1573,7 +1582,7 @@ int line_log_walk(struct rev_info *rev)
/* Clear the flags */
while (list) {
list->item->object.flags &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |
- NEED_PRINT | EVIL_MERGE);
+ NEED_PRINT | EVIL_MERGE);
list = list->next;
}
@@ -1593,7 +1602,8 @@ int line_log_walk(struct rev_info *rev)
}
if (commit->object.flags & NEED_PRINT ||
- commit->object.flags & NONTRIVIAL_MERGE)
+ commit->object.flags & NONTRIVIAL_MERGE ||
+ rev->full_line_diff)
line_log_flush(rev, commit);
clear_commit_line_range(rev, commit);
diff --git a/revision.c b/revision.c
index fbebf2f..85a60d0 100644
--- a/revision.c
+++ b/revision.c
@@ -1912,6 +1912,8 @@ int prepare_revision_walk(struct rev_info *revs)
return -1;
if (revs->topo_order)
sort_in_topological_order(&revs->commits, revs->lifo);
+ if (revs->full_line_diff)
+ revs->dense = 0;
if (revs->simplify_merges)
simplify_merges(revs);
if (revs->children.name)
diff --git a/revision.h b/revision.h
index 6100904..29babf3 100644
--- a/revision.h
+++ b/revision.h
@@ -73,7 +73,8 @@ struct rev_info {
bisect:1,
ancestry_path:1,
first_parent_only:1,
- line_level_traverse:1;
+ line_level_traverse:1,
+ full_line_diff:1;
/* Diff flags */
unsigned int diff:1,
--
1.7.3.3.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6.1 8/8] log -L: implement move/copy detection (-M/-C)
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
` (6 preceding siblings ...)
2010-12-14 22:54 ` [PATCH v6.1 7/8] log -L: add --full-line-diff option Thomas Rast
@ 2010-12-14 22:54 ` Thomas Rast
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Rast @ 2010-12-14 22:54 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Bo Yang
From: Bo Yang <struggleyb.nku@gmail.com>
The basic idea is:
* Keep a list of "candidate snippets". Start out empty.
* Go through all candidates (determined by the level of detection
chosen) and diff them against the target file.
- For each common part in the diff, put it in the "candidate
snippets" if it's "worth it". (Notably there is no point in
adding a snippet that is fully contained in another.)
* Score the snippets. Lines with alphanumeric characters count more.
* Filter out snippets with low score. Where there are overlaps,
favour higher scores.
Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
line.c | 556 ++++++++++++++++++++++++++++++++++++++-
t/t4303-log-line-move-detect.sh | 238 +++++++++++++++++
t/t4304-log-line-copy-detect.sh | 220 +++++++++++++++
3 files changed, 1001 insertions(+), 13 deletions(-)
create mode 100755 t/t4303-log-line-move-detect.sh
create mode 100755 t/t4304-log-line-copy-detect.sh
diff --git a/line.c b/line.c
index 742c17f..ae176da 100644
--- a/line.c
+++ b/line.c
@@ -17,6 +17,7 @@ struct print_range {
int start, end; /* Line range of post-image */
int pstart, pend; /* Line range of pre-image */
int line_added : 1; /* whether this range is added */
+ int copied : 1;
};
struct print_pair {
@@ -30,6 +31,7 @@ struct line_range {
long pstart, pend; /* The corresponding range of parent commit */
struct print_pair pair;
/* The changed lines inside this range */
+ int copy_score;
unsigned int diff:1;
};
@@ -66,6 +68,7 @@ static inline void print_range_init(struct print_range *r)
r->start = r->end = 0;
r->pstart = r->pend = 0;
r->line_added = 0;
+ r->copied = 0;
}
static inline void print_pair_init(struct print_pair *p)
@@ -104,6 +107,7 @@ static inline void line_range_clear(struct line_range *r)
r->start = r->end = 0;
r->pstart = r->pend = 0;
print_pair_clear(&r->pair);
+ r->copy_score = 0;
r->diff = 0;
}
@@ -594,7 +598,7 @@ static void add_line_range(struct rev_info *revs, struct commit *commit,
ret = lookup_decoration(&revs->line_range, &commit->object);
if (ret && r)
diff_line_range_merge(ret, r);
- else
+ else if (r != NULL)
add_decoration(&revs->line_range, &commit->object, r);
if (r)
@@ -620,6 +624,16 @@ static void clear_commit_line_range(struct rev_info *revs, struct commit *commit
return ret;
}
+struct diff_line_range *delete_line_range(struct rev_info *revs, struct commit *commit)
+{
+ struct diff_line_range *ret = NULL;
+
+ ret = lookup_decoration(&revs->line_range, &commit->object);
+ add_decoration(&revs->line_range, &commit->object, NULL);
+
+ return ret;
+}
+
void line_log_init(struct rev_info *rev, struct diff_line_range *r)
{
struct commit *commit = NULL;
@@ -647,6 +661,515 @@ struct take_range_cb_data {
* commit and its parent */
};
+struct map {
+ long start, end;
+ long pstart, pend;
+ struct diff_filespec *spec;
+ int score;
+};
+
+struct mac_cb_data {
+ long plno, tlno;
+ int nr;
+ int alloc;
+ struct map *maps;
+ struct diff_filespec *spec;
+};
+
+struct mac_state {
+ int nr;
+ int alloc;
+ struct map *maps;
+};
+
+static void mac_state_init(struct mac_state *state)
+{
+ state->nr = state->alloc = 0;
+ state->maps = NULL;
+}
+
+static void mac_cb(void *data, long same, long p_next, long t_next)
+{
+ struct mac_cb_data *d = data;
+ long p_start = d->plno + 1, t_start = d->tlno + 1;
+ long p_end = p_start + same - t_start, t_end = same;
+
+ if (t_end >= t_start) {
+ ALLOC_GROW(d->maps, (d->nr + 1), d->alloc);
+ d->maps[d->nr].start = t_start;
+ d->maps[d->nr].end = t_end;
+ d->maps[d->nr].pstart = p_start;
+ d->maps[d->nr].pend = p_end;
+ d->maps[d->nr].spec = d->spec;
+ d->nr++;
+ }
+
+ d->plno = p_next;
+ d->tlno = t_next;
+}
+
+static void mac_state_insert(struct mac_state *state, long s_start, long s_end,
+ long start, long end, struct diff_filespec *spec)
+{
+ int i = 0;
+ struct map *maps;
+
+ for (; i < state->nr; i++) {
+ if (state->maps[i].start > end)
+ break;
+ }
+
+ state->nr++;
+ ALLOC_GROW(state->maps, state->nr, state->alloc);
+ maps = state->maps;
+ memmove(maps + i + 1, maps + i, (state->nr - i - 1) * sizeof(*maps));
+ maps[i].start = s_start;
+ maps[i].end = s_end;
+ maps[i].pstart = start;
+ maps[i].pend = end;
+ maps[i].spec = spec;
+ spec->count++;
+}
+
+static void mac_state_remove(struct mac_state *state, long s_start, long s_end,
+ long start, long end)
+{
+ int i = 0;
+ struct map *maps = state->maps;
+
+ while (i < state->nr) {
+ if (maps[i].start < start && maps[i].end > start) {
+ maps[i].end = s_start - 1;
+ maps[i].pend -= start - s_start;
+ }
+ if (maps[i].start < end && maps[i].end > end) {
+ maps[i].start = s_end + 1;
+ maps[i].pstart += end - s_end;
+ }
+ if (maps[i].start >=start && maps[i].end <= end) {
+ memmove(maps + i, maps + i + 1, (state->nr - i - 1) * sizeof(*maps));
+ state->nr--;
+ i--;
+ }
+
+ i++;
+ }
+}
+
+/*
+ * Generally, the 'struct line_range's pstart/pend is used to store
+ * the pre-image of current range. But here, we use it to store the
+ * line range of the 'artificial' memory file.
+ */
+static struct mac_state *merge_mac(struct mac_cb_data *data,
+ struct mac_state *state, long lines)
+{
+ struct map *maps = data->maps;
+ int i = 0;
+
+ assert(state);
+
+ while (i < data->nr) {
+ int j = 0;
+ long start = maps[i].start;
+ long end = maps[i].end;
+ int should_insert = 0;
+ long should_start = start;
+ long should_end = end;
+ long may_start = start, may_end = end;
+ long start_range_len = 0, end_range_len = 0;
+
+ /* first round: finds whether this range should be inserted */
+ if (!state->nr)
+ should_insert = 1;
+ while (state && j < state->nr) {
+ if (state->maps[j].end <= start) {
+ if (j+1 == state->nr)
+ should_insert = 1;
+ j++;
+ continue;
+ }
+ if (state->maps[j].start > end)
+ should_insert = 1;
+ else {
+ if (state->maps[j].start <= start) {
+ if (state->maps[j].end >= end)
+ should_insert = 0;
+ else {
+ should_insert = 1;
+ may_start = state->maps[j].end + 1;
+ start_range_len = state->maps[j].end -
+ state->maps[j].start + 1;
+ }
+ } else {
+ if (state->maps[j].end > end) {
+ should_insert = 1;
+ may_end = state->maps[j].start - 1;
+ end_range_len = state->maps[j].end -
+ state->maps[j].start + 1;
+ } else
+ should_insert = 1;
+ }
+ }
+
+ j++;
+ }
+
+ /* second round: insert the new range and adjust the current one */
+ if (should_insert) {
+ /* We always keep the longest range in prior */
+ if (start_range_len > end_range_len) {
+ if (start_range_len > should_end - should_start + 1)
+ should_start = may_start;
+ if (end_range_len > should_end - should_start + 1)
+ should_end = may_end;
+ } else {
+ if (end_range_len > should_end - should_start + 1)
+ should_end = may_end;
+ if (start_range_len > should_end - should_start + 1)
+ should_start = may_start;
+ }
+
+ mac_state_remove(state, should_start, should_end,
+ maps[i].start, maps[i].end);
+
+ start = maps[i].pstart + should_start - maps[i].start;
+ end = maps[i].pend - (maps[i].end - should_end);
+ mac_state_insert(state, should_start, should_end, start, end, data->spec);
+ }
+
+ i++;
+ }
+
+ return state;
+}
+
+static struct mac_state *find_mac_in_file(mmfile_t *file_p, mmfile_t *file_t,
+ long lines, unsigned char *scores, struct diff_filespec *spec,
+ struct mac_state *state)
+{
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ struct mac_cb_data cb = {0, 0, 0, 0, NULL, spec};
+
+ memset(&xpp, 0, sizeof(xpp));
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = xecfg.interhunkctxlen = 0;
+
+ xdi_diff_hunks(file_p, file_t, mac_cb, &cb, &xpp, &xecfg);
+
+ if (cb.tlno < lines) {
+ ALLOC_GROW(cb.maps, (cb.nr + 1), cb.alloc);
+ cb.maps[cb.nr].start = cb.tlno + 1;
+ cb.maps[cb.nr].end = lines;
+ cb.maps[cb.nr].pstart = cb.plno + 1;
+ cb.maps[cb.nr].pend = cb.plno + lines - cb.tlno;
+ cb.maps[cb.nr].spec = spec;
+ cb.nr++;
+ }
+
+ if (cb.nr)
+ state = merge_mac(&cb, state, lines);
+ free(cb.maps);
+
+ return state;
+}
+
+#define LINE_SCORE 6
+#define TRIVIAL_SCORE 2
+static void setup_mac_file(struct diff_filespec *spec, mmfile_t *lines,
+ unsigned char *scores, long start, long end)
+{
+ int i = 0;
+ int line = 1;
+ int size = 0;
+ char *data = spec->data;
+
+ memset(scores, TRIVIAL_SCORE, end - start + 1);
+ while (i < spec->size) {
+ int index = line - start;
+ if (line < start) {
+ if (data[i] == '\n')
+ line++;
+ i++;
+ continue;
+ }
+ if (line > end)
+ break;
+ if (size == 0)
+ lines->ptr = data + i;
+ size++;
+ if (scores[index] == TRIVIAL_SCORE && data[i] <= 'z' && data[i] >= 'a')
+ scores[index] = LINE_SCORE;
+ if (scores[index] == TRIVIAL_SCORE && data[i] <= 'Z' && data[i] >= 'A')
+ scores[index] = LINE_SCORE;
+ if (scores[index] == TRIVIAL_SCORE && data[i] <= '9' && data[i] >= '0')
+ scores[index] = LINE_SCORE;
+ if (data[i] == '\n')
+ line++;
+
+ i++;
+ }
+
+ lines->size = size;
+}
+
+static void mac_state_cal_score(long lines, unsigned char *scores, struct mac_state *state)
+{
+ struct map *maps = state->maps;
+ int i = 0;
+
+ while (i < state->nr) {
+ int score = 0;
+ int j = maps[i].start;
+ while (j <= maps[i].end) {
+ assert(j <= lines);
+ score += scores[j - 1];
+ j++;
+ }
+
+ maps[i].score = score;
+ i++;
+ }
+}
+
+#define MIN_GAP 3
+#define RANGE_MIN_SCORE 20
+static struct diff_line_range *mac_combine_remove(struct diff_line_range *range)
+{
+ struct diff_line_range *r = range, *prev = range, *ret = range;
+
+ // Firstly to combine adjacent-enough range
+ while (r) {
+ int i = 0;
+ struct line_range *rs = r->ranges;
+ for (; i < r->nr; i++) {
+ if (i + 1 < r->nr &&
+ (rs[i + 1].end - rs[i].start < MIN_GAP)) {
+ rs[i].end = rs[i + 1].end;
+ rs[i].copy_score += rs[i + 1].copy_score;
+ memmove(rs + i + 1, rs + i + 2, (r->nr - i - 1) * sizeof(*rs));
+ r->nr--;
+ i--;
+ }
+ }
+ r = r->next;
+ }
+
+ // then delete the trivial ones
+ r = range;
+ while (r) {
+ int i = 0;
+ struct line_range *rs = r->ranges;
+ for (; i < r->nr; i++) {
+ if (rs[i].copy_score < RANGE_MIN_SCORE) {
+ memmove(rs + i, rs + i + 1, (r->nr - i) * sizeof(*rs));
+ r->nr--;
+ i--;
+ }
+ }
+ r = r->next;
+ }
+
+ // then delete the empty diff_line_range
+ r = range;
+ while (r) {
+ struct diff_line_range *next = r->next;
+ if (!r->nr) {
+ diff_line_range_clear(r);
+ free(r);
+ if (ret == r) {
+ ret = next;
+ prev = next;
+ }
+ } else if (prev != r) {
+ prev->next = r;
+ prev = r;
+ }
+
+ r = next;
+ }
+
+ return ret;
+}
+
+static struct diff_line_range *mac_state_to_line_range(struct mac_state *state)
+{
+ struct diff_line_range *ret = NULL;
+ struct diff_line_range *prev = NULL;
+ struct map *maps = state->maps;
+ int i = 0;
+ struct line_range *rg = NULL;
+
+ while (i < state->nr) {
+ struct diff_line_range *r = ret;
+ while (r) {
+ if (r->spec == maps[i].spec) {
+ rg = diff_line_range_insert(r, NULL, maps[i].pstart,
+ maps[i].pend);
+ rg->copy_score += maps[i].score;
+ break;
+ }
+ r = r->next;
+ }
+
+ if (!r) {
+ r = xmalloc(sizeof(*r));
+ diff_line_range_init(r);
+ r->spec = maps[i].spec;
+ rg = diff_line_range_insert(r, NULL, maps[i].pstart, maps[i].pend);
+ rg->copy_score += maps[i].score;
+
+ if (!ret) {
+ ret = r;
+ prev = r;
+ } else {
+ prev->next = r;
+ prev = r;
+ }
+ }
+
+ i++;
+ }
+
+ return mac_combine_remove(ret);
+}
+
+struct mac_state *find_mac_in_one_file(struct commit *p,
+ char *path, mmfile_t *file_t, long lines,
+ unsigned char *scores)
+{
+ struct diff_filespec *spec = alloc_filespec(path);
+ unsigned char sha1[20];
+ unsigned mode;
+ mmfile_t file_p;
+ struct mac_state *ret = xmalloc(sizeof(*ret));
+
+ mac_state_init(ret);
+ if (get_tree_entry(p->object.sha1, path, sha1, &mode))
+ return NULL;
+ fill_filespec(spec, sha1, mode);
+ diff_populate_filespec(spec, 0);
+ file_p.ptr = spec->data;
+ file_p.size = spec->size;
+
+ ret = find_mac_in_file(&file_p, file_t, lines, scores, spec, ret);
+ mac_state_cal_score(lines, scores, ret);
+
+ return ret;
+}
+
+struct mac_state *find_mac_in_all_file(struct rev_info *rev, struct commit *c,
+ struct commit *p, mmfile_t *file_t, long lines,
+ unsigned char *scores)
+{
+ struct diff_options diff_opts;
+ const char *paths[1];
+ int j = 0;
+ struct mac_state *state = xmalloc(sizeof(*state));
+
+ mac_state_init(state);
+ /* ok, we can start to do the move/copy detect now */
+ diff_setup(&diff_opts);
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ paths[0] = NULL;
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff-setup in line.c");
+ if (DIFF_OPT_TST(&rev->diffopt, FIND_COPIES_HARDER))
+ DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
+
+ diff_tree_sha1(p->tree->object.sha1, c->tree->object.sha1,
+ "", &diff_opts);
+ for (j = 0; j < diff_queued_diff.nr; j++) {
+ struct diff_filepair *p = diff_queued_diff.queue[j];
+ mmfile_t file_p;
+
+ if (!DIFF_FILE_VALID(p->one))
+ continue;
+ if (S_ISGITLINK(p->one->mode))
+ continue;
+ diff_populate_filespec(p->one, 0);
+ file_p.ptr = p->one->data;
+ file_p.size = p->one->size;
+
+ p->one->count++;
+ state = find_mac_in_file(&file_p, file_t, lines, scores, p->one, state);
+ }
+
+ diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
+
+ mac_state_cal_score(lines, scores, state);
+
+ return state;
+}
+
+static void find_mac_for_range(struct rev_info *rev,
+ struct commit *c, struct commit *p,
+ struct diff_line_range *r, struct print_range *pr)
+{
+ unsigned char *scores;
+ struct mac_state *state;
+ mmfile_t lines = {NULL, 0};
+ struct diff_line_range *copied = NULL;
+
+ /* Do not search for source of ranges shorter than 3 lines */
+ if (pr->line_added && (pr->end - pr->start) < 3)
+ return;
+
+ scores = xmalloc(pr->end - pr->start + 1);
+ setup_mac_file(r->spec, &lines, scores, pr->start, pr->end);
+
+ if (rev->diffopt.detect_rename == DIFF_DETECT_RENAME)
+ state = find_mac_in_one_file(p, r->spec->path, &lines,
+ pr->end - pr->start + 1, scores);
+ else
+ state = find_mac_in_all_file(rev, c, p, &lines,
+ pr->end - pr->start + 1, scores);
+
+ copied = mac_state_to_line_range(state);
+ if (copied) {
+ add_line_range(rev, p, copied);
+ pr->copied = 1;
+ }
+ free(scores);
+}
+
+/*
+ * Find the code move/copy, here we reuse the '-M/-C' options in diff options.
+ * -M means that finds the code in the same file;
+ * -C means that finds the code in all the files in parent commit.
+ */
+static void find_mac(struct rev_info *rev, struct commit *c)
+{
+ struct diff_line_range *r = lookup_line_range(rev, c);
+ struct diff_line_range *prange = NULL;
+ struct commit *p = NULL;
+
+ if (c->parents == NULL)
+ return;
+
+ assert(c->parents->next == NULL);
+ p = c->parents->item;
+ parse_commit(p);
+
+ while (r) {
+ int n;
+ for (n = 0; n < r->nr; n++) {
+ struct print_pair *pair = &r->ranges[n].pair;
+ int i;
+ for (i = 0; i < pair->nr; i++)
+ find_mac_for_range(rev, c, p, r, &pair->ranges[i]);
+ }
+
+ r = r->next;
+ }
+
+ add_line_range(rev, p, prange);
+}
+
#define SCALE_FACTOR 4
/*
* [p_start, p_end] represents the pre-image of current diff hunk,
@@ -942,7 +1465,7 @@ static int assign_range_to_parent(struct rev_info *rev, struct commit *commit,
void *tree1 = NULL, *tree2 = NULL;
struct tree_desc desc1, desc2;
struct diff_queue_struct *queue;
- struct take_range_cb_data cb_data = {NULL, cur_range, 0, 0};
+ struct take_range_cb_data cb_data = {NULL, cur_range, 0, 0, 0};
xpparam_t xpp;
xdemitconf_t xecfg;
int i, diff = 0;
@@ -1083,15 +1606,6 @@ static int assign_range_to_parent(struct rev_info *rev, struct commit *commit,
assert(parent);
assert(final_range->spec);
add_line_range(rev, parent, final_range);
- } else {
- /*
- * If there is no new ranges assigned to the parent,
- * we should mark it as a 'root' commit.
- */
- if (commit->parents && !commit->parents->next) {
- free(commit->parents);
- commit->parents = NULL;
- }
}
/* and the ranges of current commit is updated */
@@ -1120,6 +1634,18 @@ static void diff_update_parent_range(struct rev_info *rev,
}
assign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);
+
+ if (rev->diffopt.detect_rename > 0)
+ find_mac(rev, commit);
+
+ /*
+ * If there is no new ranges assigned to the parent,
+ * we should mark it as a 'root' commit.
+ */
+ if (c != NULL && lookup_line_range(rev, c) == NULL) {
+ free(commit->parents);
+ commit->parents = NULL;
+ }
}
struct commit_state {
@@ -1306,8 +1832,12 @@ static void diff_flush_range(struct diff_options *opt, struct line_chunk *chunk,
if (!pr->line_added)
flush_lines(opt, &chunk->one, chunk->one_end,
pr->pstart, pr->pend, &chunk->lone, old, '-');
- flush_lines(opt, &chunk->two, chunk->two_end,
- pr->start, pr->end, &chunk->ltwo, new, '+');
+ if (pr->copied)
+ flush_lines(opt, &chunk->two, chunk->two_end,
+ pr->start, pr->end, &chunk->ltwo, new, ' ');
+ else
+ flush_lines(opt, &chunk->two, chunk->two_end,
+ pr->start, pr->end, &chunk->ltwo, new, '+');
cur = pr->end + 1;
}
diff --git a/t/t4303-log-line-move-detect.sh b/t/t4303-log-line-move-detect.sh
new file mode 100755
index 0000000..0462ec0
--- /dev/null
+++ b/t/t4303-log-line-move-detect.sh
@@ -0,0 +1,238 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test git log -L with code movement'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+}
+
+void output()
+{
+ printf("hello world");
+}
+EOF
+
+test_expect_success 'add path0 and commit.' '
+ git add path0 &&
+ git commit -m "Base commit"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+}
+
+void output()
+{
+ int d = 3;
+ int e = 5;
+ printf("hello world");
+ printf("bye!");
+}
+EOF
+
+test_expect_success 'Change the some lines of path0.' '
+ git add path0 &&
+ git commit -m "Change some lines of path0"
+'
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+}
+
+void output()
+{
+ int d = 3;
+ int e = 5;
+ printf("hello world");
+ printf("bye!");
+}
+
+void comb()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+ int d = 3;
+ int e = 5;
+ printf("hello world");
+ printf("bye!");
+}
+EOF
+
+test_expect_success 'Move two functions into one' '
+ git add path0 &&
+ git commit -m "Move two functions into one"
+'
+
+cat >path0 <<\EOF
+void comb()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+ printf("hello world");
+ printf("bye!");
+}
+EOF
+
+test_expect_success 'Final change of path0.' '
+ git add path0 &&
+ git commit -m "Final change of path0"
+'
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-no-M <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index 495f978..b744a93 100644
+| --- a/path0
+| +++ b/path0
+| @@ -17,11 +1,9 @@
+| void comb()
+| {
+|QQ int a = 0;
+|QQ int b = 1;
+|QQ int c;
+|QQ c = a + b;
+| - int d = 3;
+| - int e = 5;
+|QQ printf("hello world");
+|QQ printf("bye!");
+| }
+| #
+* Move two functions into one
+ #
+ diff --git a/path0 b/path0
+ index cd42622..495f978 100644
+ --- a/path0
+ +++ b/path0
+ @@ -0,0 +17,11 @@
+ +void comb()
+ +{
+ + int a = 0;
+ + int b = 1;
+ + int c;
+ + c = a + b;
+ + int d = 3;
+ + int e = 5;
+ + printf("hello world");
+ + printf("bye!");
+ +}
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-M <<\EOF
+* Final change of path0
+| #
+| diff --git a/path0 b/path0
+| index 495f978..b744a93 100644
+| --- a/path0
+| +++ b/path0
+| @@ -17,11 +1,9 @@
+| void comb()
+| {
+|QQ int a = 0;
+|QQ int b = 1;
+|QQ int c;
+|QQ c = a + b;
+| - int d = 3;
+| - int e = 5;
+|QQ printf("hello world");
+|QQ printf("bye!");
+| }
+| #
+* Move two functions into one
+| #
+| diff --git a/path0 b/path0
+| index cd42622..495f978 100644
+| --- a/path0
+| +++ b/path0
+| @@ -0,0 +17,11 @@
+| void comb()
+| {
+|QQ int a = 0;
+|QQ int b = 1;
+|QQ int c;
+|QQ c = a + b;
+|QQ int d = 3;
+|QQ int e = 5;
+|QQ printf("hello world");
+|QQ printf("bye!");
+| }
+| #
+* Change some lines of path0
+| #
+| diff --git a/path0 b/path0
+| index f5e09df..cd42622 100644
+| --- a/path0
+| +++ b/path0
+| @@ -2,5 +2,5 @@
+| {
+|QQ int a = 0;
+|QQ int b = 1;
+|QQ int c;
+|QQ c = a + b;
+| @@ -11,2 +11,5 @@
+| + int d = 3;
+| + int e = 5;
+|QQ printf("hello world");
+| + printf("bye!");
+| }
+| #
+* Base commit
+ #
+ diff --git a/path0 b/path0
+ new file mode 100644
+ index 0000000..f5e09df
+ --- /dev/null
+ +++ b/path0
+ @@ -0,0 +2,5 @@
+ +{
+ + int a = 0;
+ + int b = 1;
+ + int c;
+ + c = a + b;
+ @@ -0,0 +11,2 @@
+ + printf("hello world");
+ +}
+EOF
+
+test_expect_success 'Show the line level log of path0' '
+ git log --pretty=format:%s%n%b --graph -L /comb/,/^}/:path0 > current-no-M
+'
+
+test_expect_success 'validate the path0 output.' '
+ test_cmp current-no-M expected-no-M
+'
+
+test_expect_success 'Show the line level log of path0 with -M' '
+ git log --pretty=format:%s%n%b --graph -M -L /comb/,/^}/:path0 > current-M
+'
+
+test_expect_success 'validate the path1 output.' '
+ test_cmp current-M expected-M
+'
+
+test_done
diff --git a/t/t4304-log-line-copy-detect.sh b/t/t4304-log-line-copy-detect.sh
new file mode 100755
index 0000000..de0e004
--- /dev/null
+++ b/t/t4304-log-line-copy-detect.sh
@@ -0,0 +1,220 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test git log -L with -C'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+cat >path0 <<\EOF
+void func()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+}
+EOF
+
+cat >path1 <<\EOF
+void output()
+{
+ printf("hello world");
+}
+EOF
+
+test_expect_success 'add path0/path1 and commit.' '
+ git add path0 path1 &&
+ git commit -m "Base commit"
+'
+
+cat >path1 <<\EOF
+void output()
+{
+ int d = 3;
+ int e = 5;
+ printf("hello world");
+ printf("bye!");
+}
+EOF
+
+test_expect_success 'Change the some lines of path1.' '
+ git add path1 &&
+ git commit -m "Change some lines of path1"
+'
+
+cat >path2 <<\EOF
+void comb()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+ int d = 3;
+ int e = 5;
+ printf("hello world");
+ printf("bye!");
+}
+EOF
+
+test_expect_success 'Move two functions into one in path2' '
+ git add path2 &&
+ git rm path0 path1 &&
+ git commit -m "Move two functions into path2"
+'
+
+cat >path2 <<\EOF
+void comb()
+{
+ int a = 0;
+ int b = 1;
+ int c;
+ c = a + b;
+ printf("hello world");
+ printf("bye!");
+}
+EOF
+
+test_expect_success 'Final change of path2.' '
+ git add path2 &&
+ git commit -m "Final change of path2"
+'
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-no-C <<\EOF
+* Final change of path2
+| #
+| diff --git a/path2 b/path2
+| index ca6a800..b744a93 100644
+| --- a/path2
+| +++ b/path2
+| @@ -1,11 +1,9 @@
+| void comb()
+| {
+|QQ int a = 0;
+|QQ int b = 1;
+|QQ int c;
+|QQ c = a + b;
+| - int d = 3;
+| - int e = 5;
+|QQ printf("hello world");
+|QQ printf("bye!");
+| }
+| #
+* Move two functions into path2
+ #
+ diff --git a/path2 b/path2
+ new file mode 100644
+ index 0000000..ca6a800
+ --- /dev/null
+ +++ b/path2
+ @@ -0,0 +1,11 @@
+ +void comb()
+ +{
+ + int a = 0;
+ + int b = 1;
+ + int c;
+ + c = a + b;
+ + int d = 3;
+ + int e = 5;
+ + printf("hello world");
+ + printf("bye!");
+ +}
+EOF
+
+sed -e 's/Q/ /g' -e 's/#$//' >expected-C <<\EOF
+* Final change of path2
+| #
+| diff --git a/path2 b/path2
+| index ca6a800..b744a93 100644
+| --- a/path2
+| +++ b/path2
+| @@ -1,11 +1,9 @@
+| void comb()
+| {
+|QQ int a = 0;
+|QQ int b = 1;
+|QQ int c;
+|QQ c = a + b;
+| - int d = 3;
+| - int e = 5;
+|QQ printf("hello world");
+|QQ printf("bye!");
+| }
+| #
+* Move two functions into path2
+| #
+| diff --git a/path2 b/path2
+| new file mode 100644
+| index 0000000..ca6a800
+| --- /dev/null
+| +++ b/path2
+| @@ -0,0 +1,11 @@
+| void comb()
+| {
+|QQ int a = 0;
+|QQ int b = 1;
+|QQ int c;
+|QQ c = a + b;
+|QQ int d = 3;
+|QQ int e = 5;
+|QQ printf("hello world");
+|QQ printf("bye!");
+| }
+| #
+* Change some lines of path1
+| #
+| diff --git a/path1 b/path1
+| index 52be2a5..bf3a80f 100644
+| --- a/path1
+| +++ b/path1
+| @@ -2,3 +2,6 @@
+| {
+| + int d = 3;
+| + int e = 5;
+|QQ printf("hello world");
+| + printf("bye!");
+| }
+| #
+* Base commit
+ #
+ diff --git a/path1 b/path1
+ new file mode 100644
+ index 0000000..52be2a5
+ --- /dev/null
+ +++ b/path1
+ @@ -0,0 +2,3 @@
+ +{
+ + printf("hello world");
+ +}
+ diff --git a/path0 b/path0
+ new file mode 100644
+ index 0000000..fb33939
+ --- /dev/null
+ +++ b/path0
+ @@ -0,0 +2,5 @@
+ +{
+ + int a = 0;
+ + int b = 1;
+ + int c;
+ + c = a + b;
+EOF
+
+test_expect_success 'Show the line level log of path2' '
+ git log --pretty=format:%s%n%b --graph -L /comb/,/^}/:path2 > current-no-C
+'
+
+test_expect_success 'validate the path2 output.' '
+ test_cmp current-no-C expected-no-C
+'
+
+test_expect_success 'Show the line level log of path2 with -C' '
+ git log --pretty=format:%s%n%b --graph -C -L /comb/,/^}/:path2 > current-C
+'
+
+test_expect_success 'validate the path2 output.' '
+ test_cmp current-C expected-C
+'
+
+test_done
--
1.7.3.3.807.g6ee1f
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v6.1 4/8] Implement line-history search (git log -L)
2010-12-14 22:54 ` [PATCH v6.1 4/8] Implement line-history search (git log -L) Thomas Rast
@ 2010-12-15 0:20 ` Junio C Hamano
0 siblings, 0 replies; 10+ messages in thread
From: Junio C Hamano @ 2010-12-15 0:20 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Bo Yang
Thomas Rast <trast@student.ethz.ch> writes:
> +void line_log_init(struct rev_info *rev, struct diff_line_range *r)
> +{
> + struct commit *commit = NULL;
> + struct diff_options *opt = &rev->diffopt;
> +
> + commit = (struct commit *)verify_commit(rev);
> + parse_lines(commit, r);
> +
> + add_line_range(rev, commit, r);
> + /*
> + * Note we support -M/-C to detect file rename
> + */
> + opt->nr_paths = 0;
> + diff_tree_release_paths(opt);
> +}
Note that opt->nr_paths may be going away soon (cf. nd/struct-pathspec
topic). Do you need this assignment here?
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2010-12-15 0:21 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <7vhbegroj2.fsf@alter.siamese.dyndns.org>
2010-12-14 22:54 ` [PATCH v6.1 0/8] git log -L, cleaned up and (hopefully) fixed Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 1/8] Refactor parse_loc Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 2/8] Export three functions from diff.c Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 3/8] Export rewrite_parents() for 'log -L' Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 4/8] Implement line-history search (git log -L) Thomas Rast
2010-12-15 0:20 ` Junio C Hamano
2010-12-14 22:54 ` [PATCH v6.1 5/8] log -L: support parent rewriting Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 6/8] log -L: add --graph prefix before output Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 7/8] log -L: add --full-line-diff option Thomas Rast
2010-12-14 22:54 ` [PATCH v6.1 8/8] log -L: implement move/copy detection (-M/-C) Thomas Rast
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).