|
20 | 20 | #include "bloom.h" |
21 | 21 | #include "tree-walk.h" |
22 | 22 |
|
| 23 | +/* |
| 24 | + * "git log -L": follow a set of line ranges through history. |
| 25 | + * |
| 26 | + * Each interesting commit carries a "struct line_log_data" (a per-path |
| 27 | + * list of tracked "struct range_set" line ranges) as a decoration in |
| 28 | + * rev->line_log_data. Walking from a commit to its parent maps those |
| 29 | + * ranges back across the commit's diff (process_ranges_ordinary_commit() |
| 30 | + * via range_set_map_across_diff()), so each parent inherits the ranges |
| 31 | + * it is responsible for. |
| 32 | + * |
| 33 | + * The walk runs in one of two modes: |
| 34 | + * |
| 35 | + * - Eager, under parent rewriting (want_ancestry(), e.g. --graph): |
| 36 | + * line_log_filter() processes every commit in prepare_revision_walk() |
| 37 | + * and rewrites the parent links. |
| 38 | + * |
| 39 | + * - Lazy, for a plain "git log -L": each commit is processed on demand |
| 40 | + * during the walk, from get_commit_action() in revision.c. |
| 41 | + * |
| 42 | + * Both share line_log_process_commit(), which processes |
| 43 | + * one commit and reports whether it changed a tracked range. |
| 44 | + */ |
| 45 | + |
| 46 | +/* line_log_data: the sorted per-path list of tracked line ranges. */ |
| 47 | + |
23 | 48 | static void line_log_data_init(struct line_log_data *r) |
24 | 49 | { |
25 | 50 | memset(r, 0, sizeof(struct line_log_data)); |
@@ -92,42 +117,119 @@ static void line_log_data_insert(struct line_log_data **list, |
92 | 117 | } |
93 | 118 | } |
94 | 119 |
|
95 | | -struct collect_diff_cbdata { |
96 | | - struct diff_ranges *diff; |
97 | | -}; |
| 120 | +static struct line_log_data *line_log_data_copy_one(struct line_log_data *r) |
| 121 | +{ |
| 122 | + struct line_log_data *ret = xmalloc(sizeof(*ret)); |
98 | 123 |
|
99 | | -static int collect_diff_cb(long start_a, long count_a, |
100 | | - long start_b, long count_b, |
101 | | - void *data) |
| 124 | + assert(r); |
| 125 | + line_log_data_init(ret); |
| 126 | + range_set_copy(&ret->ranges, &r->ranges); |
| 127 | + |
| 128 | + ret->path = xstrdup(r->path); |
| 129 | + |
| 130 | + return ret; |
| 131 | +} |
| 132 | + |
| 133 | +static struct line_log_data * |
| 134 | +line_log_data_copy(struct line_log_data *r) |
102 | 135 | { |
103 | | - struct collect_diff_cbdata *d = data; |
| 136 | + struct line_log_data *ret = NULL; |
| 137 | + struct line_log_data *tmp = NULL, *prev = NULL; |
104 | 138 |
|
105 | | - if (count_a >= 0) |
106 | | - range_set_append(&d->diff->parent, start_a, start_a + count_a); |
107 | | - if (count_b >= 0) |
108 | | - range_set_append(&d->diff->target, start_b, start_b + count_b); |
| 139 | + assert(r); |
| 140 | + ret = tmp = prev = line_log_data_copy_one(r); |
| 141 | + r = r->next; |
| 142 | + while (r) { |
| 143 | + tmp = line_log_data_copy_one(r); |
| 144 | + prev->next = tmp; |
| 145 | + prev = tmp; |
| 146 | + r = r->next; |
| 147 | + } |
109 | 148 |
|
110 | | - return 0; |
| 149 | + return ret; |
111 | 150 | } |
112 | 151 |
|
113 | | -static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out) |
| 152 | +/* merge two range sets across files */ |
| 153 | +static struct line_log_data *line_log_data_merge(struct line_log_data *a, |
| 154 | + struct line_log_data *b) |
114 | 155 | { |
115 | | - struct collect_diff_cbdata cbdata = {NULL}; |
116 | | - xpparam_t xpp; |
117 | | - xdemitconf_t xecfg; |
118 | | - xdemitcb_t ecb; |
| 156 | + struct line_log_data *head = NULL, **pp = &head; |
119 | 157 |
|
120 | | - memset(&xpp, 0, sizeof(xpp)); |
121 | | - memset(&xecfg, 0, sizeof(xecfg)); |
122 | | - xecfg.ctxlen = xecfg.interhunkctxlen = 0; |
| 158 | + while (a || b) { |
| 159 | + struct line_log_data *src; |
| 160 | + struct line_log_data *src2 = NULL; |
| 161 | + struct line_log_data *d; |
| 162 | + int cmp; |
| 163 | + if (!a) |
| 164 | + cmp = 1; |
| 165 | + else if (!b) |
| 166 | + cmp = -1; |
| 167 | + else |
| 168 | + cmp = strcmp(a->path, b->path); |
| 169 | + if (cmp < 0) { |
| 170 | + src = a; |
| 171 | + a = a->next; |
| 172 | + } else if (cmp == 0) { |
| 173 | + src = a; |
| 174 | + a = a->next; |
| 175 | + src2 = b; |
| 176 | + b = b->next; |
| 177 | + } else { |
| 178 | + src = b; |
| 179 | + b = b->next; |
| 180 | + } |
| 181 | + d = xmalloc(sizeof(struct line_log_data)); |
| 182 | + line_log_data_init(d); |
| 183 | + d->path = xstrdup(src->path); |
| 184 | + *pp = d; |
| 185 | + pp = &d->next; |
| 186 | + if (src2) |
| 187 | + range_set_union(&d->ranges, &src->ranges, &src2->ranges); |
| 188 | + else |
| 189 | + range_set_copy(&d->ranges, &src->ranges); |
| 190 | + } |
123 | 191 |
|
124 | | - cbdata.diff = out; |
125 | | - xecfg.hunk_func = collect_diff_cb; |
126 | | - memset(&ecb, 0, sizeof(ecb)); |
127 | | - ecb.priv = &cbdata; |
128 | | - return xdi_diff(parent, target, &xpp, &xecfg, &ecb); |
| 192 | + return head; |
| 193 | +} |
| 194 | + |
| 195 | +static void add_line_range(struct rev_info *revs, struct commit *commit, |
| 196 | + struct line_log_data *range) |
| 197 | +{ |
| 198 | + struct line_log_data *old_line = NULL; |
| 199 | + struct line_log_data *new_line = NULL; |
| 200 | + |
| 201 | + old_line = lookup_decoration(&revs->line_log_data, &commit->object); |
| 202 | + if (old_line && range) { |
| 203 | + new_line = line_log_data_merge(old_line, range); |
| 204 | + free_line_log_data(old_line); |
| 205 | + } else if (range) |
| 206 | + new_line = line_log_data_copy(range); |
| 207 | + |
| 208 | + if (new_line) |
| 209 | + add_decoration(&revs->line_log_data, &commit->object, new_line); |
| 210 | +} |
| 211 | + |
| 212 | +static void clear_commit_line_range(struct rev_info *revs, struct commit *commit) |
| 213 | +{ |
| 214 | + struct line_log_data *r; |
| 215 | + r = lookup_decoration(&revs->line_log_data, &commit->object); |
| 216 | + if (!r) |
| 217 | + return; |
| 218 | + free_line_log_data(r); |
| 219 | + add_decoration(&revs->line_log_data, &commit->object, NULL); |
129 | 220 | } |
130 | 221 |
|
| 222 | +static struct line_log_data *lookup_line_range(struct rev_info *revs, |
| 223 | + struct commit *commit) |
| 224 | +{ |
| 225 | + return lookup_decoration(&revs->line_log_data, &commit->object); |
| 226 | +} |
| 227 | + |
| 228 | +/* |
| 229 | + * Parsing "-L" arguments into an initial set of tracked ranges and the |
| 230 | + * pathspec that limits the diff. line_log_init() is the entry point. |
| 231 | + */ |
| 232 | + |
131 | 233 | static struct commit *check_single_commit(struct rev_info *revs) |
132 | 234 | { |
133 | 235 | struct object *commit = NULL; |
@@ -164,8 +266,6 @@ static void fill_blob_sha1(struct repository *r, struct commit *commit, |
164 | 266 | if (get_tree_entry(r, &commit->object.oid, spec->path, &oid, &mode)) |
165 | 267 | die("There is no path %s in the commit", spec->path); |
166 | 268 | fill_filespec(spec, &oid, 1, mode); |
167 | | - |
168 | | - return; |
169 | 269 | } |
170 | 270 |
|
171 | 271 | static void fill_line_ends(struct repository *r, |
@@ -282,114 +382,6 @@ parse_lines(struct repository *r, struct commit *commit, |
282 | 382 | return ranges; |
283 | 383 | } |
284 | 384 |
|
285 | | -static struct line_log_data *line_log_data_copy_one(struct line_log_data *r) |
286 | | -{ |
287 | | - struct line_log_data *ret = xmalloc(sizeof(*ret)); |
288 | | - |
289 | | - assert(r); |
290 | | - line_log_data_init(ret); |
291 | | - range_set_copy(&ret->ranges, &r->ranges); |
292 | | - |
293 | | - ret->path = xstrdup(r->path); |
294 | | - |
295 | | - return ret; |
296 | | -} |
297 | | - |
298 | | -static struct line_log_data * |
299 | | -line_log_data_copy(struct line_log_data *r) |
300 | | -{ |
301 | | - struct line_log_data *ret = NULL; |
302 | | - struct line_log_data *tmp = NULL, *prev = NULL; |
303 | | - |
304 | | - assert(r); |
305 | | - ret = tmp = prev = line_log_data_copy_one(r); |
306 | | - r = r->next; |
307 | | - while (r) { |
308 | | - tmp = line_log_data_copy_one(r); |
309 | | - prev->next = tmp; |
310 | | - prev = tmp; |
311 | | - r = r->next; |
312 | | - } |
313 | | - |
314 | | - return ret; |
315 | | -} |
316 | | - |
317 | | -/* merge two range sets across files */ |
318 | | -static struct line_log_data *line_log_data_merge(struct line_log_data *a, |
319 | | - struct line_log_data *b) |
320 | | -{ |
321 | | - struct line_log_data *head = NULL, **pp = &head; |
322 | | - |
323 | | - while (a || b) { |
324 | | - struct line_log_data *src; |
325 | | - struct line_log_data *src2 = NULL; |
326 | | - struct line_log_data *d; |
327 | | - int cmp; |
328 | | - if (!a) |
329 | | - cmp = 1; |
330 | | - else if (!b) |
331 | | - cmp = -1; |
332 | | - else |
333 | | - cmp = strcmp(a->path, b->path); |
334 | | - if (cmp < 0) { |
335 | | - src = a; |
336 | | - a = a->next; |
337 | | - } else if (cmp == 0) { |
338 | | - src = a; |
339 | | - a = a->next; |
340 | | - src2 = b; |
341 | | - b = b->next; |
342 | | - } else { |
343 | | - src = b; |
344 | | - b = b->next; |
345 | | - } |
346 | | - d = xmalloc(sizeof(struct line_log_data)); |
347 | | - line_log_data_init(d); |
348 | | - d->path = xstrdup(src->path); |
349 | | - *pp = d; |
350 | | - pp = &d->next; |
351 | | - if (src2) |
352 | | - range_set_union(&d->ranges, &src->ranges, &src2->ranges); |
353 | | - else |
354 | | - range_set_copy(&d->ranges, &src->ranges); |
355 | | - } |
356 | | - |
357 | | - return head; |
358 | | -} |
359 | | - |
360 | | -static void add_line_range(struct rev_info *revs, struct commit *commit, |
361 | | - struct line_log_data *range) |
362 | | -{ |
363 | | - struct line_log_data *old_line = NULL; |
364 | | - struct line_log_data *new_line = NULL; |
365 | | - |
366 | | - old_line = lookup_decoration(&revs->line_log_data, &commit->object); |
367 | | - if (old_line && range) { |
368 | | - new_line = line_log_data_merge(old_line, range); |
369 | | - free_line_log_data(old_line); |
370 | | - } else if (range) |
371 | | - new_line = line_log_data_copy(range); |
372 | | - |
373 | | - if (new_line) |
374 | | - add_decoration(&revs->line_log_data, &commit->object, new_line); |
375 | | -} |
376 | | - |
377 | | -static void clear_commit_line_range(struct rev_info *revs, struct commit *commit) |
378 | | -{ |
379 | | - struct line_log_data *r; |
380 | | - r = lookup_decoration(&revs->line_log_data, &commit->object); |
381 | | - if (!r) |
382 | | - return; |
383 | | - free_line_log_data(r); |
384 | | - add_decoration(&revs->line_log_data, &commit->object, NULL); |
385 | | -} |
386 | | - |
387 | | -static struct line_log_data *lookup_line_range(struct rev_info *revs, |
388 | | - struct commit *commit) |
389 | | -{ |
390 | | - return lookup_decoration(&revs->line_log_data, &commit->object); |
391 | | -} |
392 | | - |
393 | 385 | static int same_paths_in_pathspec_and_range(struct pathspec *pathspec, |
394 | 386 | struct line_log_data *range) |
395 | 387 | { |
@@ -434,6 +426,47 @@ void line_log_init(struct rev_info *rev, const char *prefix, struct string_list |
434 | 426 | free_line_log_data(range); |
435 | 427 | } |
436 | 428 |
|
| 429 | +/* |
| 430 | + * Compute one commit's diff against a parent as a set of changed line |
| 431 | + * ranges (collect_diff), then walk the tracked ranges back across it. |
| 432 | + */ |
| 433 | + |
| 434 | +struct collect_diff_cbdata { |
| 435 | + struct diff_ranges *diff; |
| 436 | +}; |
| 437 | + |
| 438 | +static int collect_diff_cb(long start_a, long count_a, |
| 439 | + long start_b, long count_b, |
| 440 | + void *data) |
| 441 | +{ |
| 442 | + struct collect_diff_cbdata *d = data; |
| 443 | + |
| 444 | + if (count_a >= 0) |
| 445 | + range_set_append(&d->diff->parent, start_a, start_a + count_a); |
| 446 | + if (count_b >= 0) |
| 447 | + range_set_append(&d->diff->target, start_b, start_b + count_b); |
| 448 | + |
| 449 | + return 0; |
| 450 | +} |
| 451 | + |
| 452 | +static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out) |
| 453 | +{ |
| 454 | + struct collect_diff_cbdata cbdata = {NULL}; |
| 455 | + xpparam_t xpp; |
| 456 | + xdemitconf_t xecfg; |
| 457 | + xdemitcb_t ecb; |
| 458 | + |
| 459 | + memset(&xpp, 0, sizeof(xpp)); |
| 460 | + memset(&xecfg, 0, sizeof(xecfg)); |
| 461 | + xecfg.ctxlen = xecfg.interhunkctxlen = 0; |
| 462 | + |
| 463 | + cbdata.diff = out; |
| 464 | + xecfg.hunk_func = collect_diff_cb; |
| 465 | + memset(&ecb, 0, sizeof(ecb)); |
| 466 | + ecb.priv = &cbdata; |
| 467 | + return xdi_diff(parent, target, &xpp, &xecfg, &ecb); |
| 468 | +} |
| 469 | + |
437 | 470 | static void move_diff_queue(struct diff_queue_struct *dst, |
438 | 471 | struct diff_queue_struct *src) |
439 | 472 | { |
@@ -475,11 +508,8 @@ static inline int diff_might_be_rename(void) |
475 | 508 | { |
476 | 509 | int i; |
477 | 510 | for (i = 0; i < diff_queued_diff.nr; i++) |
478 | | - if (!DIFF_FILE_VALID(diff_queued_diff.queue[i]->one)) { |
479 | | - /* fprintf(stderr, "diff_might_be_rename found creation of: %s\n", */ |
480 | | - /* diff_queued_diff.queue[i]->two->path); */ |
| 511 | + if (!DIFF_FILE_VALID(diff_queued_diff.queue[i]->one)) |
481 | 512 | return 1; |
482 | | - } |
483 | 513 | return 0; |
484 | 514 | } |
485 | 515 |
|
@@ -695,6 +725,8 @@ static int bloom_filter_check(struct rev_info *rev, |
695 | 725 | return result; |
696 | 726 | } |
697 | 727 |
|
| 728 | +/* Walking history: map each commit's tracked ranges to its parent(s). */ |
| 729 | + |
698 | 730 | static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit, |
699 | 731 | struct line_log_data *range) |
700 | 732 | { |
@@ -780,7 +812,15 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm |
780 | 812 | /* NEEDSWORK evil merge detection stuff */ |
781 | 813 | } |
782 | 814 |
|
783 | | -int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit) |
| 815 | +/* |
| 816 | + * Entry point for one commit (both traversal modes call this). Dispatch |
| 817 | + * on the commit's shape: no tracked ranges (nothing to do); a Bloom |
| 818 | + * filter proving the file untouched (carry ranges to the sole parent |
| 819 | + * without diffing); a merge (map across each parent, splitting the |
| 820 | + * ranges); or an ordinary commit. A commit that changed no tracked |
| 821 | + * range is marked TREESAME so the caller can prune it. |
| 822 | + */ |
| 823 | +int line_log_process_commit(struct rev_info *rev, struct commit *commit) |
784 | 824 | { |
785 | 825 | struct line_log_data *range = lookup_line_range(rev, commit); |
786 | 826 | int changed = 0; |
@@ -828,7 +868,7 @@ int line_log_filter(struct rev_info *rev) |
828 | 868 | while (list) { |
829 | 869 | struct commit_list *to_free = NULL; |
830 | 870 | commit = list->item; |
831 | | - if (line_log_process_ranges_arbitrary_commit(rev, commit)) { |
| 871 | + if (line_log_process_commit(rev, commit)) { |
832 | 872 | *pp = list; |
833 | 873 | pp = &list->next; |
834 | 874 | } else |
|
0 commit comments