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