git
/
line-log.c
1343 строки · 33.3 Кб
1#include "git-compat-util.h"2#include "diffcore.h"3#include "line-range.h"4#include "hex.h"5#include "tag.h"6#include "tree.h"7#include "diff.h"8#include "commit.h"9#include "decorate.h"10#include "repository.h"11#include "revision.h"12#include "xdiff-interface.h"13#include "strbuf.h"14#include "log-tree.h"15#include "line-log.h"16#include "setup.h"17#include "strvec.h"18#include "bloom.h"19#include "tree-walk.h"20
21static void range_set_grow(struct range_set *rs, size_t extra)22{
23ALLOC_GROW(rs->ranges, rs->nr + extra, rs->alloc);24}
25
26/* Either initialization would be fine */
27#define RANGE_SET_INIT {0}28
29void range_set_init(struct range_set *rs, size_t prealloc)30{
31rs->alloc = rs->nr = 0;32rs->ranges = NULL;33if (prealloc)34range_set_grow(rs, prealloc);35}
36
37void range_set_release(struct range_set *rs)38{
39FREE_AND_NULL(rs->ranges);40rs->alloc = rs->nr = 0;41}
42
43/* dst must be uninitialized! */
44static void range_set_copy(struct range_set *dst, struct range_set *src)45{
46range_set_init(dst, src->nr);47COPY_ARRAY(dst->ranges, src->ranges, src->nr);48dst->nr = src->nr;49}
50
51static void range_set_move(struct range_set *dst, struct range_set *src)52{
53range_set_release(dst);54dst->ranges = src->ranges;55dst->nr = src->nr;56dst->alloc = src->alloc;57src->ranges = NULL;58src->alloc = src->nr = 0;59}
60
61/* tack on a _new_ range _at the end_ */
62void range_set_append_unsafe(struct range_set *rs, long a, long b)63{
64assert(a <= b);65range_set_grow(rs, 1);66rs->ranges[rs->nr].start = a;67rs->ranges[rs->nr].end = b;68rs->nr++;69}
70
71void range_set_append(struct range_set *rs, long a, long b)72{
73assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);74range_set_append_unsafe(rs, a, b);75}
76
77static int range_cmp(const void *_r, const void *_s)78{
79const struct range *r = _r;80const struct range *s = _s;81
82/* this could be simply 'return r.start-s.start', but for the types */83if (r->start == s->start)84return 0;85if (r->start < s->start)86return -1;87return 1;88}
89
90/*
91* Check that the ranges are non-empty, sorted and non-overlapping
92*/
93static void range_set_check_invariants(struct range_set *rs)94{
95unsigned int i;96
97if (!rs)98return;99
100if (rs->nr)101assert(rs->ranges[0].start < rs->ranges[0].end);102
103for (i = 1; i < rs->nr; i++) {104assert(rs->ranges[i-1].end < rs->ranges[i].start);105assert(rs->ranges[i].start < rs->ranges[i].end);106}107}
108
109/*
110* In-place pass of sorting and merging the ranges in the range set,
111* to establish the invariants when we get the ranges from the user
112*/
113void sort_and_merge_range_set(struct range_set *rs)114{
115unsigned int i;116unsigned int o = 0; /* output cursor */117
118QSORT(rs->ranges, rs->nr, range_cmp);119
120for (i = 0; i < rs->nr; i++) {121if (rs->ranges[i].start == rs->ranges[i].end)122continue;123if (o > 0 && rs->ranges[i].start <= rs->ranges[o-1].end) {124if (rs->ranges[o-1].end < rs->ranges[i].end)125rs->ranges[o-1].end = rs->ranges[i].end;126} else {127rs->ranges[o].start = rs->ranges[i].start;128rs->ranges[o].end = rs->ranges[i].end;129o++;130}131}132assert(o <= rs->nr);133rs->nr = o;134
135range_set_check_invariants(rs);136}
137
138/*
139* Union of range sets (i.e., sets of line numbers). Used to merge
140* them when searches meet at a common ancestor.
141*
142* This is also where the ranges are consolidated into canonical form:
143* overlapping and adjacent ranges are merged, and empty ranges are
144* removed.
145*/
146static void range_set_union(struct range_set *out,147struct range_set *a, struct range_set *b)148{
149unsigned int i = 0, j = 0;150struct range *ra = a->ranges;151struct range *rb = b->ranges;152/* cannot make an alias of out->ranges: it may change during grow */153
154assert(out->nr == 0);155while (i < a->nr || j < b->nr) {156struct range *new_range;157if (i < a->nr && j < b->nr) {158if (ra[i].start < rb[j].start)159new_range = &ra[i++];160else if (ra[i].start > rb[j].start)161new_range = &rb[j++];162else if (ra[i].end < rb[j].end)163new_range = &ra[i++];164else165new_range = &rb[j++];166} else if (i < a->nr) /* b exhausted */167new_range = &ra[i++];168else /* a exhausted */169new_range = &rb[j++];170if (new_range->start == new_range->end)171; /* empty range */172else if (!out->nr || out->ranges[out->nr-1].end < new_range->start) {173range_set_grow(out, 1);174out->ranges[out->nr].start = new_range->start;175out->ranges[out->nr].end = new_range->end;176out->nr++;177} else if (out->ranges[out->nr-1].end < new_range->end) {178out->ranges[out->nr-1].end = new_range->end;179}180}181}
182
183/*
184* Difference of range sets (out = a \ b). Pass the "interesting"
185* ranges as 'a' and the target side of the diff as 'b': it removes
186* the ranges for which the commit is responsible.
187*/
188static void range_set_difference(struct range_set *out,189struct range_set *a, struct range_set *b)190{
191unsigned int i, j = 0;192for (i = 0; i < a->nr; i++) {193long start = a->ranges[i].start;194long end = a->ranges[i].end;195while (start < end) {196while (j < b->nr && start >= b->ranges[j].end)197/*198* a: |-------
199* b: ------|
200*/
201j++;202if (j >= b->nr || end < b->ranges[j].start) {203/*204* b exhausted, or
205* a: ----|
206* b: |----
207*/
208range_set_append(out, start, end);209break;210}211if (start >= b->ranges[j].start) {212/*213* a: |--????
214* b: |------|
215*/
216start = b->ranges[j].end;217} else if (end > b->ranges[j].start) {218/*219* a: |-----|
220* b: |--?????
221*/
222if (start < b->ranges[j].start)223range_set_append(out, start, b->ranges[j].start);224start = b->ranges[j].end;225}226}227}228}
229
230static void diff_ranges_init(struct diff_ranges *diff)231{
232range_set_init(&diff->parent, 0);233range_set_init(&diff->target, 0);234}
235
236static void diff_ranges_release(struct diff_ranges *diff)237{
238range_set_release(&diff->parent);239range_set_release(&diff->target);240}
241
242static void line_log_data_init(struct line_log_data *r)243{
244memset(r, 0, sizeof(struct line_log_data));245range_set_init(&r->ranges, 0);246}
247
248static void line_log_data_clear(struct line_log_data *r)249{
250range_set_release(&r->ranges);251if (r->pair)252diff_free_filepair(r->pair);253}
254
255static void free_line_log_data(struct line_log_data *r)256{
257while (r) {258struct line_log_data *next = r->next;259line_log_data_clear(r);260free(r);261r = next;262}263}
264
265static struct line_log_data *266search_line_log_data(struct line_log_data *list, const char *path,267struct line_log_data **insertion_point)268{
269struct line_log_data *p = list;270if (insertion_point)271*insertion_point = NULL;272while (p) {273int cmp = strcmp(p->path, path);274if (!cmp)275return p;276if (insertion_point && cmp < 0)277*insertion_point = p;278p = p->next;279}280return NULL;281}
282
283/*
284* Note: takes ownership of 'path', which happens to be what the only
285* caller needs.
286*/
287static void line_log_data_insert(struct line_log_data **list,288char *path,289long begin, long end)290{
291struct line_log_data *ip;292struct line_log_data *p = search_line_log_data(*list, path, &ip);293
294if (p) {295range_set_append_unsafe(&p->ranges, begin, end);296free(path);297return;298}299
300CALLOC_ARRAY(p, 1);301p->path = path;302range_set_append(&p->ranges, begin, end);303if (ip) {304p->next = ip->next;305ip->next = p;306} else {307p->next = *list;308*list = p;309}310}
311
312struct collect_diff_cbdata {313struct diff_ranges *diff;314};315
316static int collect_diff_cb(long start_a, long count_a,317long start_b, long count_b,318void *data)319{
320struct collect_diff_cbdata *d = data;321
322if (count_a >= 0)323range_set_append(&d->diff->parent, start_a, start_a + count_a);324if (count_b >= 0)325range_set_append(&d->diff->target, start_b, start_b + count_b);326
327return 0;328}
329
330static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)331{
332struct collect_diff_cbdata cbdata = {NULL};333xpparam_t xpp;334xdemitconf_t xecfg;335xdemitcb_t ecb;336
337memset(&xpp, 0, sizeof(xpp));338memset(&xecfg, 0, sizeof(xecfg));339xecfg.ctxlen = xecfg.interhunkctxlen = 0;340
341cbdata.diff = out;342xecfg.hunk_func = collect_diff_cb;343memset(&ecb, 0, sizeof(ecb));344ecb.priv = &cbdata;345return xdi_diff(parent, target, &xpp, &xecfg, &ecb);346}
347
348/*
349* These are handy for debugging. Removing them with #if 0 silences
350* the "unused function" warning.
351*/
352#if 0353static void dump_range_set(struct range_set *rs, const char *desc)354{
355int i;356printf("range set %s (%d items):\n", desc, rs->nr);357for (i = 0; i < rs->nr; i++)358printf("\t[%ld,%ld]\n", rs->ranges[i].start, rs->ranges[i].end);359}
360
361static void dump_line_log_data(struct line_log_data *r)362{
363char buf[4096];364while (r) {365snprintf(buf, 4096, "file %s\n", r->path);366dump_range_set(&r->ranges, buf);367r = r->next;368}369}
370
371static void dump_diff_ranges(struct diff_ranges *diff, const char *desc)372{
373int i;374assert(diff->parent.nr == diff->target.nr);375printf("diff ranges %s (%d items):\n", desc, diff->parent.nr);376printf("\tparent\ttarget\n");377for (i = 0; i < diff->parent.nr; i++) {378printf("\t[%ld,%ld]\t[%ld,%ld]\n",379diff->parent.ranges[i].start,380diff->parent.ranges[i].end,381diff->target.ranges[i].start,382diff->target.ranges[i].end);383}384}
385#endif386
387
388static int ranges_overlap(struct range *a, struct range *b)389{
390return !(a->end <= b->start || b->end <= a->start);391}
392
393/*
394* Given a diff and the set of interesting ranges, determine all hunks
395* of the diff which touch (overlap) at least one of the interesting
396* ranges in the target.
397*/
398static void diff_ranges_filter_touched(struct diff_ranges *out,399struct diff_ranges *diff,400struct range_set *rs)401{
402unsigned int i, j = 0;403
404assert(out->target.nr == 0);405
406for (i = 0; i < diff->target.nr; i++) {407while (diff->target.ranges[i].start > rs->ranges[j].end) {408j++;409if (j == rs->nr)410return;411}412if (ranges_overlap(&diff->target.ranges[i], &rs->ranges[j])) {413range_set_append(&out->parent,414diff->parent.ranges[i].start,415diff->parent.ranges[i].end);416range_set_append(&out->target,417diff->target.ranges[i].start,418diff->target.ranges[i].end);419}420}421}
422
423/*
424* Adjust the line counts in 'rs' to account for the lines
425* added/removed in the diff.
426*/
427static void range_set_shift_diff(struct range_set *out,428struct range_set *rs,429struct diff_ranges *diff)430{
431unsigned int i, j = 0;432long offset = 0;433struct range *src = rs->ranges;434struct range *target = diff->target.ranges;435struct range *parent = diff->parent.ranges;436
437for (i = 0; i < rs->nr; i++) {438while (j < diff->target.nr && src[i].start >= target[j].start) {439offset += (parent[j].end-parent[j].start)440- (target[j].end-target[j].start);441j++;442}443range_set_append(out, src[i].start+offset, src[i].end+offset);444}445}
446
447/*
448* Given a diff and the set of interesting ranges, map the ranges
449* across the diff. That is: observe that the target commit takes
450* blame for all the + (target-side) ranges. So for every pair of
451* ranges in the diff that was touched, we remove the latter and add
452* its parent side.
453*/
454static void range_set_map_across_diff(struct range_set *out,455struct range_set *rs,456struct diff_ranges *diff,457struct diff_ranges **touched_out)458{
459struct diff_ranges *touched = xmalloc(sizeof(*touched));460struct range_set tmp1 = RANGE_SET_INIT;461struct range_set tmp2 = RANGE_SET_INIT;462
463diff_ranges_init(touched);464diff_ranges_filter_touched(touched, diff, rs);465range_set_difference(&tmp1, rs, &touched->target);466range_set_shift_diff(&tmp2, &tmp1, diff);467range_set_union(out, &tmp2, &touched->parent);468range_set_release(&tmp1);469range_set_release(&tmp2);470
471*touched_out = touched;472}
473
474static struct commit *check_single_commit(struct rev_info *revs)475{
476struct object *commit = NULL;477int found = -1;478int i;479
480for (i = 0; i < revs->pending.nr; i++) {481struct object *obj = revs->pending.objects[i].item;482if (obj->flags & UNINTERESTING)483continue;484obj = deref_tag(revs->repo, obj, NULL, 0);485if (!obj || obj->type != OBJ_COMMIT)486die("Non commit %s?", revs->pending.objects[i].name);487if (commit)488die("More than one commit to dig from: %s and %s?",489revs->pending.objects[i].name,490revs->pending.objects[found].name);491commit = obj;492found = i;493}494
495if (!commit)496die("No commit specified?");497
498return (struct commit *) commit;499}
500
501static void fill_blob_sha1(struct repository *r, struct commit *commit,502struct diff_filespec *spec)503{
504unsigned short mode;505struct object_id oid;506
507if (get_tree_entry(r, &commit->object.oid, spec->path, &oid, &mode))508die("There is no path %s in the commit", spec->path);509fill_filespec(spec, &oid, 1, mode);510
511return;512}
513
514static void fill_line_ends(struct repository *r,515struct diff_filespec *spec,516long *lines,517unsigned long **line_ends)518{
519int num = 0, size = 50;520long cur = 0;521unsigned long *ends = NULL;522char *data = NULL;523
524if (diff_populate_filespec(r, spec, NULL))525die("Cannot read blob %s", oid_to_hex(&spec->oid));526
527ALLOC_ARRAY(ends, size);528ends[cur++] = 0;529data = spec->data;530while (num < spec->size) {531if (data[num] == '\n' || num == spec->size - 1) {532ALLOC_GROW(ends, (cur + 1), size);533ends[cur++] = num;534}535num++;536}537
538/* shrink the array to fit the elements */539REALLOC_ARRAY(ends, cur);540*lines = cur-1;541*line_ends = ends;542}
543
544struct nth_line_cb {545struct diff_filespec *spec;546long lines;547unsigned long *line_ends;548};549
550static const char *nth_line(void *data, long line)551{
552struct nth_line_cb *d = data;553assert(d && line <= d->lines);554assert(d->spec && d->spec->data);555
556if (line == 0)557return (char *)d->spec->data;558else559return (char *)d->spec->data + d->line_ends[line] + 1;560}
561
562static struct line_log_data *563parse_lines(struct repository *r, struct commit *commit,564const char *prefix, struct string_list *args)565{
566long lines = 0;567unsigned long *ends = NULL;568struct nth_line_cb cb_data;569struct string_list_item *item;570struct line_log_data *ranges = NULL;571struct line_log_data *p;572
573for_each_string_list_item(item, args) {574const char *name_part, *range_part;575char *full_name;576struct diff_filespec *spec;577long begin = 0, end = 0;578long anchor;579
580name_part = skip_range_arg(item->string, r->index);581if (!name_part || *name_part != ':' || !name_part[1])582die("-L argument not 'start,end:file' or ':funcname:file': %s",583item->string);584range_part = xstrndup(item->string, name_part - item->string);585name_part++;586
587full_name = prefix_path(prefix, prefix ? strlen(prefix) : 0,588name_part);589
590spec = alloc_filespec(full_name);591fill_blob_sha1(r, commit, spec);592fill_line_ends(r, spec, &lines, &ends);593cb_data.spec = spec;594cb_data.lines = lines;595cb_data.line_ends = ends;596
597p = search_line_log_data(ranges, full_name, NULL);598if (p && p->ranges.nr)599anchor = p->ranges.ranges[p->ranges.nr - 1].end + 1;600else601anchor = 1;602
603if (parse_range_arg(range_part, nth_line, &cb_data,604lines, anchor, &begin, &end,605full_name, r->index))606die("malformed -L argument '%s'", range_part);607if ((!lines && (begin || end)) || lines < begin)608die("file %s has only %lu lines", name_part, lines);609if (begin < 1)610begin = 1;611if (end < 1 || lines < end)612end = lines;613begin--;614line_log_data_insert(&ranges, full_name, begin, end);615
616free_filespec(spec);617FREE_AND_NULL(ends);618}619
620for (p = ranges; p; p = p->next)621sort_and_merge_range_set(&p->ranges);622
623return ranges;624}
625
626static struct line_log_data *line_log_data_copy_one(struct line_log_data *r)627{
628struct line_log_data *ret = xmalloc(sizeof(*ret));629
630assert(r);631line_log_data_init(ret);632range_set_copy(&ret->ranges, &r->ranges);633
634ret->path = xstrdup(r->path);635
636return ret;637}
638
639static struct line_log_data *640line_log_data_copy(struct line_log_data *r)641{
642struct line_log_data *ret = NULL;643struct line_log_data *tmp = NULL, *prev = NULL;644
645assert(r);646ret = tmp = prev = line_log_data_copy_one(r);647r = r->next;648while (r) {649tmp = line_log_data_copy_one(r);650prev->next = tmp;651prev = tmp;652r = r->next;653}654
655return ret;656}
657
658/* merge two range sets across files */
659static struct line_log_data *line_log_data_merge(struct line_log_data *a,660struct line_log_data *b)661{
662struct line_log_data *head = NULL, **pp = &head;663
664while (a || b) {665struct line_log_data *src;666struct line_log_data *src2 = NULL;667struct line_log_data *d;668int cmp;669if (!a)670cmp = 1;671else if (!b)672cmp = -1;673else674cmp = strcmp(a->path, b->path);675if (cmp < 0) {676src = a;677a = a->next;678} else if (cmp == 0) {679src = a;680a = a->next;681src2 = b;682b = b->next;683} else {684src = b;685b = b->next;686}687d = xmalloc(sizeof(struct line_log_data));688line_log_data_init(d);689d->path = xstrdup(src->path);690*pp = d;691pp = &d->next;692if (src2)693range_set_union(&d->ranges, &src->ranges, &src2->ranges);694else695range_set_copy(&d->ranges, &src->ranges);696}697
698return head;699}
700
701static void add_line_range(struct rev_info *revs, struct commit *commit,702struct line_log_data *range)703{
704struct line_log_data *old_line = NULL;705struct line_log_data *new_line = NULL;706
707old_line = lookup_decoration(&revs->line_log_data, &commit->object);708if (old_line && range) {709new_line = line_log_data_merge(old_line, range);710free_line_log_data(old_line);711} else if (range)712new_line = line_log_data_copy(range);713
714if (new_line)715add_decoration(&revs->line_log_data, &commit->object, new_line);716}
717
718static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)719{
720struct line_log_data *r;721r = lookup_decoration(&revs->line_log_data, &commit->object);722if (!r)723return;724free_line_log_data(r);725add_decoration(&revs->line_log_data, &commit->object, NULL);726}
727
728static struct line_log_data *lookup_line_range(struct rev_info *revs,729struct commit *commit)730{
731struct line_log_data *ret = NULL;732struct line_log_data *d;733
734ret = lookup_decoration(&revs->line_log_data, &commit->object);735
736for (d = ret; d; d = d->next)737range_set_check_invariants(&d->ranges);738
739return ret;740}
741
742static int same_paths_in_pathspec_and_range(struct pathspec *pathspec,743struct line_log_data *range)744{
745int i;746struct line_log_data *r;747
748for (i = 0, r = range; i < pathspec->nr && r; i++, r = r->next)749if (strcmp(pathspec->items[i].match, r->path))750return 0;751if (i < pathspec->nr || r)752/* different number of pathspec items and ranges */753return 0;754
755return 1;756}
757
758static void parse_pathspec_from_ranges(struct pathspec *pathspec,759struct line_log_data *range)760{
761struct line_log_data *r;762struct strvec array = STRVEC_INIT;763const char **paths;764
765for (r = range; r; r = r->next)766strvec_push(&array, r->path);767paths = strvec_detach(&array);768
769parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL, "", paths);770/* strings are now owned by pathspec */771free(paths);772}
773
774void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args)775{
776struct commit *commit = NULL;777struct line_log_data *range;778
779commit = check_single_commit(rev);780range = parse_lines(rev->diffopt.repo, commit, prefix, args);781add_line_range(rev, commit, range);782
783parse_pathspec_from_ranges(&rev->diffopt.pathspec, range);784}
785
786static void move_diff_queue(struct diff_queue_struct *dst,787struct diff_queue_struct *src)788{
789assert(src != dst);790memcpy(dst, src, sizeof(struct diff_queue_struct));791DIFF_QUEUE_CLEAR(src);792}
793
794static void filter_diffs_for_paths(struct line_log_data *range, int keep_deletions)795{
796int i;797struct diff_queue_struct outq;798DIFF_QUEUE_CLEAR(&outq);799
800for (i = 0; i < diff_queued_diff.nr; i++) {801struct diff_filepair *p = diff_queued_diff.queue[i];802struct line_log_data *rg = NULL;803
804if (!DIFF_FILE_VALID(p->two)) {805if (keep_deletions)806diff_q(&outq, p);807else808diff_free_filepair(p);809continue;810}811for (rg = range; rg; rg = rg->next) {812if (!strcmp(rg->path, p->two->path))813break;814}815if (rg)816diff_q(&outq, p);817else818diff_free_filepair(p);819}820free(diff_queued_diff.queue);821diff_queued_diff = outq;822}
823
824static inline int diff_might_be_rename(void)825{
826int i;827for (i = 0; i < diff_queued_diff.nr; i++)828if (!DIFF_FILE_VALID(diff_queued_diff.queue[i]->one)) {829/* fprintf(stderr, "diff_might_be_rename found creation of: %s\n", */830/* diff_queued_diff.queue[i]->two->path); */831return 1;832}833return 0;834}
835
836static void queue_diffs(struct line_log_data *range,837struct diff_options *opt,838struct diff_queue_struct *queue,839struct commit *commit, struct commit *parent)840{
841struct object_id *tree_oid, *parent_tree_oid;842
843assert(commit);844
845tree_oid = get_commit_tree_oid(commit);846parent_tree_oid = parent ? get_commit_tree_oid(parent) : NULL;847
848if (opt->detect_rename &&849!same_paths_in_pathspec_and_range(&opt->pathspec, range)) {850clear_pathspec(&opt->pathspec);851parse_pathspec_from_ranges(&opt->pathspec, range);852}853DIFF_QUEUE_CLEAR(&diff_queued_diff);854diff_tree_oid(parent_tree_oid, tree_oid, "", opt);855if (opt->detect_rename && diff_might_be_rename()) {856/* must look at the full tree diff to detect renames */857clear_pathspec(&opt->pathspec);858DIFF_QUEUE_CLEAR(&diff_queued_diff);859
860diff_tree_oid(parent_tree_oid, tree_oid, "", opt);861
862filter_diffs_for_paths(range, 1);863diffcore_std(opt);864filter_diffs_for_paths(range, 0);865}866move_diff_queue(queue, &diff_queued_diff);867}
868
869static char *get_nth_line(long line, unsigned long *ends, void *data)870{
871if (line == 0)872return (char *)data;873else874return (char *)data + ends[line] + 1;875}
876
877static void print_line(const char *prefix, char first,878long line, unsigned long *ends, void *data,879const char *color, const char *reset, FILE *file)880{
881char *begin = get_nth_line(line, ends, data);882char *end = get_nth_line(line+1, ends, data);883int had_nl = 0;884
885if (end > begin && end[-1] == '\n') {886end--;887had_nl = 1;888}889
890fputs(prefix, file);891fputs(color, file);892putc(first, file);893fwrite(begin, 1, end-begin, file);894fputs(reset, file);895putc('\n', file);896if (!had_nl)897fputs("\\ No newline at end of file\n", file);898}
899
900static char *output_prefix(struct diff_options *opt)901{
902if (opt->output_prefix) {903struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);904return sb->buf;905} else {906return xstrdup("");907}908}
909
910static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)911{
912unsigned int i, j = 0;913long p_lines, t_lines;914unsigned long *p_ends = NULL, *t_ends = NULL;915struct diff_filepair *pair = range->pair;916struct diff_ranges *diff = &range->diff;917
918struct diff_options *opt = &rev->diffopt;919char *prefix = output_prefix(opt);920const char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);921const char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);922const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);923const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);924const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);925const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);926
927if (!pair || !diff)928goto out;929
930if (pair->one->oid_valid)931fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);932fill_line_ends(rev->diffopt.repo, pair->two, &t_lines, &t_ends);933
934fprintf(opt->file, "%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);935fprintf(opt->file, "%s%s--- %s%s%s\n", prefix, c_meta,936pair->one->oid_valid ? "a/" : "",937pair->one->oid_valid ? pair->one->path : "/dev/null",938c_reset);939fprintf(opt->file, "%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset);940for (i = 0; i < range->ranges.nr; i++) {941long p_start, p_end;942long t_start = range->ranges.ranges[i].start;943long t_end = range->ranges.ranges[i].end;944long t_cur = t_start;945unsigned int j_last;946
947while (j < diff->target.nr && diff->target.ranges[j].end < t_start)948j++;949if (j == diff->target.nr || diff->target.ranges[j].start > t_end)950continue;951
952/* Scan ahead to determine the last diff that falls in this range */953j_last = j;954while (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)955j_last++;956if (j_last > j)957j_last--;958
959/*960* Compute parent hunk headers: we know that the diff
961* has the correct line numbers (but not all hunks).
962* So it suffices to shift the start/end according to
963* the line numbers of the first/last hunk(s) that
964* fall in this range.
965*/
966if (t_start < diff->target.ranges[j].start)967p_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);968else969p_start = diff->parent.ranges[j].start;970if (t_end > diff->target.ranges[j_last].end)971p_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);972else973p_end = diff->parent.ranges[j_last].end;974
975if (!p_start && !p_end) {976p_start = -1;977p_end = -1;978}979
980/* Now output a diff hunk for this range */981fprintf(opt->file, "%s%s@@ -%ld,%ld +%ld,%ld @@%s\n",982prefix, c_frag,983p_start+1, p_end-p_start, t_start+1, t_end-t_start,984c_reset);985while (j < diff->target.nr && diff->target.ranges[j].start < t_end) {986int k;987for (; t_cur < diff->target.ranges[j].start; t_cur++)988print_line(prefix, ' ', t_cur, t_ends, pair->two->data,989c_context, c_reset, opt->file);990for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)991print_line(prefix, '-', k, p_ends, pair->one->data,992c_old, c_reset, opt->file);993for (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++)994print_line(prefix, '+', t_cur, t_ends, pair->two->data,995c_new, c_reset, opt->file);996j++;997}998for (; t_cur < t_end; t_cur++)999print_line(prefix, ' ', t_cur, t_ends, pair->two->data,1000c_context, c_reset, opt->file);1001}1002
1003out:1004free(p_ends);1005free(t_ends);1006free(prefix);1007}
1008
1009/*
1010* NEEDSWORK: manually building a diff here is not the Right
1011* Thing(tm). log -L should be built into the diff pipeline.
1012*/
1013static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)1014{
1015char *prefix = output_prefix(&rev->diffopt);1016
1017fprintf(rev->diffopt.file, "%s\n", prefix);1018free(prefix);1019
1020while (range) {1021dump_diff_hacky_one(rev, range);1022range = range->next;1023}1024}
1025
1026/*
1027* Unlike most other functions, this destructively operates on
1028* 'range'.
1029*/
1030static int process_diff_filepair(struct rev_info *rev,1031struct diff_filepair *pair,1032struct line_log_data *range,1033struct diff_ranges **diff_out)1034{
1035struct line_log_data *rg = range;1036struct range_set tmp;1037struct diff_ranges diff;1038mmfile_t file_parent, file_target;1039char *parent_data_to_free = NULL;1040
1041assert(pair->two->path);1042while (rg) {1043assert(rg->path);1044if (!strcmp(rg->path, pair->two->path))1045break;1046rg = rg->next;1047}1048
1049if (!rg)1050return 0;1051if (rg->ranges.nr == 0)1052return 0;1053
1054assert(pair->two->oid_valid);1055diff_populate_filespec(rev->diffopt.repo, pair->two, NULL);1056file_target.ptr = pair->two->data;1057file_target.size = pair->two->size;1058
1059if (pair->one->oid_valid) {1060diff_populate_filespec(rev->diffopt.repo, pair->one, NULL);1061file_parent.ptr = pair->one->data;1062file_parent.size = pair->one->size;1063} else {1064file_parent.ptr = parent_data_to_free = xstrdup("");1065file_parent.size = 0;1066}1067
1068diff_ranges_init(&diff);1069if (collect_diff(&file_parent, &file_target, &diff))1070die("unable to generate diff for %s", pair->one->path);1071
1072/* NEEDSWORK should apply some heuristics to prevent mismatches */1073free(rg->path);1074rg->path = xstrdup(pair->one->path);1075
1076range_set_init(&tmp, 0);1077range_set_map_across_diff(&tmp, &rg->ranges, &diff, diff_out);1078range_set_release(&rg->ranges);1079range_set_move(&rg->ranges, &tmp);1080
1081diff_ranges_release(&diff);1082
1083free(parent_data_to_free);1084return ((*diff_out)->parent.nr > 0);1085}
1086
1087static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)1088{
1089struct diff_filepair *new_filepair = xmalloc(sizeof(struct diff_filepair));1090new_filepair->one = pair->one;1091new_filepair->two = pair->two;1092new_filepair->one->count++;1093new_filepair->two->count++;1094return new_filepair;1095}
1096
1097static void free_diffqueues(int n, struct diff_queue_struct *dq)1098{
1099for (int i = 0; i < n; i++)1100diff_free_queue(&dq[i]);1101free(dq);1102}
1103
1104static int process_all_files(struct line_log_data **range_out,1105struct rev_info *rev,1106struct diff_queue_struct *queue,1107struct line_log_data *range)1108{
1109int i, changed = 0;1110
1111*range_out = line_log_data_copy(range);1112
1113for (i = 0; i < queue->nr; i++) {1114struct diff_ranges *pairdiff = NULL;1115struct diff_filepair *pair = queue->queue[i];1116if (process_diff_filepair(rev, pair, *range_out, &pairdiff)) {1117/*1118* Store away the diff for later output. We
1119* tuck it in the ranges we got as _input_,
1120* since that's the commit that caused the
1121* diff.
1122*
1123* NEEDSWORK not enough when we get around to
1124* doing something interesting with merges;
1125* currently each invocation on a merge parent
1126* trashes the previous one's diff.
1127*
1128* NEEDSWORK tramples over data structures not owned here
1129*/
1130struct line_log_data *rg = range;1131changed++;1132while (rg && strcmp(rg->path, pair->two->path))1133rg = rg->next;1134assert(rg);1135rg->pair = diff_filepair_dup(queue->queue[i]);1136memcpy(&rg->diff, pairdiff, sizeof(struct diff_ranges));1137}1138free(pairdiff);1139}1140
1141return changed;1142}
1143
1144int line_log_print(struct rev_info *rev, struct commit *commit)1145{
1146
1147show_log(rev);1148if (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) {1149struct line_log_data *range = lookup_line_range(rev, commit);1150dump_diff_hacky(rev, range);1151}1152return 1;1153}
1154
1155static int bloom_filter_check(struct rev_info *rev,1156struct commit *commit,1157struct line_log_data *range)1158{
1159struct bloom_filter *filter;1160struct bloom_key key;1161int result = 0;1162
1163if (!commit->parents)1164return 1;1165
1166if (!rev->bloom_filter_settings ||1167!(filter = get_bloom_filter(rev->repo, commit)))1168return 1;1169
1170if (!range)1171return 0;1172
1173while (!result && range) {1174fill_bloom_key(range->path, strlen(range->path), &key, rev->bloom_filter_settings);1175
1176if (bloom_filter_contains(filter, &key, rev->bloom_filter_settings))1177result = 1;1178
1179clear_bloom_key(&key);1180range = range->next;1181}1182
1183return result;1184}
1185
1186static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit,1187struct line_log_data *range)1188{
1189struct commit *parent = NULL;1190struct diff_queue_struct queue;1191struct line_log_data *parent_range;1192int changed;1193
1194if (commit->parents)1195parent = commit->parents->item;1196
1197queue_diffs(range, &rev->diffopt, &queue, commit, parent);1198changed = process_all_files(&parent_range, rev, &queue, range);1199
1200if (parent)1201add_line_range(rev, parent, parent_range);1202free_line_log_data(parent_range);1203diff_free_queue(&queue);1204return changed;1205}
1206
1207static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit,1208struct line_log_data *range)1209{
1210struct diff_queue_struct *diffqueues;1211struct line_log_data **cand;1212struct commit **parents;1213struct commit_list *p;1214int i;1215int nparents = commit_list_count(commit->parents);1216
1217if (nparents > 1 && rev->first_parent_only)1218nparents = 1;1219
1220ALLOC_ARRAY(diffqueues, nparents);1221ALLOC_ARRAY(cand, nparents);1222ALLOC_ARRAY(parents, nparents);1223
1224p = commit->parents;1225for (i = 0; i < nparents; i++) {1226parents[i] = p->item;1227p = p->next;1228queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]);1229}1230
1231for (i = 0; i < nparents; i++) {1232int changed;1233cand[i] = NULL;1234changed = process_all_files(&cand[i], rev, &diffqueues[i], range);1235if (!changed) {1236/*1237* This parent can take all the blame, so we
1238* don't follow any other path in history
1239*/
1240add_line_range(rev, parents[i], cand[i]);1241clear_commit_line_range(rev, commit);1242commit_list_append(parents[i], &commit->parents);1243free(parents);1244free(cand);1245free_diffqueues(nparents, diffqueues);1246/* NEEDSWORK leaking like a sieve */1247return 0;1248}1249}1250
1251/*1252* No single parent took the blame. We add the candidates
1253* from the above loop to the parents.
1254*/
1255for (i = 0; i < nparents; i++) {1256add_line_range(rev, parents[i], cand[i]);1257}1258
1259clear_commit_line_range(rev, commit);1260free(parents);1261free(cand);1262free_diffqueues(nparents, diffqueues);1263return 1;1264
1265/* NEEDSWORK evil merge detection stuff */1266/* NEEDSWORK leaking like a sieve */1267}
1268
1269int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)1270{
1271struct line_log_data *range = lookup_line_range(rev, commit);1272int changed = 0;1273
1274if (range) {1275if (commit->parents && !bloom_filter_check(rev, commit, range)) {1276struct line_log_data *prange = line_log_data_copy(range);1277add_line_range(rev, commit->parents->item, prange);1278clear_commit_line_range(rev, commit);1279} else if (!commit->parents || !commit->parents->next)1280changed = process_ranges_ordinary_commit(rev, commit, range);1281else1282changed = process_ranges_merge_commit(rev, commit, range);1283}1284
1285if (!changed)1286commit->object.flags |= TREESAME;1287
1288return changed;1289}
1290
1291static enum rewrite_result line_log_rewrite_one(struct rev_info *rev UNUSED,1292struct commit **pp)1293{
1294for (;;) {1295struct commit *p = *pp;1296if (p->parents && p->parents->next)1297return rewrite_one_ok;1298if (p->object.flags & UNINTERESTING)1299return rewrite_one_ok;1300if (!(p->object.flags & TREESAME))1301return rewrite_one_ok;1302if (!p->parents)1303return rewrite_one_noparents;1304*pp = p->parents->item;1305}1306}
1307
1308int line_log_filter(struct rev_info *rev)1309{
1310struct commit *commit;1311struct commit_list *list = rev->commits;1312struct commit_list *out = NULL, **pp = &out;1313
1314while (list) {1315struct commit_list *to_free = NULL;1316commit = list->item;1317if (line_log_process_ranges_arbitrary_commit(rev, commit)) {1318*pp = list;1319pp = &list->next;1320} else1321to_free = list;1322list = list->next;1323free(to_free);1324}1325*pp = NULL;1326
1327for (list = out; list; list = list->next)1328rewrite_parents(rev, list->item, line_log_rewrite_one);1329
1330rev->commits = out;1331
1332return 0;1333}
1334
1335static void free_void_line_log_data(void *data)1336{
1337free_line_log_data(data);1338}
1339
1340void line_log_free(struct rev_info *rev)1341{
1342clear_decoration(&rev->line_log_data, free_void_line_log_data);1343}
1344