Implement line-history search (git log -L)

This is a rewrite of much of Bo's work, mainly in an effort to split it into smaller, easier to understand routines. The algorithm is built around the struct range_set, which encodes a series of line ranges as intervals [a,b). This is used in two contexts: * A set of lines we are tracking (which will change as we dig through history). * To encode diffs, as pairs of ranges. The main routine is range_set_map_across_diff(). It processes the diff between a commit C and some parent P. It determines which diff hunks are relevant to the ranges tracked in C, and computes the new ranges for P. The algorithm is then simply to process history in topological order from newest to oldest, computing ranges and (partial) diffs. At branch points, we need to merge the ranges we are watching. We will find that many commits do not affect the chosen ranges, and mark them TREESAME (in addition to those already filtered by pathspec limiting). Another pass of history simplification then gets rid of such commits. This is wired as an extra filtering pass in the log machinery. This currently only reduces code duplication, but should allow for other simplifications and options to be used. Finally, we hook a diff printer into the output chain. Ideally we would wire directly into the diff logic, to optionally use features like word diff. However, that will require some major reworking of the diff chain, so we completely replace the output with our own diff for now. As this was a GSoC project, and has quite some history by now, many people have helped. In no particular order, thanks go to Jakub Narebski <jnareb@gmail.com> Jens Lehmann <Jens.Lehmann@web.de> Jonathan Nieder <jrnieder@gmail.com> Junio C Hamano <gitster@pobox.com> Ramsay Jones <ramsay@ramsay1.demon.co.uk> Will Palmer <wmpalmer@gmail.com> Apologies to everyone I forgot. Signed-off-by: Bo Yang <struggleyb.nku@gmail.com> Signed-off-by: Thomas Rast <trast@student.ethz.ch> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Thomas Rast committed Mar 28, 2013 at 17:47 UTC 12da1d1f6ffcd546a892a33302bb34fd37169022
20 files changed +2156 -2
Documentation/git-log.txt
+22
@@ -69,6 +69,23 @@ produced by --stat etc.
69 Note that only message is considered, if also a diff is shown
70 its size is not included.
71
72 +-L <start>,<end>:<file>::
73 + Trace the evolution of the line range given by "<start>,<end>"
74 + within the <file>. You may not give any pathspec limiters.
75 + This is currently limited to a walk starting from a single
76 + revision, i.e., you may only give zero or one positive
77 + revision arguments.
78 +
79 +<start> and <end> can take one of these forms:
80 +
81 +include::line-range-format.txt[]
82 +You can specify this option more than once.
83 +
84 +
85 +--full-line-diff::
86 + Always print the interesting range even if the current commit
87 + does not change any line of the range.
88 +
89 [\--] <path>...::
90 Show only commits that are enough to explain how the files
91 that match the specified paths came to be. See "History
@@ -138,6 +155,11 @@ Examples
155 This makes sense only when following a strict policy of merging all
156 topic branches when staying on a single integration branch.
157
158 +git log -L '/int main/',/^}/:main.c::
159 +
160 + Shows how the function `main()` in the file 'main.c' evolved
161 + over time.
162 +
163 `git log -3`::
164 Limits the number of commits to show to 3.
165
Makefile
+2
@@ -683,6 +683,7 @@ LIB_H += help.h
683 LIB_H += http.h
684 LIB_H += kwset.h
685 LIB_H += levenshtein.h
686 +LIB_H += line-log.h
687 LIB_H += line-range.h
688 LIB_H += list-objects.h
689 LIB_H += ll-merge.h
@@ -799,6 +800,7 @@ LIB_OBJS += hex.o
800 LIB_OBJS += ident.o
801 LIB_OBJS += kwset.o
802 LIB_OBJS += levenshtein.o
803 +LIB_OBJS += line-log.o
804 LIB_OBJS += line-range.o
805 LIB_OBJS += list-objects.o
806 LIB_OBJS += ll-merge.o
builtin/log.c
+31
@@ -19,6 +19,7 @@
19 #include "remote.h"
20 #include "string-list.h"
21 #include "parse-options.h"
22 +#include "line-log.h"
23 #include "branch.h"
24 #include "streaming.h"
25 #include "version.h"
@@ -41,6 +42,12 @@ static const char * const builtin_log_usage[] = {
42 NULL
43 };
44
45 +struct line_opt_callback_data {
46 + struct rev_info *rev;
47 + const char *prefix;
48 + struct string_list args;
49 +};
50 +
51 static int parse_decoration_style(const char *var, const char *value)
52 {
53 switch (git_config_maybe_bool(var, value)) {
@@ -75,6 +82,19 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
82 return 0;
83 }
84
85 +static int log_line_range_callback(const struct option *option, const char *arg, int unset)
86 +{
87 + struct line_opt_callback_data *data = option->value;
88 +
89 + if (!arg)
90 + return -1;
91 +
92 + data->rev->line_level_traverse = 1;
93 + string_list_append(&data->args, arg);
94 +
95 + return 0;
96 +}
97 +
98 static void cmd_log_init_defaults(struct rev_info *rev)
99 {
100 if (fmt_pretty)
@@ -97,6 +117,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
117 {
118 struct userformat_want w;
119 int quiet = 0, source = 0, mailmap = 0;
120 + static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
121
122 const struct option builtin_log_options[] = {
123 OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")),
@@ -104,9 +125,15 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
125 OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")),
126 { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
127 PARSE_OPT_OPTARG, decorate_callback},
128 + OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
129 + "Process line range n,m in file, counting from 1",
130 + log_line_range_callback),
131 OPT_END()
132 };
133
134 + line_cb.rev = rev;
135 + line_cb.prefix = prefix;
136 +
137 mailmap = use_mailmap_config;
138 argc = parse_options(argc, argv, prefix,
139 builtin_log_options, builtin_log_usage,
@@ -160,6 +187,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
187 rev->show_decorations = 1;
188 load_ref_decorations(decoration_style);
189 }
190 +
191 + if (rev->line_level_traverse)
192 + line_log_init(rev, line_cb.prefix, &line_cb.args);
193 +
194 setup_pager();
195 }
196
line-log.c new
+1179
@@ -0,0 +1,1179 @@
1 +#include "git-compat-util.h"
2 +#include "line-range.h"
3 +#include "cache.h"
4 +#include "tag.h"
5 +#include "blob.h"
6 +#include "tree.h"
7 +#include "diff.h"
8 +#include "commit.h"
9 +#include "decorate.h"
10 +#include "revision.h"
11 +#include "xdiff-interface.h"
12 +#include "strbuf.h"
13 +#include "log-tree.h"
14 +#include "graph.h"
15 +#include "line-log.h"
16 +
17 +static void range_set_grow(struct range_set *rs, size_t extra)
18 +{
19 + ALLOC_GROW(rs->ranges, rs->nr + extra, rs->alloc);
20 +}
21 +
22 +/* Either initialization would be fine */
23 +#define RANGE_SET_INIT {0}
24 +
25 +static void range_set_init(struct range_set *rs, size_t prealloc)
26 +{
27 + rs->alloc = rs->nr = 0;
28 + rs->ranges = NULL;
29 + if (prealloc)
30 + range_set_grow(rs, prealloc);
31 +}
32 +
33 +static void range_set_release(struct range_set *rs)
34 +{
35 + free(rs->ranges);
36 + rs->alloc = rs->nr = 0;
37 + rs->ranges = NULL;
38 +}
39 +
40 +/* dst must be uninitialized! */
41 +static void range_set_copy(struct range_set *dst, struct range_set *src)
42 +{
43 + range_set_init(dst, src->nr);
44 + memcpy(dst->ranges, src->ranges, src->nr*sizeof(struct range_set));
45 + dst->nr = src->nr;
46 +}
47 +static void range_set_move(struct range_set *dst, struct range_set *src)
48 +{
49 + range_set_release(dst);
50 + dst->ranges = src->ranges;
51 + dst->nr = src->nr;
52 + dst->alloc = src->alloc;
53 + src->ranges = NULL;
54 + src->alloc = src->nr = 0;
55 +}
56 +
57 +/* tack on a _new_ range _at the end_ */
58 +static void range_set_append(struct range_set *rs, long a, long b)
59 +{
60 + assert(a <= b);
61 + assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
62 + range_set_grow(rs, 1);
63 + rs->ranges[rs->nr].start = a;
64 + rs->ranges[rs->nr].end = b;
65 + rs->nr++;
66 +}
67 +
68 +static int range_cmp(const void *_r, const void *_s)
69 +{
70 + const struct range *r = _r;
71 + const struct range *s = _s;
72 +
73 + /* this could be simply 'return r.start-s.start', but for the types */
74 + if (r->start == s->start)
75 + return 0;
76 + if (r->start < s->start)
77 + return -1;
78 + return 1;
79 +}
80 +
81 +/*
82 + * Helper: In-place pass of sorting and merging the ranges in the
83 + * range set, to re-establish the invariants after another operation
84 + *
85 + * NEEDSWORK currently not needed
86 + */
87 +static void sort_and_merge_range_set(struct range_set *rs)
88 +{
89 + int i;
90 + int o = 1; /* output cursor */
91 +
92 + qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp);
93 +
94 + for (i = 1; i < rs->nr; i++) {
95 + if (rs->ranges[i].start <= rs->ranges[o-1].end) {
96 + rs->ranges[o-1].end = rs->ranges[i].end;
97 + } else {
98 + rs->ranges[o].start = rs->ranges[i].start;
99 + rs->ranges[o].end = rs->ranges[i].end;
100 + o++;
101 + }
102 + }
103 + assert(o <= rs->nr);
104 + rs->nr = o;
105 +}
106 +
107 +/*
108 + * Union of range sets (i.e., sets of line numbers). Used to merge
109 + * them when searches meet at a common ancestor.
110 + *
111 + * This is also where the ranges are consolidated into canonical form:
112 + * overlapping and adjacent ranges are merged, and empty ranges are
113 + * removed.
114 + */
115 +static void range_set_union(struct range_set *out,
116 + struct range_set *a, struct range_set *b)
117 +{
118 + int i = 0, j = 0, o = 0;
119 + struct range *ra = a->ranges;
120 + struct range *rb = b->ranges;
121 + /* cannot make an alias of out->ranges: it may change during grow */
122 +
123 + assert(out->nr == 0);
124 + while (i < a->nr || j < b->nr) {
125 + struct range *new;
126 + if (i < a->nr && j < b->nr) {
127 + if (ra[i].start < rb[j].start)
128 + new = &ra[i++];
129 + else if (ra[i].start > rb[j].start)
130 + new = &rb[j++];
131 + else if (ra[i].end < rb[j].end)
132 + new = &ra[i++];
133 + else
134 + new = &rb[j++];
135 + } else if (i < a->nr) /* b exhausted */
136 + new = &ra[i++];
137 + else /* a exhausted */
138 + new = &rb[j++];
139 + if (new->start == new->end)
140 + ; /* empty range */
141 + else if (!o || out->ranges[o-1].end < new->start) {
142 + range_set_grow(out, 1);
143 + out->ranges[o].start = new->start;
144 + out->ranges[o].end = new->end;
145 + o++;
146 + } else if (out->ranges[o-1].end < new->end) {
147 + out->ranges[o-1].end = new->end;
148 + }
149 + }
150 + out->nr = o;
151 +}
152 +
153 +/*
154 + * Difference of range sets (out = a \ b). Pass the "interesting"
155 + * ranges as 'a' and the target side of the diff as 'b': it removes
156 + * the ranges for which the commit is responsible.
157 + */
158 +static void range_set_difference(struct range_set *out,
159 + struct range_set *a, struct range_set *b)
160 +{
161 + int i, j = 0;
162 + for (i = 0; i < a->nr; i++) {
163 + long start = a->ranges[i].start;
164 + long end = a->ranges[i].end;
165 + while (start < end) {
166 + while (j < b->nr && start >= b->ranges[j].end)
167 + /*
168 + * a: |-------
169 + * b: ------|
170 + */
171 + j++;
172 + if (j >= b->nr || end < b->ranges[j].start) {
173 + /*
174 + * b exhausted, or
175 + * a: ----|
176 + * b: |----
177 + */
178 + range_set_append(out, start, end);
179 + break;
180 + }
181 + if (start >= b->ranges[j].start) {
182 + /*
183 + * a: |--????
184 + * b: |------|
185 + */
186 + start = b->ranges[j].end;
187 + } else if (end > b->ranges[j].start) {
188 + /*
189 + * a: |-----|
190 + * b: |--?????
191 + */
192 + if (start < b->ranges[j].start)
193 + range_set_append(out, start, b->ranges[j].start);
194 + start = b->ranges[j].end;
195 + }
196 + }
197 + }
198 +}
199 +
200 +static void diff_ranges_init(struct diff_ranges *diff)
201 +{
202 + range_set_init(&diff->parent, 0);
203 + range_set_init(&diff->target, 0);
204 +}
205 +
206 +static void diff_ranges_release(struct diff_ranges *diff)
207 +{
208 + range_set_release(&diff->parent);
209 + range_set_release(&diff->target);
210 +}
211 +
212 +void line_log_data_init(struct line_log_data *r)
213 +{
214 + memset(r, 0, sizeof(struct line_log_data));
215 + range_set_init(&r->ranges, 0);
216 +}
217 +
218 +static void line_log_data_clear(struct line_log_data *r)
219 +{
220 + range_set_release(&r->ranges);
221 + if (r->pair)
222 + diff_free_filepair(r->pair);
223 +}
224 +
225 +static void free_line_log_data(struct line_log_data *r)
226 +{
227 + while (r) {
228 + struct line_log_data *next = r->next;
229 + line_log_data_clear(r);
230 + free(r);
231 + r = next;
232 + }
233 +}
234 +
235 +static struct line_log_data *
236 +search_line_log_data(struct line_log_data *list, const char *path,
237 + struct line_log_data **insertion_point)
238 +{
239 + struct line_log_data *p = list;
240 + if (insertion_point)
241 + *insertion_point = NULL;
242 + while (p) {
243 + int cmp = strcmp(p->spec->path, path);
244 + if (!cmp)
245 + return p;
246 + if (insertion_point && cmp < 0)
247 + *insertion_point = p;
248 + p = p->next;
249 + }
250 + return NULL;
251 +}
252 +
253 +static void line_log_data_insert(struct line_log_data **list,
254 + struct diff_filespec *spec,
255 + long begin, long end)
256 +{
257 + struct line_log_data *ip;
258 + struct line_log_data *p = search_line_log_data(*list, spec->path, &ip);
259 +
260 + if (p) {
261 + range_set_append(&p->ranges, begin, end);
262 + sort_and_merge_range_set(&p->ranges);
263 + free_filespec(spec);
264 + return;
265 + }
266 +
267 + p = xcalloc(1, sizeof(struct line_log_data));
268 + p->spec = spec;
269 + range_set_append(&p->ranges, begin, end);
270 + if (ip) {
271 + p->next = ip->next;
272 + ip->next = p;
273 + } else {
274 + p->next = *list;
275 + *list = p;
276 + }
277 +}
278 +
279 +struct collect_diff_cbdata {
280 + struct diff_ranges *diff;
281 +};
282 +
283 +static int collect_diff_cb(long start_a, long count_a,
284 + long start_b, long count_b,
285 + void *data)
286 +{
287 + struct collect_diff_cbdata *d = data;
288 +
289 + if (count_a >= 0)
290 + range_set_append(&d->diff->parent, start_a, start_a + count_a);
291 + if (count_b >= 0)
292 + range_set_append(&d->diff->target, start_b, start_b + count_b);
293 +
294 + return 0;
295 +}
296 +
297 +static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
298 +{
299 + struct collect_diff_cbdata cbdata = {NULL};
300 + xpparam_t xpp;
301 + xdemitconf_t xecfg;
302 + xdemitcb_t ecb;
303 +
304 + memset(&xpp, 0, sizeof(xpp));
305 + memset(&xecfg, 0, sizeof(xecfg));
306 + xecfg.ctxlen = xecfg.interhunkctxlen = 0;
307 +
308 + cbdata.diff = out;
309 + xecfg.hunk_func = collect_diff_cb;
310 + memset(&ecb, 0, sizeof(ecb));
311 + ecb.priv = &cbdata;
312 + xdi_diff(parent, target, &xpp, &xecfg, &ecb);
313 +}
314 +
315 +/*
316 + * These are handy for debugging. Removing them with #if 0 silences
317 + * the "unused function" warning.
318 + */
319 +#if 0
320 +static void dump_range_set(struct range_set *rs, const char *desc)
321 +{
322 + int i;
323 + printf("range set %s (%d items):\n", desc, rs->nr);
324 + for (i = 0; i < rs->nr; i++)
325 + printf("\t[%ld,%ld]\n", rs->ranges[i].start, rs->ranges[i].end);
326 +}
327 +
328 +static void dump_line_log_data(struct line_log_data *r)
329 +{
330 + char buf[4096];
331 + while (r) {
332 + snprintf(buf, 4096, "file %s\n", r->spec->path);
333 + dump_range_set(&r->ranges, buf);
334 + r = r->next;
335 + }
336 +}
337 +
338 +static void dump_diff_ranges(struct diff_ranges *diff, const char *desc)
339 +{
340 + int i;
341 + assert(diff->parent.nr == diff->target.nr);
342 + printf("diff ranges %s (%d items):\n", desc, diff->parent.nr);
343 + printf("\tparent\ttarget\n");
344 + for (i = 0; i < diff->parent.nr; i++) {
345 + printf("\t[%ld,%ld]\t[%ld,%ld]\n",
346 + diff->parent.ranges[i].start,
347 + diff->parent.ranges[i].end,
348 + diff->target.ranges[i].start,
349 + diff->target.ranges[i].end);
350 + }
351 +}
352 +#endif
353 +
354 +
355 +static int ranges_overlap(struct range *a, struct range *b)
356 +{
357 + return !(a->end <= b->start || b->end <= a->start);
358 +}
359 +
360 +/*
361 + * Given a diff and the set of interesting ranges, determine all hunks
362 + * of the diff which touch (overlap) at least one of the interesting
363 + * ranges in the target.
364 + */
365 +static void diff_ranges_filter_touched(struct diff_ranges *out,
366 + struct diff_ranges *diff,
367 + struct range_set *rs)
368 +{
369 + int i, j = 0;
370 +
371 + assert(out->target.nr == 0);
372 +
373 + for (i = 0; i < diff->target.nr; i++) {
374 + while (diff->target.ranges[i].start > rs->ranges[j].end) {
375 + j++;
376 + if (j == rs->nr)
377 + return;
378 + }
379 + if (ranges_overlap(&diff->target.ranges[i], &rs->ranges[j])) {
380 + range_set_append(&out->parent,
381 + diff->parent.ranges[i].start,
382 + diff->parent.ranges[i].end);
383 + range_set_append(&out->target,
384 + diff->target.ranges[i].start,
385 + diff->target.ranges[i].end);
386 + }
387 + }
388 +}
389 +
390 +/*
391 + * Adjust the line counts in 'rs' to account for the lines
392 + * added/removed in the diff.
393 + */
394 +static void range_set_shift_diff(struct range_set *out,
395 + struct range_set *rs,
396 + struct diff_ranges *diff)
397 +{
398 + int i, j = 0;
399 + long offset = 0;
400 + struct range *src = rs->ranges;
401 + struct range *target = diff->target.ranges;
402 + struct range *parent = diff->parent.ranges;
403 +
404 + for (i = 0; i < rs->nr; i++) {
405 + while (j < diff->target.nr && src[i].start >= target[j].start) {
406 + offset += (parent[j].end-parent[j].start)
407 + - (target[j].end-target[j].start);
408 + j++;
409 + }
410 + range_set_append(out, src[i].start+offset, src[i].end+offset);
411 + }
412 +}
413 +
414 +/*
415 + * Given a diff and the set of interesting ranges, map the ranges
416 + * across the diff. That is: observe that the target commit takes
417 + * blame for all the + (target-side) ranges. So for every pair of
418 + * ranges in the diff that was touched, we remove the latter and add
419 + * its parent side.
420 + */
421 +static void range_set_map_across_diff(struct range_set *out,
422 + struct range_set *rs,
423 + struct diff_ranges *diff,
424 + struct diff_ranges **touched_out)
425 +{
426 + struct diff_ranges *touched = xmalloc(sizeof(*touched));
427 + struct range_set tmp1 = RANGE_SET_INIT;
428 + struct range_set tmp2 = RANGE_SET_INIT;
429 +
430 + diff_ranges_init(touched);
431 + diff_ranges_filter_touched(touched, diff, rs);
432 + range_set_difference(&tmp1, rs, &touched->target);
433 + range_set_shift_diff(&tmp2, &tmp1, diff);
434 + range_set_union(out, &tmp2, &touched->parent);
435 + range_set_release(&tmp1);
436 + range_set_release(&tmp2);
437 +
438 + *touched_out = touched;
439 +}
440 +
441 +
442 +static struct commit *check_single_commit(struct rev_info *revs)
443 +{
444 + struct object *commit = NULL;
445 + int found = -1;
446 + int i;
447 +
448 + for (i = 0; i < revs->pending.nr; i++) {
449 + struct object *obj = revs->pending.objects[i].item;
450 + if (obj->flags & UNINTERESTING)
451 + continue;
452 + while (obj->type == OBJ_TAG)
453 + obj = deref_tag(obj, NULL, 0);
454 + if (obj->type != OBJ_COMMIT)
455 + die("Non commit %s?", revs->pending.objects[i].name);
456 + if (commit)
457 + die("More than one commit to dig from: %s and %s?",
458 + revs->pending.objects[i].name,
459 + revs->pending.objects[found].name);
460 + commit = obj;
461 + found = i;
462 + }
463 +
464 + if (!commit)
465 + die("No commit specified?");
466 +
467 + return (struct commit *) commit;
468 +}
469 +
470 +static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec)
471 +{
472 + unsigned mode;
473 + unsigned char sha1[20];
474 +
475 + if (get_tree_entry(commit->object.sha1, spec->path,
476 + sha1, &mode))
477 + die("There is no path %s in the commit", spec->path);
478 + fill_filespec(spec, sha1, 1, mode);
479 +
480 + return;
481 +}
482 +
483 +static void fill_line_ends(struct diff_filespec *spec, long *lines,
484 + unsigned long **line_ends)
485 +{
486 + int num = 0, size = 50;
487 + long cur = 0;
488 + unsigned long *ends = NULL;
489 + char *data = NULL;
490 +
491 + if (diff_populate_filespec(spec, 0))
492 + die("Cannot read blob %s", sha1_to_hex(spec->sha1));
493 +
494 + ends = xmalloc(size * sizeof(*ends));
495 + ends[cur++] = 0;
496 + data = spec->data;
497 + while (num < spec->size) {
498 + if (data[num] == '\n' || num == spec->size - 1) {
499 + ALLOC_GROW(ends, (cur + 1), size);
500 + ends[cur++] = num;
501 + }
502 + num++;
503 + }
504 +
505 + /* shrink the array to fit the elements */
506 + ends = xrealloc(ends, cur * sizeof(*ends));
507 + *lines = cur-1;
508 + *line_ends = ends;
509 +}
510 +
511 +struct nth_line_cb {
512 + struct diff_filespec *spec;
513 + long lines;
514 + unsigned long *line_ends;
515 +};
516 +
517 +static const char *nth_line(void *data, long line)
518 +{
519 + struct nth_line_cb *d = data;
520 + assert(d && line <= d->lines);
521 + assert(d->spec && d->spec->data);
522 +
523 + if (line == 0)
524 + return (char *)d->spec->data;
525 + else
526 + return (char *)d->spec->data + d->line_ends[line] + 1;
527 +}
528 +
529 +static struct line_log_data *
530 +parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
531 +{
532 + long lines = 0;
533 + unsigned long *ends = NULL;
534 + struct nth_line_cb cb_data;
535 + struct string_list_item *item;
536 + struct line_log_data *ranges = NULL;
537 +
538 + for_each_string_list_item(item, args) {
539 + const char *name_part, *range_part;
540 + const char *full_name;
541 + struct diff_filespec *spec;
542 + long begin = 0, end = 0;
543 +
544 + name_part = skip_range_arg(item->string);
545 + if (!name_part || *name_part != ':' || !name_part[1])
546 + die("-L argument '%s' not of the form start,end:file",
547 + item->string);
548 + range_part = xstrndup(item->string, name_part - item->string);
549 + name_part++;
550 +
551 + full_name = prefix_path(prefix, prefix ? strlen(prefix) : 0,
552 + name_part);
553 +
554 + spec = alloc_filespec(full_name);
555 + fill_blob_sha1(commit, spec);
556 + fill_line_ends(spec, &lines, &ends);
557 + cb_data.spec = spec;
558 + cb_data.lines = lines;
559 + cb_data.line_ends = ends;
560 +
561 + if (parse_range_arg(range_part, nth_line, &cb_data,
562 + lines, &begin, &end))
563 + die("malformed -L argument '%s'", range_part);
564 + if (begin < 1)
565 + begin = 1;
566 + if (end < 1)
567 + end = lines;
568 + begin--;
569 + if (lines < end || lines < begin)
570 + die("file %s has only %ld lines", name_part, lines);
571 + line_log_data_insert(&ranges, spec, begin, end);
572 +
573 + free(ends);
574 + ends = NULL;
575 + }
576 +
577 + return ranges;
578 +}
579 +
580 +static struct line_log_data *line_log_data_copy_one(struct line_log_data *r)
581 +{
582 + struct line_log_data *ret = xmalloc(sizeof(*ret));
583 +
584 + assert(r);
585 + line_log_data_init(ret);
586 + range_set_copy(&ret->ranges, &r->ranges);
587 +
588 + ret->spec = r->spec;
589 + assert(ret->spec);
590 + ret->spec->count++;
591 +
592 + return ret;
593 +}
594 +
595 +static struct line_log_data *
596 +line_log_data_copy(struct line_log_data *r)
597 +{
598 + struct line_log_data *ret = NULL;
599 + struct line_log_data *tmp = NULL, *prev = NULL;
600 +
601 + assert(r);
602 + ret = tmp = prev = line_log_data_copy_one(r);
603 + r = r->next;
604 + while (r) {
605 + tmp = line_log_data_copy_one(r);
606 + prev->next = tmp;
607 + prev = tmp;
608 + r = r->next;
609 + }
610 +
611 + return ret;
612 +}
613 +
614 +/* merge two range sets across files */
615 +static struct line_log_data *line_log_data_merge(struct line_log_data *a,
616 + struct line_log_data *b)
617 +{
618 + struct line_log_data *head = NULL, **pp = &head;
619 +
620 + while (a || b) {
621 + struct line_log_data *src;
622 + struct line_log_data *src2 = NULL;
623 + struct line_log_data *d;
624 + int cmp;
625 + if (!a)
626 + cmp = 1;
627 + else if (!b)
628 + cmp = -1;
629 + else
630 + cmp = strcmp(a->spec->path, b->spec->path);
631 + if (cmp < 0) {
632 + src = a;
633 + a = a->next;
634 + } else if (cmp == 0) {
635 + src = a;
636 + a = a->next;
637 + src2 = b;
638 + b = b->next;
639 + } else {
640 + src = b;
641 + b = b->next;
642 + }
643 + d = xmalloc(sizeof(struct line_log_data));
644 + line_log_data_init(d);
645 + d->spec = src->spec;
646 + d->spec->count++;
647 + *pp = d;
648 + pp = &d->next;
649 + if (src2)
650 + range_set_union(&d->ranges, &src->ranges, &src2->ranges);
651 + else
652 + range_set_copy(&d->ranges, &src->ranges);
653 + }
654 +
655 + return head;
656 +}
657 +
658 +static void add_line_range(struct rev_info *revs, struct commit *commit,
659 + struct line_log_data *range)
660 +{
661 + struct line_log_data *old = NULL;
662 + struct line_log_data *new = NULL;
663 +
664 + old = lookup_decoration(&revs->line_log_data, &commit->object);
665 + if (old && range) {
666 + new = line_log_data_merge(old, range);
667 + free_line_log_data(old);
668 + } else if (range)
669 + new = line_log_data_copy(range);
670 +
671 + if (new)
672 + add_decoration(&revs->line_log_data, &commit->object, new);
673 +}
674 +
675 +static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
676 +{
677 + struct line_log_data *r;
678 + r = lookup_decoration(&revs->line_log_data, &commit->object);
679 + if (!r)
680 + return;
681 + free_line_log_data(r);
682 + add_decoration(&revs->line_log_data, &commit->object, NULL);
683 +}
684 +
685 +static struct line_log_data *lookup_line_range(struct rev_info *revs,
686 + struct commit *commit)
687 +{
688 + struct line_log_data *ret = NULL;
689 +
690 + ret = lookup_decoration(&revs->line_log_data, &commit->object);
691 + return ret;
692 +}
693 +
694 +void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args)
695 +{
696 + struct commit *commit = NULL;
697 + struct line_log_data *range;
698 +
699 + commit = check_single_commit(rev);
700 + range = parse_lines(commit, prefix, args);
701 + add_line_range(rev, commit, range);
702 +
703 + if (!rev->diffopt.detect_rename) {
704 + int i, count = 0;
705 + struct line_log_data *r = range;
706 + const char **paths;
707 + while (r) {
708 + count++;
709 + r = r->next;
710 + }
711 + paths = xmalloc((count+1)*sizeof(char *));
712 + r = range;
713 + for (i = 0; i < count; i++) {
714 + paths[i] = xstrdup(r->spec->path);
715 + r = r->next;
716 + }
717 + paths[count] = NULL;
718 + init_pathspec(&rev->diffopt.pathspec, paths);
719 + free(paths);
720 + }
721 +}
722 +
723 +static void load_tree_desc(struct tree_desc *desc, void **tree,
724 + const unsigned char *sha1)
725 +{
726 + unsigned long size;
727 + *tree = read_object_with_reference(sha1, tree_type, &size, NULL);
728 + if (!*tree)
729 + die("Unable to read tree (%s)", sha1_to_hex(sha1));
730 + init_tree_desc(desc, *tree, size);
731 +}
732 +
733 +static int count_parents(struct commit *commit)
734 +{
735 + struct commit_list *parents = commit->parents;
736 + int count = 0;
737 + while (parents) {
738 + count++;
739 + parents = parents->next;
740 + }
741 + return count;
742 +}
743 +
744 +static void move_diff_queue(struct diff_queue_struct *dst,
745 + struct diff_queue_struct *src)
746 +{
747 + assert(src != dst);
748 + memcpy(dst, src, sizeof(struct diff_queue_struct));
749 + DIFF_QUEUE_CLEAR(src);
750 +}
751 +
752 +static void queue_diffs(struct diff_options *opt,
753 + struct diff_queue_struct *queue,
754 + struct commit *commit, struct commit *parent)
755 +{
756 + void *tree1 = NULL, *tree2 = NULL;
757 + struct tree_desc desc1, desc2;
758 +
759 + assert(commit);
760 + load_tree_desc(&desc2, &tree2, commit->tree->object.sha1);
761 + if (parent)
762 + load_tree_desc(&desc1, &tree1, parent->tree->object.sha1);
763 + else
764 + init_tree_desc(&desc1, "", 0);
765 +
766 + DIFF_QUEUE_CLEAR(&diff_queued_diff);
767 + diff_tree(&desc1, &desc2, "", opt);
768 + diffcore_std(opt);
769 + move_diff_queue(queue, &diff_queued_diff);
770 +
771 + if (tree1)
772 + free(tree1);
773 + if (tree2)
774 + free(tree2);
775 +}
776 +
777 +static char *get_nth_line(long line, unsigned long *ends, void *data)
778 +{
779 + if (line == 0)
780 + return (char *)data;
781 + else
782 + return (char *)data + ends[line] + 1;
783 +}
784 +
785 +static void print_line(const char *prefix, char first,
786 + long line, unsigned long *ends, void *data,
787 + const char *color, const char *reset)
788 +{
789 + char *begin = get_nth_line(line, ends, data);
790 + char *end = get_nth_line(line+1, ends, data);
791 + int had_nl = 0;
792 +
793 + if (end > begin && end[-1] == '\n') {
794 + end--;
795 + had_nl = 1;
796 + }
797 +
798 + fputs(prefix, stdout);
799 + fputs(color, stdout);
800 + putchar(first);
801 + fwrite(begin, 1, end-begin, stdout);
802 + fputs(reset, stdout);
803 + putchar('\n');
804 + if (!had_nl)
805 + fputs("\\ No newline at end of file\n", stdout);
806 +}
807 +
808 +static char *output_prefix(struct diff_options *opt)
809 +{
810 + char *prefix = "";
811 +
812 + if (opt->output_prefix) {
813 + struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
814 + prefix = sb->buf;
815 + }
816 +
817 + return prefix;
818 +}
819 +
820 +static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
821 +{
822 + int i, j = 0;
823 + long p_lines, t_lines;
824 + unsigned long *p_ends = NULL, *t_ends = NULL;
825 + struct diff_filepair *pair = range->pair;
826 + struct diff_ranges *diff = &range->diff;
827 +
828 + struct diff_options *opt = &rev->diffopt;
829 + char *prefix = output_prefix(opt);
830 + const char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);
831 + const char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);
832 + const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);
833 + const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);
834 + const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);
835 + const char *c_plain = diff_get_color(opt->use_color, DIFF_PLAIN);
836 +
837 + if (!pair || !diff)
838 + return;
839 +
840 + if (pair->one->sha1_valid)
841 + fill_line_ends(pair->one, &p_lines, &p_ends);
842 + fill_line_ends(pair->two, &t_lines, &t_ends);
843 +
844 + printf("%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);
845 + printf("%s%s--- %s%s%s\n", prefix, c_meta,
846 + pair->one->sha1_valid ? "a/" : "",
847 + pair->one->sha1_valid ? pair->one->path : "/dev/null",
848 + c_reset);
849 + printf("%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset);
850 + for (i = 0; i < range->ranges.nr; i++) {
851 + long p_start, p_end;
852 + long t_start = range->ranges.ranges[i].start;
853 + long t_end = range->ranges.ranges[i].end;
854 + long t_cur = t_start;
855 + int j_last;
856 +
857 + while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
858 + j++;
859 + if (j == diff->target.nr || diff->target.ranges[j].start > t_end)
860 + continue;
861 +
862 + /* Scan ahead to determine the last diff that falls in this range */
863 + j_last = j;
864 + while (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)
865 + j_last++;
866 + if (j_last > j)
867 + j_last--;
868 +
869 + /*
870 + * Compute parent hunk headers: we know that the diff
871 + * has the correct line numbers (but not all hunks).
872 + * So it suffices to shift the start/end according to
873 + * the line numbers of the first/last hunk(s) that
874 + * fall in this range.
875 + */
876 + if (t_start < diff->target.ranges[j].start)
877 + p_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);
878 + else
879 + p_start = diff->parent.ranges[j].start;
880 + if (t_end > diff->target.ranges[j_last].end)
881 + p_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);
882 + else
883 + p_end = diff->parent.ranges[j_last].end;
884 +
885 + if (!p_start && !p_end) {
886 + p_start = -1;
887 + p_end = -1;
888 + }
889 +
890 + /* Now output a diff hunk for this range */
891 + printf("%s%s@@ -%ld,%ld +%ld,%ld @@%s\n",
892 + prefix, c_frag,
893 + p_start+1, p_end-p_start, t_start+1, t_end-t_start,
894 + c_reset);
895 + while (j < diff->target.nr && diff->target.ranges[j].start < t_end) {
896 + int k;
897 + for (; t_cur < diff->target.ranges[j].start; t_cur++)
898 + print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
899 + c_plain, c_reset);
900 + for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)
901 + print_line(prefix, '-', k, p_ends, pair->one->data,
902 + c_old, c_reset);
903 + for (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++)
904 + print_line(prefix, '+', t_cur, t_ends, pair->two->data,
905 + c_new, c_reset);
906 + j++;
907 + }
908 + for (; t_cur < t_end; t_cur++)
909 + print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
910 + c_plain, c_reset);
911 + }
912 +
913 + free(p_ends);
914 + free(t_ends);
915 +}
916 +
917 +/*
918 + * NEEDSWORK: manually building a diff here is not the Right
919 + * Thing(tm). log -L should be built into the diff pipeline.
920 + */
921 +static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
922 +{
923 + puts(output_prefix(&rev->diffopt));
924 + while (range) {
925 + dump_diff_hacky_one(rev, range);
926 + range = range->next;
927 + }
928 +}
929 +
930 +/*
931 + * Unlike most other functions, this destructively operates on
932 + * 'range'.
933 + */
934 +static int process_diff_filepair(struct rev_info *rev,
935 + struct diff_filepair *pair,
936 + struct line_log_data *range,
937 + struct diff_ranges **diff_out)
938 +{
939 + struct line_log_data *rg = range;
940 + struct range_set tmp;
941 + struct diff_ranges diff;
942 + mmfile_t file_parent, file_target;
943 +
944 + assert(pair->two->path);
945 + while (rg) {
946 + assert(rg->spec->path);
947 + if (!strcmp(rg->spec->path, pair->two->path))
948 + break;
949 + rg = rg->next;
950 + }
951 +
952 + if (!rg)
953 + return 0;
954 + if (rg->ranges.nr == 0)
955 + return 0;
956 +
957 + assert(pair->two->sha1_valid);
958 + diff_populate_filespec(pair->two, 0);
959 + file_target.ptr = pair->two->data;
960 + file_target.size = pair->two->size;
961 +
962 + if (pair->one->sha1_valid) {
963 + diff_populate_filespec(pair->one, 0);
964 + file_parent.ptr = pair->one->data;
965 + file_parent.size = pair->one->size;
966 + } else {
967 + file_parent.ptr = "";
968 + file_parent.size = 0;
969 + }
970 +
971 + diff_ranges_init(&diff);
972 + collect_diff(&file_parent, &file_target, &diff);
973 +
974 + /* NEEDSWORK should apply some heuristics to prevent mismatches */
975 + rg->spec->path = xstrdup(pair->one->path);
976 +
977 + range_set_init(&tmp, 0);
978 + range_set_map_across_diff(&tmp, &rg->ranges, &diff, diff_out);
979 + range_set_release(&rg->ranges);
980 + range_set_move(&rg->ranges, &tmp);
981 +
982 + diff_ranges_release(&diff);
983 +
984 + return ((*diff_out)->parent.nr > 0);
985 +}
986 +
987 +static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)
988 +{
989 + struct diff_filepair *new = xmalloc(sizeof(struct diff_filepair));
990 + new->one = pair->one;
991 + new->two = pair->two;
992 + new->one->count++;
993 + new->two->count++;
994 + return new;
995 +}
996 +
997 +static void free_diffqueues(int n, struct diff_queue_struct *dq)
998 +{
999 + int i, j;
1000 + for (i = 0; i < n; i++)
1001 + for (j = 0; j < dq[i].nr; j++)
1002 + diff_free_filepair(dq[i].queue[j]);
1003 + free(dq);
1004 +}
1005 +
1006 +static int process_all_files(struct line_log_data **range_out,
1007 + struct rev_info *rev,
1008 + struct diff_queue_struct *queue,
1009 + struct line_log_data *range)
1010 +{
1011 + int i, changed = 0;
1012 +
1013 + *range_out = line_log_data_copy(range);
1014 +
1015 + for (i = 0; i < queue->nr; i++) {
1016 + struct diff_ranges *pairdiff = NULL;
1017 + if (process_diff_filepair(rev, queue->queue[i], *range_out, &pairdiff)) {
1018 + struct line_log_data *rg = range;
1019 + changed++;
1020 + /* NEEDSWORK tramples over data structures not owned here */
1021 + while (rg && strcmp(rg->spec->path, queue->queue[i]->two->path))
1022 + rg = rg->next;
1023 + assert(rg);
1024 + rg->pair = diff_filepair_dup(queue->queue[i]);
1025 + memcpy(&rg->diff, pairdiff, sizeof(struct diff_ranges));
1026 + }
1027 + }
1028 +
1029 + return changed;
1030 +}
1031 +
1032 +int line_log_print(struct rev_info *rev, struct commit *commit)
1033 +{
1034 + struct line_log_data *range = lookup_line_range(rev, commit);
1035 +
1036 + show_log(rev);
1037 + dump_diff_hacky(rev, range);
1038 + return 1;
1039 +}
1040 +
1041 +static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit,
1042 + struct line_log_data *range)
1043 +{
1044 + struct commit *parent = NULL;
1045 + struct diff_queue_struct queue;
1046 + struct line_log_data *parent_range;
1047 + int changed;
1048 +
1049 + if (commit->parents)
1050 + parent = commit->parents->item;
1051 +
1052 + queue_diffs(&rev->diffopt, &queue, commit, parent);
1053 + changed = process_all_files(&parent_range, rev, &queue, range);
1054 + if (parent)
1055 + add_line_range(rev, parent, parent_range);
1056 + return changed;
1057 +}
1058 +
1059 +static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit,
1060 + struct line_log_data *range)
1061 +{
1062 + struct diff_queue_struct *diffqueues;
1063 + struct line_log_data **cand;
1064 + struct commit **parents;
1065 + struct commit_list *p;
1066 + int i;
1067 + int nparents = count_parents(commit);
1068 +
1069 + diffqueues = xmalloc(nparents * sizeof(*diffqueues));
1070 + cand = xmalloc(nparents * sizeof(*cand));
1071 + parents = xmalloc(nparents * sizeof(*parents));
1072 +
1073 + p = commit->parents;
1074 + for (i = 0; i < nparents; i++) {
1075 + parents[i] = p->item;
1076 + p = p->next;
1077 + queue_diffs(&rev->diffopt, &diffqueues[i], commit, parents[i]);
1078 + }
1079 +
1080 + for (i = 0; i < nparents; i++) {
1081 + int changed;
1082 + cand[i] = NULL;
1083 + changed = process_all_files(&cand[i], rev, &diffqueues[i], range);
1084 + if (!changed) {
1085 + /*
1086 + * This parent can take all the blame, so we
1087 + * don't follow any other path in history
1088 + */
1089 + add_line_range(rev, parents[i], cand[i]);
1090 + clear_commit_line_range(rev, commit);
1091 + commit->parents = xmalloc(sizeof(struct commit_list));
1092 + commit->parents->item = parents[i];
1093 + commit->parents->next = NULL;
1094 + free(parents);
1095 + free(cand);
1096 + free_diffqueues(nparents, diffqueues);
1097 + /* NEEDSWORK leaking like a sieve */
1098 + return 0;
1099 + }
1100 + }
1101 +
1102 + /*
1103 + * No single parent took the blame. We add the candidates
1104 + * from the above loop to the parents.
1105 + */
1106 + for (i = 0; i < nparents; i++) {
1107 + add_line_range(rev, parents[i], cand[i]);
1108 + }
1109 +
1110 + clear_commit_line_range(rev, commit);
1111 + free(parents);
1112 + free(cand);
1113 + free_diffqueues(nparents, diffqueues);
1114 + return 1;
1115 +
1116 + /* NEEDSWORK evil merge detection stuff */
1117 + /* NEEDSWORK leaking like a sieve */
1118 +}
1119 +
1120 +static int process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)
1121 +{
1122 + struct line_log_data *range = lookup_line_range(rev, commit);
1123 + int changed = 0;
1124 +
1125 + if (range) {
1126 + if (!commit->parents || !commit->parents->next)
1127 + changed = process_ranges_ordinary_commit(rev, commit, range);
1128 + else
1129 + changed = process_ranges_merge_commit(rev, commit, range);
1130 + }
1131 +
1132 + if (!changed)
1133 + commit->object.flags |= TREESAME;
1134 +
1135 + return changed;
1136 +}
1137 +
1138 +static enum rewrite_result line_log_rewrite_one(struct rev_info *rev, struct commit **pp)
1139 +{
1140 + for (;;) {
1141 + struct commit *p = *pp;
1142 + if (p->parents && p->parents->next)
1143 + return rewrite_one_ok;
1144 + if (p->object.flags & UNINTERESTING)
1145 + return rewrite_one_ok;
1146 + if (!(p->object.flags & TREESAME))
1147 + return rewrite_one_ok;
1148 + if (!p->parents)
1149 + return rewrite_one_noparents;
1150 + *pp = p->parents->item;
1151 + }
1152 +}
1153 +
1154 +int line_log_filter(struct rev_info *rev)
1155 +{
1156 + struct commit *commit;
1157 + struct commit_list *list = rev->commits;
1158 + struct commit_list *out = NULL, **pp = &out;
1159 +
1160 + while (list) {
1161 + struct commit_list *to_free = NULL;
1162 + commit = list->item;
1163 + if (process_ranges_arbitrary_commit(rev, commit)) {
1164 + *pp = list;
1165 + pp = &list->next;
1166 + } else
1167 + to_free = list;
1168 + list = list->next;
1169 + free(to_free);
1170 + }
1171 + *pp = NULL;
1172 +
1173 + for (list = out; list; list = list->next)
1174 + rewrite_parents(rev, list->item, line_log_rewrite_one);
1175 +
1176 + rev->commits = out;
1177 +
1178 + return 0;
1179 +}
line-log.h new
+49
@@ -0,0 +1,49 @@
1 +#ifndef LINE_LOG_H
2 +#define LINE_LOG_H
3 +
4 +#include "diffcore.h"
5 +
6 +struct rev_info;
7 +struct commit;
8 +
9 +/* A range [start,end]. Lines are numbered starting at 0, and the
10 + * ranges include start but exclude end. */
11 +struct range {
12 + long start, end;
13 +};
14 +
15 +/* A set of ranges. The ranges must always be disjoint and sorted. */
16 +struct range_set {
17 + int alloc, nr;
18 + struct range *ranges;
19 +};
20 +
21 +/* A diff, encoded as the set of pre- and post-image ranges where the
22 + * files differ. A pair of ranges corresponds to a hunk. */
23 +struct diff_ranges {
24 + struct range_set parent;
25 + struct range_set target;
26 +};
27 +
28 +/* Linked list of interesting files and their associated ranges. The
29 + * list must be kept sorted by spec->path */
30 +struct line_log_data {
31 + struct line_log_data *next;
32 + struct diff_filespec *spec;
33 + char status;
34 + struct range_set ranges;
35 + int arg_alloc, arg_nr;
36 + const char **args;
37 + struct diff_filepair *pair;
38 + struct diff_ranges diff;
39 +};
40 +
41 +extern void line_log_data_init(struct line_log_data *r);
42 +
43 +extern void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args);
44 +
45 +extern int line_log_filter(struct rev_info *rev);
46 +
47 +extern int line_log_print(struct rev_info *rev, struct commit *commit);
48 +
49 +#endif /* LINE_LOG_H */
line-range.c
+18 -1
@@ -21,6 +21,8 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
21 if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
22 num = strtol(spec + 1, &term, 10);
23 if (term != spec + 1) {
24 + if (!ret)
25 + return term;
26 if (spec[0] == '-')
27 num = 0 - num;
28 if (0 < num)
@@ -35,7 +37,8 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
37 }
38 num = strtol(spec, &term, 10);
39 if (term != spec) {
38 - *ret = num;
40 + if (ret)
41 + *ret = num;
42 return term;
43 }
44 if (spec[0] != '/')
@@ -49,6 +52,10 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
52 if (*term != '/')
53 return spec;
54
55 + /* in the scan-only case we are not interested in the regex */
56 + if (!ret)
57 + return term+1;
58 +
59 /* try [spec+1 .. term-1] as regexp */
60 *term = 0;
61 begin--; /* input is in human terms */
@@ -90,3 +97,13 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
97
98 return 0;
99 }
100 +
101 +const char *skip_range_arg(const char *arg)
102 +{
103 + arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
104 +
105 + if (*arg == ',')
106 + arg = parse_loc(arg+1, NULL, NULL, 0, 0, NULL);
107 +
108 + return arg;
109 +}
line-range.h
+11
@@ -21,4 +21,15 @@ extern int parse_range_arg(const char *arg,
21 void *cb_data, long lines,
22 long *begin, long *end);
23
24 +/*
25 + * Scan past a range argument that could be parsed by
26 + * 'parse_range_arg', to help the caller determine the start of the
27 + * filename in '-L n,m:file' syntax.
28 + *
29 + * Returns a pointer to the first character after the 'n,m' part, or
30 + * NULL in case the argument is obviously malformed.
31 + */
32 +
33 +extern const char *skip_range_arg(const char *arg);
34 +
35 #endif /* LINE_RANGE_H */
log-tree.c
+4
@@ -9,6 +9,7 @@
9 #include "string-list.h"
10 #include "color.h"
11 #include "gpg-interface.h"
12 +#include "line-log.h"
13
14 struct decoration name_decoration = { "object names" };
15
@@ -856,6 +857,9 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
857 log.parent = NULL;
858 opt->loginfo = &log;
859
860 + if (opt->line_level_traverse)
861 + return line_log_print(opt, commit);
862 +
863 shown = log_tree_diff(opt, commit, &log);
864 if (!shown && opt->loginfo && opt->always_show_header) {
865 log.parent = NULL;
revision.c
+9
@@ -13,6 +13,7 @@
13 #include "decorate.h"
14 #include "log-tree.h"
15 #include "string-list.h"
16 +#include "line-log.h"
17 #include "mailmap.h"
18
19 volatile show_early_output_fn_t show_early_output;
@@ -1896,6 +1897,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
1897 if (revs->combine_merges)
1898 revs->ignore_merges = 0;
1899 revs->diffopt.abbrev = revs->abbrev;
1900 +
1901 + if (revs->line_level_traverse) {
1902 + revs->limited = 1;
1903 + revs->topo_order = 1;
1904 + }
1905 +
1906 diff_setup_done(&revs->diffopt);
1907
1908 grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
@@ -2166,6 +2173,8 @@ int prepare_revision_walk(struct rev_info *revs)
2173 return -1;
2174 if (revs->topo_order)
2175 sort_in_topological_order(&revs->commits, revs->lifo);
2176 + if (revs->line_level_traverse)
2177 + line_log_filter(revs);
2178 if (revs->simplify_merges)
2179 simplify_merges(revs);
2180 if (revs->children.name)
revision.h
+5 -1
@@ -96,7 +96,8 @@ struct rev_info {
96 cherry_mark:1,
97 bisect:1,
98 ancestry_path:1,
99 - first_parent_only:1;
99 + first_parent_only:1,
100 + line_level_traverse:1;
101
102 /* Diff flags */
103 unsigned int diff:1,
@@ -175,6 +176,9 @@ struct rev_info {
176 int count_left;
177 int count_right;
178 int count_same;
179 +
180 + /* line level range that we are chasing */
181 + struct decoration line_log_data;
182 };
183
184 #define REV_TREE_SAME 0
t/perf/p4211-line-log.sh new
+34
@@ -0,0 +1,34 @@
1 +#!/bin/sh
2 +
3 +test_description='Tests log -L performance'
4 +. ./perf-lib.sh
5 +
6 +test_perf_default_repo
7 +
8 +# Pick a file to log pseudo-randomly. The sort key is the blob hash,
9 +# so it is stable.
10 +test_expect_success 'select a file' '
11 + git ls-tree HEAD | grep ^100644 |
12 + sort -k 3 | head -1 | cut -f 2 >filelist
13 +'
14 +
15 +file=$(cat filelist)
16 +export file
17 +
18 +test_perf 'git rev-list --topo-order (baseline)' '
19 + git rev-list --topo-order HEAD >/dev/null
20 +'
21 +
22 +test_perf 'git log --follow (baseline for -M)' '
23 + git log --oneline --follow -- "$file" >/dev/null
24 +'
25 +
26 +test_perf 'git log -L' '
27 + git log -L 1:"$file" >/dev/null
28 +'
29 +
30 +test_perf 'git log -M -L' '
31 + git log -M -L 1:"$file" >/dev/null
32 +'
33 +
34 +test_done
t/t4211-line-log.sh new
+49
@@ -0,0 +1,49 @@
1 +#!/bin/sh
2 +
3 +test_description='test log -L'
4 +. ./test-lib.sh
5 +
6 +test_expect_success 'setup (import history)' '
7 + git fast-import < "$TEST_DIRECTORY"/t4211/history.export &&
8 + git reset --hard
9 +'
10 +
11 +canned_test () {
12 + test_expect_success "$1" "
13 + git log $1 >actual &&
14 + test_cmp \"\$TEST_DIRECTORY\"/t4211/expect.$2 actual
15 + "
16 +}
17 +
18 +test_bad_opts () {
19 + test_expect_success "invalid args: $1" "
20 + test_must_fail git log $1 2>errors &&
21 + grep '$2' errors
22 + "
23 +}
24 +
25 +canned_test "-L 4,12:a.c simple" simple-f
26 +canned_test "-L 4,+9:a.c simple" simple-f
27 +canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
28 +
29 +canned_test "-L '/main/,/^}/:a.c' simple" simple-main
30 +
31 +canned_test "-L 1,+4:a.c simple" beginning-of-file
32 +
33 +canned_test "-L 20:a.c simple" end-of-file
34 +
35 +canned_test "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple" two-ranges
36 +canned_test "-L 24,+1:a.c simple" vanishes-early
37 +
38 +canned_test "-L '/long f/,/^}/:b.c' move-support" move-support-f
39 +
40 +test_bad_opts "-L" "switch.*requires a value"
41 +test_bad_opts "-L b.c" "argument.*not of the form"
42 +test_bad_opts "-L 1:" "argument.*not of the form"
43 +test_bad_opts "-L 1:nonexistent" "There is no path"
44 +test_bad_opts "-L 1:simple" "There is no path"
45 +test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
46 +test_bad_opts "-L 1000:b.c" "has only.*lines"
47 +test_bad_opts "-L 1,1000:b.c" "has only.*lines"
48 +
49 +test_done
t/t4211/expect.beginning-of-file new
+43
@@ -0,0 +1,43 @@
1 +commit 4a23ae5c98d59a58c6da036156959f2dc9f472ad
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:47:40 2013 +0100
4 +
5 + change at very beginning
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -1,3 +1,4 @@
11 ++#include <unistd.h>
12 + #include <stdio.h>
13 +
14 + long f(long x)
15 +
16 +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
17 +Author: Thomas Rast <trast@student.ethz.ch>
18 +Date: Thu Feb 28 10:45:16 2013 +0100
19 +
20 + touch both functions
21 +
22 +diff --git a/a.c b/a.c
23 +--- a/a.c
24 ++++ b/a.c
25 +@@ -1,3 +1,3 @@
26 + #include <stdio.h>
27 +
28 +-int f(int x)
29 ++long f(long x)
30 +
31 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
32 +Author: Thomas Rast <trast@student.ethz.ch>
33 +Date: Thu Feb 28 10:44:48 2013 +0100
34 +
35 + initial
36 +
37 +diff --git a/a.c b/a.c
38 +--- /dev/null
39 ++++ b/a.c
40 +@@ -0,0 +1,3 @@
41 ++#include <stdio.h>
42 ++
43 ++int f(int x)
t/t4211/expect.end-of-file new
+62
@@ -0,0 +1,62 @@
1 +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:48:43 2013 +0100
4 +
5 + change back to complete line
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -20,3 +20,5 @@
11 + printf("%ld\n", f(15));
12 + return 0;
13 +-}
14 +\ No newline at end of file
15 ++}
16 ++
17 ++/* incomplete lines are bad! */
18 +
19 +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
20 +Author: Thomas Rast <trast@student.ethz.ch>
21 +Date: Thu Feb 28 10:48:10 2013 +0100
22 +
23 + change to an incomplete line at end
24 +
25 +diff --git a/a.c b/a.c
26 +--- a/a.c
27 ++++ b/a.c
28 +@@ -20,3 +20,3 @@
29 + printf("%ld\n", f(15));
30 + return 0;
31 +-}
32 ++}
33 +\ No newline at end of file
34 +
35 +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
36 +Author: Thomas Rast <trast@student.ethz.ch>
37 +Date: Thu Feb 28 10:45:16 2013 +0100
38 +
39 + touch both functions
40 +
41 +diff --git a/a.c b/a.c
42 +--- a/a.c
43 ++++ b/a.c
44 +@@ -19,3 +19,3 @@
45 +- printf("%d\n", f(15));
46 ++ printf("%ld\n", f(15));
47 + return 0;
48 + }
49 +
50 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
51 +Author: Thomas Rast <trast@student.ethz.ch>
52 +Date: Thu Feb 28 10:44:48 2013 +0100
53 +
54 + initial
55 +
56 +diff --git a/a.c b/a.c
57 +--- /dev/null
58 ++++ b/a.c
59 +@@ -0,0 +18,3 @@
60 ++ printf("%d\n", f(15));
61 ++ return 0;
62 ++}
t/t4211/expect.move-support-f new
+40
@@ -0,0 +1,40 @@
1 +commit 6ce3c4ff690136099bb17e1a8766b75764726ea7
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:49:50 2013 +0100
4 +
5 + another simple change
6 +
7 +diff --git a/b.c b/b.c
8 +--- a/b.c
9 ++++ b/b.c
10 +@@ -4,9 +4,9 @@
11 + long f(long x)
12 + {
13 + int s = 0;
14 + while (x) {
15 +- x >>= 1;
16 ++ x /= 2;
17 + s++;
18 + }
19 + return s;
20 + }
21 +
22 +commit e6da343666244ea9e67cbe3f3bd26da860f9fe0e
23 +Author: Thomas Rast <trast@student.ethz.ch>
24 +Date: Thu Feb 28 10:49:28 2013 +0100
25 +
26 + move file
27 +
28 +diff --git a/b.c b/b.c
29 +--- /dev/null
30 ++++ b/b.c
31 +@@ -0,0 +4,9 @@
32 ++long f(long x)
33 ++{
34 ++ int s = 0;
35 ++ while (x) {
36 ++ x >>= 1;
37 ++ s++;
38 ++ }
39 ++ return s;
40 ++}
t/t4211/expect.simple-f new
+59
@@ -0,0 +1,59 @@
1 +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:45:16 2013 +0100
4 +
5 + touch both functions
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -3,9 +3,9 @@
11 +-int f(int x)
12 ++long f(long x)
13 + {
14 + int s = 0;
15 + while (x) {
16 + x >>= 1;
17 + s++;
18 + }
19 + return s;
20 + }
21 +
22 +commit f04fb20f2c77850996cba739709acc6faecc58f7
23 +Author: Thomas Rast <trast@student.ethz.ch>
24 +Date: Thu Feb 28 10:44:55 2013 +0100
25 +
26 + change f()
27 +
28 +diff --git a/a.c b/a.c
29 +--- a/a.c
30 ++++ b/a.c
31 +@@ -3,8 +3,9 @@
32 + int f(int x)
33 + {
34 + int s = 0;
35 + while (x) {
36 + x >>= 1;
37 + s++;
38 + }
39 ++ return s;
40 + }
41 +
42 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
43 +Author: Thomas Rast <trast@student.ethz.ch>
44 +Date: Thu Feb 28 10:44:48 2013 +0100
45 +
46 + initial
47 +
48 +diff --git a/a.c b/a.c
49 +--- /dev/null
50 ++++ b/a.c
51 +@@ -0,0 +3,8 @@
52 ++int f(int x)
53 ++{
54 ++ int s = 0;
55 ++ while (x) {
56 ++ x >>= 1;
57 ++ s++;
58 ++ }
59 ++}
t/t4211/expect.simple-main new
+68
@@ -0,0 +1,68 @@
1 +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:48:43 2013 +0100
4 +
5 + change back to complete line
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -18,5 +18,5 @@
11 + int main ()
12 + {
13 + printf("%ld\n", f(15));
14 + return 0;
15 +-}
16 +\ No newline at end of file
17 ++}
18 +
19 +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
20 +Author: Thomas Rast <trast@student.ethz.ch>
21 +Date: Thu Feb 28 10:48:10 2013 +0100
22 +
23 + change to an incomplete line at end
24 +
25 +diff --git a/a.c b/a.c
26 +--- a/a.c
27 ++++ b/a.c
28 +@@ -18,5 +18,5 @@
29 + int main ()
30 + {
31 + printf("%ld\n", f(15));
32 + return 0;
33 +-}
34 ++}
35 +\ No newline at end of file
36 +
37 +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
38 +Author: Thomas Rast <trast@student.ethz.ch>
39 +Date: Thu Feb 28 10:45:16 2013 +0100
40 +
41 + touch both functions
42 +
43 +diff --git a/a.c b/a.c
44 +--- a/a.c
45 ++++ b/a.c
46 +@@ -17,5 +17,5 @@
47 + int main ()
48 + {
49 +- printf("%d\n", f(15));
50 ++ printf("%ld\n", f(15));
51 + return 0;
52 + }
53 +
54 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
55 +Author: Thomas Rast <trast@student.ethz.ch>
56 +Date: Thu Feb 28 10:44:48 2013 +0100
57 +
58 + initial
59 +
60 +diff --git a/a.c b/a.c
61 +--- /dev/null
62 ++++ b/a.c
63 +@@ -0,0 +16,5 @@
64 ++int main ()
65 ++{
66 ++ printf("%d\n", f(15));
67 ++ return 0;
68 ++}
t/t4211/expect.two-ranges new
+102
@@ -0,0 +1,102 @@
1 +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:48:43 2013 +0100
4 +
5 + change back to complete line
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -18,5 +18,5 @@
11 + int main ()
12 + {
13 + printf("%ld\n", f(15));
14 + return 0;
15 +-}
16 +\ No newline at end of file
17 ++}
18 +
19 +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
20 +Author: Thomas Rast <trast@student.ethz.ch>
21 +Date: Thu Feb 28 10:48:10 2013 +0100
22 +
23 + change to an incomplete line at end
24 +
25 +diff --git a/a.c b/a.c
26 +--- a/a.c
27 ++++ b/a.c
28 +@@ -18,5 +18,5 @@
29 + int main ()
30 + {
31 + printf("%ld\n", f(15));
32 + return 0;
33 +-}
34 ++}
35 +\ No newline at end of file
36 +
37 +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
38 +Author: Thomas Rast <trast@student.ethz.ch>
39 +Date: Thu Feb 28 10:45:16 2013 +0100
40 +
41 + touch both functions
42 +
43 +diff --git a/a.c b/a.c
44 +--- a/a.c
45 ++++ b/a.c
46 +@@ -3,9 +3,9 @@
47 +-int f(int x)
48 ++long f(long x)
49 + {
50 + int s = 0;
51 + while (x) {
52 + x >>= 1;
53 + s++;
54 + }
55 + return s;
56 + }
57 +@@ -17,5 +17,5 @@
58 + int main ()
59 + {
60 +- printf("%d\n", f(15));
61 ++ printf("%ld\n", f(15));
62 + return 0;
63 + }
64 +
65 +commit f04fb20f2c77850996cba739709acc6faecc58f7
66 +Author: Thomas Rast <trast@student.ethz.ch>
67 +Date: Thu Feb 28 10:44:55 2013 +0100
68 +
69 + change f()
70 +
71 +diff --git a/a.c b/a.c
72 +--- a/a.c
73 ++++ b/a.c
74 +@@ -3,8 +3,9 @@
75 + int f(int x)
76 + {
77 + int s = 0;
78 + while (x) {
79 + x >>= 1;
80 + s++;
81 + }
82 ++ return s;
83 + }
84 +
85 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
86 +Author: Thomas Rast <trast@student.ethz.ch>
87 +Date: Thu Feb 28 10:44:48 2013 +0100
88 +
89 + initial
90 +
91 +diff --git a/a.c b/a.c
92 +--- /dev/null
93 ++++ b/a.c
94 +@@ -0,0 +3,8 @@
95 ++int f(int x)
96 ++{
97 ++ int s = 0;
98 ++ while (x) {
99 ++ x >>= 1;
100 ++ s++;
101 ++ }
102 ++}
t/t4211/expect.vanishes-early new
+39
@@ -0,0 +1,39 @@
1 +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:48:43 2013 +0100
4 +
5 + change back to complete line
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -22,1 +24,1 @@
11 +-}
12 +\ No newline at end of file
13 ++/* incomplete lines are bad! */
14 +
15 +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
16 +Author: Thomas Rast <trast@student.ethz.ch>
17 +Date: Thu Feb 28 10:48:10 2013 +0100
18 +
19 + change to an incomplete line at end
20 +
21 +diff --git a/a.c b/a.c
22 +--- a/a.c
23 ++++ b/a.c
24 +@@ -22,1 +22,1 @@
25 +-}
26 ++}
27 +\ No newline at end of file
28 +
29 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
30 +Author: Thomas Rast <trast@student.ethz.ch>
31 +Date: Thu Feb 28 10:44:48 2013 +0100
32 +
33 + initial
34 +
35 +diff --git a/a.c b/a.c
36 +--- /dev/null
37 ++++ b/a.c
38 +@@ -0,0 +20,1 @@
39 ++}
t/t4211/history.export new
+330
@@ -0,0 +1,330 @@
1 +blob
2 +mark :1
3 +data 157
4 +#include <stdio.h>
5 +
6 +int f(int x)
7 +{
8 + int s = 0;
9 + while (x) {
10 + x >>= 1;
11 + s++;
12 + }
13 +}
14 +
15 +/*
16 + * A comment.
17 + */
18 +
19 +int main ()
20 +{
21 + printf("%d\n", f(15));
22 + return 0;
23 +}
24 +
25 +reset refs/tags/simple
26 +commit refs/tags/simple
27 +mark :2
28 +author Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
29 +committer Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
30 +data 8
31 +initial
32 +M 100644 :1 a.c
33 +
34 +blob
35 +mark :3
36 +data 168
37 +#include <stdio.h>
38 +
39 +int f(int x)
40 +{
41 + int s = 0;
42 + while (x) {
43 + x >>= 1;
44 + s++;
45 + }
46 + return s;
47 +}
48 +
49 +/*
50 + * A comment.
51 + */
52 +
53 +int main ()
54 +{
55 + printf("%d\n", f(15));
56 + return 0;
57 +}
58 +
59 +commit refs/tags/simple
60 +mark :4
61 +author Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
62 +committer Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
63 +data 11
64 +change f()
65 +from :2
66 +M 100644 :3 a.c
67 +
68 +blob
69 +mark :5
70 +data 171
71 +#include <stdio.h>
72 +
73 +long f(long x)
74 +{
75 + int s = 0;
76 + while (x) {
77 + x >>= 1;
78 + s++;
79 + }
80 + return s;
81 +}
82 +
83 +/*
84 + * A comment.
85 + */
86 +
87 +int main ()
88 +{
89 + printf("%ld\n", f(15));
90 + return 0;
91 +}
92 +
93 +commit refs/tags/simple
94 +mark :6
95 +author Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
96 +committer Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
97 +data 21
98 +touch both functions
99 +from :4
100 +M 100644 :5 a.c
101 +
102 +blob
103 +mark :7
104 +data 185
105 +#include <stdio.h>
106 +
107 +long f(long x)
108 +{
109 + int s = 0;
110 + while (x) {
111 + x >>= 1;
112 + s++;
113 + }
114 + return s;
115 +}
116 +
117 +/*
118 + * This is only an example!
119 + */
120 +
121 +int main ()
122 +{
123 + printf("%ld\n", f(15));
124 + return 0;
125 +}
126 +
127 +commit refs/tags/simple
128 +mark :8
129 +author Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
130 +committer Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
131 +data 14
132 +touch comment
133 +from :6
134 +M 100644 :7 a.c
135 +
136 +blob
137 +mark :9
138 +data 205
139 +#include <unistd.h>
140 +#include <stdio.h>
141 +
142 +long f(long x)
143 +{
144 + int s = 0;
145 + while (x) {
146 + x >>= 1;
147 + s++;
148 + }
149 + return s;
150 +}
151 +
152 +/*
153 + * This is only an example!
154 + */
155 +
156 +int main ()
157 +{
158 + printf("%ld\n", f(15));
159 + return 0;
160 +}
161 +
162 +commit refs/tags/simple
163 +mark :10
164 +author Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
165 +committer Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
166 +data 25
167 +change at very beginning
168 +from :8
169 +M 100644 :9 a.c
170 +
171 +blob
172 +mark :11
173 +data 204
174 +#include <unistd.h>
175 +#include <stdio.h>
176 +
177 +long f(long x)
178 +{
179 + int s = 0;
180 + while (x) {
181 + x >>= 1;
182 + s++;
183 + }
184 + return s;
185 +}
186 +
187 +/*
188 + * This is only an example!
189 + */
190 +
191 +int main ()
192 +{
193 + printf("%ld\n", f(15));
194 + return 0;
195 +}
196 +commit refs/tags/simple
197 +mark :12
198 +author Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
199 +committer Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
200 +data 36
201 +change to an incomplete line at end
202 +from :10
203 +M 100644 :11 a.c
204 +
205 +blob
206 +mark :13
207 +data 238
208 +#include <unistd.h>
209 +#include <stdio.h>
210 +
211 +long f(long x)
212 +{
213 + int s = 0;
214 + while (x) {
215 + x >>= 1;
216 + s++;
217 + }
218 + return s;
219 +}
220 +
221 +/*
222 + * This is only an example!
223 + */
224 +
225 +int main ()
226 +{
227 + printf("%ld\n", f(15));
228 + return 0;
229 +}
230 +
231 +/* incomplete lines are bad! */
232 +
233 +commit refs/tags/simple
234 +mark :14
235 +author Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
236 +committer Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
237 +data 29
238 +change back to complete line
239 +from :12
240 +M 100644 :13 a.c
241 +
242 +commit refs/tags/move-support
243 +mark :15
244 +author Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
245 +committer Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
246 +data 10
247 +move file
248 +from :14
249 +D a.c
250 +M 100644 :13 b.c
251 +
252 +blob
253 +mark :16
254 +data 237
255 +#include <unistd.h>
256 +#include <stdio.h>
257 +
258 +long f(long x)
259 +{
260 + int s = 0;
261 + while (x) {
262 + x /= 2;
263 + s++;
264 + }
265 + return s;
266 +}
267 +
268 +/*
269 + * This is only an example!
270 + */
271 +
272 +int main ()
273 +{
274 + printf("%ld\n", f(15));
275 + return 0;
276 +}
277 +
278 +/* incomplete lines are bad! */
279 +
280 +commit refs/tags/move-support
281 +mark :17
282 +author Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
283 +committer Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
284 +data 22
285 +another simple change
286 +from :15
287 +M 100644 :16 b.c
288 +
289 +blob
290 +mark :18
291 +data 254
292 +#include <unistd.h>
293 +#include <stdio.h>
294 +
295 +long f(long x);
296 +
297 +/*
298 + * This is only an example!
299 + */
300 +
301 +int main ()
302 +{
303 + printf("%ld\n", f(15));
304 + return 0;
305 +}
306 +
307 +/* incomplete lines are bad! */
308 +
309 +long f(long x)
310 +{
311 + int s = 0;
312 + while (x) {
313 + x /= 2;
314 + s++;
315 + }
316 + return s;
317 +}
318 +
319 +commit refs/heads/master
320 +mark :19
321 +author Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
322 +committer Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
323 +data 21
324 +move within the file
325 +from :17
326 +M 100644 :18 b.c
327 +
328 +reset refs/heads/master
329 +from :19
330 +