git
/
combine-diff.c
1668 строк · 43.4 Кб
1#define USE_THE_REPOSITORY_VARIABLE2
3#include "git-compat-util.h"4#include "object-store-ll.h"5#include "commit.h"6#include "convert.h"7#include "diff.h"8#include "diffcore.h"9#include "environment.h"10#include "hex.h"11#include "object-name.h"12#include "quote.h"13#include "xdiff-interface.h"14#include "xdiff/xmacros.h"15#include "log-tree.h"16#include "refs.h"17#include "tree.h"18#include "userdiff.h"19#include "oid-array.h"20#include "revision.h"21
22static int compare_paths(const struct combine_diff_path *one,23const struct diff_filespec *two)24{
25if (!S_ISDIR(one->mode) && !S_ISDIR(two->mode))26return strcmp(one->path, two->path);27
28return base_name_compare(one->path, strlen(one->path), one->mode,29two->path, strlen(two->path), two->mode);30}
31
32static int filename_changed(char status)33{
34return status == 'R' || status == 'C';35}
36
37static struct combine_diff_path *intersect_paths(38struct combine_diff_path *curr,39int n,40int num_parent,41int combined_all_paths)42{
43struct diff_queue_struct *q = &diff_queued_diff;44struct combine_diff_path *p, **tail = &curr;45int i, j, cmp;46
47if (!n) {48for (i = 0; i < q->nr; i++) {49int len;50const char *path;51if (diff_unmodified_pair(q->queue[i]))52continue;53path = q->queue[i]->two->path;54len = strlen(path);55p = xmalloc(combine_diff_path_size(num_parent, len));56p->path = (char *) &(p->parent[num_parent]);57memcpy(p->path, path, len);58p->path[len] = 0;59p->next = NULL;60memset(p->parent, 0,61sizeof(p->parent[0]) * num_parent);62
63oidcpy(&p->oid, &q->queue[i]->two->oid);64p->mode = q->queue[i]->two->mode;65oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);66p->parent[n].mode = q->queue[i]->one->mode;67p->parent[n].status = q->queue[i]->status;68
69if (combined_all_paths &&70filename_changed(p->parent[n].status)) {71strbuf_init(&p->parent[n].path, 0);72strbuf_addstr(&p->parent[n].path,73q->queue[i]->one->path);74}75*tail = p;76tail = &p->next;77}78return curr;79}80
81/*82* paths in curr (linked list) and q->queue[] (array) are
83* both sorted in the tree order.
84*/
85i = 0;86while ((p = *tail) != NULL) {87cmp = ((i >= q->nr)88? -1 : compare_paths(p, q->queue[i]->two));89
90if (cmp < 0) {91/* p->path not in q->queue[]; drop it */92*tail = p->next;93for (j = 0; j < num_parent; j++)94if (combined_all_paths &&95filename_changed(p->parent[j].status))96strbuf_release(&p->parent[j].path);97free(p);98continue;99}100
101if (cmp > 0) {102/* q->queue[i] not in p->path; skip it */103i++;104continue;105}106
107oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);108p->parent[n].mode = q->queue[i]->one->mode;109p->parent[n].status = q->queue[i]->status;110if (combined_all_paths &&111filename_changed(p->parent[n].status))112strbuf_addstr(&p->parent[n].path,113q->queue[i]->one->path);114
115tail = &p->next;116i++;117}118return curr;119}
120
121/* Lines lost from parent */
122struct lline {123struct lline *next, *prev;124int len;125unsigned long parent_map;126char line[FLEX_ARRAY];127};128
129/* Lines lost from current parent (before coalescing) */
130struct plost {131struct lline *lost_head, *lost_tail;132int len;133};134
135/* Lines surviving in the merge result */
136struct sline {137/* Accumulated and coalesced lost lines */138struct lline *lost;139int lenlost;140struct plost plost;141char *bol;142int len;143/* bit 0 up to (N-1) are on if the parent has this line (i.e.144* we did not change it).
145* bit N is used for "interesting" lines, including context.
146* bit (N+1) is used for "do not show deletion before this".
147*/
148unsigned long flag;149unsigned long *p_lno;150};151
152static int match_string_spaces(const char *line1, int len1,153const char *line2, int len2,154long flags)155{
156if (flags & XDF_WHITESPACE_FLAGS) {157for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--);158for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--);159}160
161if (!(flags & (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE)))162return (len1 == len2 && !memcmp(line1, line2, len1));163
164while (len1 > 0 && len2 > 0) {165len1--;166len2--;167if (XDL_ISSPACE(line1[len1]) || XDL_ISSPACE(line2[len2])) {168if ((flags & XDF_IGNORE_WHITESPACE_CHANGE) &&169(!XDL_ISSPACE(line1[len1]) || !XDL_ISSPACE(line2[len2])))170return 0;171
172for (; len1 > 0 && XDL_ISSPACE(line1[len1]); len1--);173for (; len2 > 0 && XDL_ISSPACE(line2[len2]); len2--);174}175if (line1[len1] != line2[len2])176return 0;177}178
179if (flags & XDF_IGNORE_WHITESPACE) {180/* Consume remaining spaces */181for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--);182for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--);183}184
185/* We matched full line1 and line2 */186if (!len1 && !len2)187return 1;188
189return 0;190}
191
192enum coalesce_direction { MATCH, BASE, NEW };193
194/* Coalesce new lines into base by finding LCS */
195static struct lline *coalesce_lines(struct lline *base, int *lenbase,196struct lline *newline, int lennew,197unsigned long parent, long flags)198{
199int **lcs;200enum coalesce_direction **directions;201struct lline *baseend, *newend = NULL;202int i, j, origbaselen = *lenbase;203
204if (!newline)205return base;206
207if (!base) {208*lenbase = lennew;209return newline;210}211
212/*213* Coalesce new lines into base by finding the LCS
214* - Create the table to run dynamic programming
215* - Compute the LCS
216* - Then reverse read the direction structure:
217* - If we have MATCH, assign parent to base flag, and consume
218* both baseend and newend
219* - Else if we have BASE, consume baseend
220* - Else if we have NEW, insert newend lline into base and
221* consume newend
222*/
223CALLOC_ARRAY(lcs, st_add(origbaselen, 1));224CALLOC_ARRAY(directions, st_add(origbaselen, 1));225for (i = 0; i < origbaselen + 1; i++) {226CALLOC_ARRAY(lcs[i], st_add(lennew, 1));227CALLOC_ARRAY(directions[i], st_add(lennew, 1));228directions[i][0] = BASE;229}230for (j = 1; j < lennew + 1; j++)231directions[0][j] = NEW;232
233for (i = 1, baseend = base; i < origbaselen + 1; i++) {234for (j = 1, newend = newline; j < lennew + 1; j++) {235if (match_string_spaces(baseend->line, baseend->len,236newend->line, newend->len, flags)) {237lcs[i][j] = lcs[i - 1][j - 1] + 1;238directions[i][j] = MATCH;239} else if (lcs[i][j - 1] >= lcs[i - 1][j]) {240lcs[i][j] = lcs[i][j - 1];241directions[i][j] = NEW;242} else {243lcs[i][j] = lcs[i - 1][j];244directions[i][j] = BASE;245}246if (newend->next)247newend = newend->next;248}249if (baseend->next)250baseend = baseend->next;251}252
253for (i = 0; i < origbaselen + 1; i++)254free(lcs[i]);255free(lcs);256
257/* At this point, baseend and newend point to the end of each lists */258i--;259j--;260while (i != 0 || j != 0) {261if (directions[i][j] == MATCH) {262baseend->parent_map |= 1<<parent;263baseend = baseend->prev;264newend = newend->prev;265i--;266j--;267} else if (directions[i][j] == NEW) {268struct lline *lline;269
270lline = newend;271/* Remove lline from new list and update newend */272if (lline->prev)273lline->prev->next = lline->next;274else275newline = lline->next;276if (lline->next)277lline->next->prev = lline->prev;278
279newend = lline->prev;280j--;281
282/* Add lline to base list */283if (baseend) {284lline->next = baseend->next;285lline->prev = baseend;286if (lline->prev)287lline->prev->next = lline;288}289else {290lline->next = base;291base = lline;292}293(*lenbase)++;294
295if (lline->next)296lline->next->prev = lline;297
298} else {299baseend = baseend->prev;300i--;301}302}303
304newend = newline;305while (newend) {306struct lline *lline = newend;307newend = newend->next;308free(lline);309}310
311for (i = 0; i < origbaselen + 1; i++)312free(directions[i]);313free(directions);314
315return base;316}
317
318static char *grab_blob(struct repository *r,319const struct object_id *oid, unsigned int mode,320unsigned long *size, struct userdiff_driver *textconv,321const char *path)322{
323char *blob;324enum object_type type;325
326if (S_ISGITLINK(mode)) {327struct strbuf buf = STRBUF_INIT;328strbuf_addf(&buf, "Subproject commit %s\n", oid_to_hex(oid));329*size = buf.len;330blob = strbuf_detach(&buf, NULL);331} else if (is_null_oid(oid)) {332/* deleted blob */333*size = 0;334return xcalloc(1, 1);335} else if (textconv) {336struct diff_filespec *df = alloc_filespec(path);337fill_filespec(df, oid, 1, mode);338*size = fill_textconv(r, textconv, df, &blob);339free_filespec(df);340} else {341blob = repo_read_object_file(r, oid, &type, size);342if (!blob)343die(_("unable to read %s"), oid_to_hex(oid));344if (type != OBJ_BLOB)345die("object '%s' is not a blob!", oid_to_hex(oid));346}347return blob;348}
349
350static void append_lost(struct sline *sline, int n, const char *line, int len)351{
352struct lline *lline;353unsigned long this_mask = (1UL<<n);354if (line[len-1] == '\n')355len--;356
357FLEX_ALLOC_MEM(lline, line, line, len);358lline->len = len;359lline->next = NULL;360lline->prev = sline->plost.lost_tail;361if (lline->prev)362lline->prev->next = lline;363else364sline->plost.lost_head = lline;365sline->plost.lost_tail = lline;366sline->plost.len++;367lline->parent_map = this_mask;368}
369
370struct combine_diff_state {371unsigned int lno;372int ob, on, nb, nn;373unsigned long nmask;374int num_parent;375int n;376struct sline *sline;377struct sline *lost_bucket;378};379
380static void consume_hunk(void *state_,381long ob, long on,382long nb, long nn,383const char *func UNUSED, long funclen UNUSED)384{
385struct combine_diff_state *state = state_;386
387state->ob = ob;388state->on = on;389state->nb = nb;390state->nn = nn;391state->lno = state->nb;392if (state->nn == 0) {393/* @@ -X,Y +N,0 @@ removed Y lines394* that would have come *after* line N
395* in the result. Our lost buckets hang
396* to the line after the removed lines,
397*
398* Note that this is correct even when N == 0,
399* in which case the hunk removes the first
400* line in the file.
401*/
402state->lost_bucket = &state->sline[state->nb];403if (!state->nb)404state->nb = 1;405} else {406state->lost_bucket = &state->sline[state->nb-1];407}408if (!state->sline[state->nb-1].p_lno)409CALLOC_ARRAY(state->sline[state->nb - 1].p_lno,410state->num_parent);411state->sline[state->nb-1].p_lno[state->n] = state->ob;412}
413
414static int consume_line(void *state_, char *line, unsigned long len)415{
416struct combine_diff_state *state = state_;417if (!state->lost_bucket)418return 0; /* not in any hunk yet */419switch (line[0]) {420case '-':421append_lost(state->lost_bucket, state->n, line+1, len-1);422break;423case '+':424state->sline[state->lno-1].flag |= state->nmask;425state->lno++;426break;427}428return 0;429}
430
431static void combine_diff(struct repository *r,432const struct object_id *parent, unsigned int mode,433mmfile_t *result_file,434struct sline *sline, unsigned int cnt, int n,435int num_parent, int result_deleted,436struct userdiff_driver *textconv,437const char *path, long flags)438{
439unsigned int p_lno, lno;440unsigned long nmask = (1UL << n);441xpparam_t xpp;442xdemitconf_t xecfg;443mmfile_t parent_file;444struct combine_diff_state state;445unsigned long sz;446
447if (result_deleted)448return; /* result deleted */449
450parent_file.ptr = grab_blob(r, parent, mode, &sz, textconv, path);451parent_file.size = sz;452memset(&xpp, 0, sizeof(xpp));453xpp.flags = flags;454memset(&xecfg, 0, sizeof(xecfg));455memset(&state, 0, sizeof(state));456state.nmask = nmask;457state.sline = sline;458state.lno = 1;459state.num_parent = num_parent;460state.n = n;461
462if (xdi_diff_outf(&parent_file, result_file, consume_hunk,463consume_line, &state, &xpp, &xecfg))464die("unable to generate combined diff for %s",465oid_to_hex(parent));466free(parent_file.ptr);467
468/* Assign line numbers for this parent.469*
470* sline[lno].p_lno[n] records the first line number
471* (counting from 1) for parent N if the final hunk display
472* started by showing sline[lno] (possibly showing the lost
473* lines attached to it first).
474*/
475for (lno = 0, p_lno = 1; lno <= cnt; lno++) {476struct lline *ll;477sline[lno].p_lno[n] = p_lno;478
479/* Coalesce new lines */480if (sline[lno].plost.lost_head) {481struct sline *sl = &sline[lno];482sl->lost = coalesce_lines(sl->lost, &sl->lenlost,483sl->plost.lost_head,484sl->plost.len, n, flags);485sl->plost.lost_head = sl->plost.lost_tail = NULL;486sl->plost.len = 0;487}488
489/* How many lines would this sline advance the p_lno? */490ll = sline[lno].lost;491while (ll) {492if (ll->parent_map & nmask)493p_lno++; /* '-' means parent had it */494ll = ll->next;495}496if (lno < cnt && !(sline[lno].flag & nmask))497p_lno++; /* no '+' means parent had it */498}499sline[lno].p_lno[n] = p_lno; /* trailer */500}
501
502static unsigned long context = 3;503static char combine_marker = '@';504
505static int interesting(struct sline *sline, unsigned long all_mask)506{
507/* If some parents lost lines here, or if we have added to508* some parent, it is interesting.
509*/
510return ((sline->flag & all_mask) || sline->lost);511}
512
513static unsigned long adjust_hunk_tail(struct sline *sline,514unsigned long all_mask,515unsigned long hunk_begin,516unsigned long i)517{
518/* i points at the first uninteresting line. If the last line519* of the hunk was interesting only because it has some
520* deletion, then it is not all that interesting for the
521* purpose of giving trailing context lines. This is because
522* we output '-' line and then unmodified sline[i-1] itself in
523* that case which gives us one extra context line.
524*/
525if ((hunk_begin + 1 <= i) && !(sline[i-1].flag & all_mask))526i--;527return i;528}
529
530static unsigned long find_next(struct sline *sline,531unsigned long mark,532unsigned long i,533unsigned long cnt,534int look_for_uninteresting)535{
536/* We have examined up to i-1 and are about to look at i.537* Find next interesting or uninteresting line. Here,
538* "interesting" does not mean interesting(), but marked by
539* the give_context() function below (i.e. it includes context
540* lines that are not interesting to interesting() function
541* that are surrounded by interesting() ones.
542*/
543while (i <= cnt)544if (look_for_uninteresting545? !(sline[i].flag & mark)546: (sline[i].flag & mark))547return i;548else549i++;550return i;551}
552
553static int give_context(struct sline *sline, unsigned long cnt, int num_parent)554{
555unsigned long all_mask = (1UL<<num_parent) - 1;556unsigned long mark = (1UL<<num_parent);557unsigned long no_pre_delete = (2UL<<num_parent);558unsigned long i;559
560/* Two groups of interesting lines may have a short gap of561* uninteresting lines. Connect such groups to give them a
562* bit of context.
563*
564* We first start from what the interesting() function says,
565* and mark them with "mark", and paint context lines with the
566* mark. So interesting() would still say false for such context
567* lines but they are treated as "interesting" in the end.
568*/
569i = find_next(sline, mark, 0, cnt, 0);570if (cnt < i)571return 0;572
573while (i <= cnt) {574unsigned long j = (context < i) ? (i - context) : 0;575unsigned long k;576
577/* Paint a few lines before the first interesting line. */578while (j < i) {579if (!(sline[j].flag & mark))580sline[j].flag |= no_pre_delete;581sline[j++].flag |= mark;582}583
584again:585/* we know up to i is to be included. where does the586* next uninteresting one start?
587*/
588j = find_next(sline, mark, i, cnt, 1);589if (cnt < j)590break; /* the rest are all interesting */591
592/* lookahead context lines */593k = find_next(sline, mark, j, cnt, 0);594j = adjust_hunk_tail(sline, all_mask, i, j);595
596if (k < j + context) {597/* k is interesting and [j,k) are not, but598* paint them interesting because the gap is small.
599*/
600while (j < k)601sline[j++].flag |= mark;602i = k;603goto again;604}605
606/* j is the first uninteresting line and there is607* no overlap beyond it within context lines. Paint
608* the trailing edge a bit.
609*/
610i = k;611k = (j + context < cnt+1) ? j + context : cnt+1;612while (j < k)613sline[j++].flag |= mark;614}615return 1;616}
617
618static int make_hunks(struct sline *sline, unsigned long cnt,619int num_parent, int dense)620{
621unsigned long all_mask = (1UL<<num_parent) - 1;622unsigned long mark = (1UL<<num_parent);623unsigned long i;624int has_interesting = 0;625
626for (i = 0; i <= cnt; i++) {627if (interesting(&sline[i], all_mask))628sline[i].flag |= mark;629else630sline[i].flag &= ~mark;631}632if (!dense)633return give_context(sline, cnt, num_parent);634
635/* Look at each hunk, and if we have changes from only one636* parent, or the changes are the same from all but one
637* parent, mark that uninteresting.
638*/
639i = 0;640while (i <= cnt) {641unsigned long j, hunk_begin, hunk_end;642unsigned long same_diff;643while (i <= cnt && !(sline[i].flag & mark))644i++;645if (cnt < i)646break; /* No more interesting hunks */647hunk_begin = i;648for (j = i + 1; j <= cnt; j++) {649if (!(sline[j].flag & mark)) {650/* Look beyond the end to see if there651* is an interesting line after this
652* hunk within context span.
653*/
654unsigned long la; /* lookahead */655int contin = 0;656la = adjust_hunk_tail(sline, all_mask,657hunk_begin, j);658la = (la + context < cnt + 1) ?659(la + context) : cnt + 1;660while (la && j <= --la) {661if (sline[la].flag & mark) {662contin = 1;663break;664}665}666if (!contin)667break;668j = la;669}670}671hunk_end = j;672
673/* [i..hunk_end) are interesting. Now is it really674* interesting? We check if there are only two versions
675* and the result matches one of them. That is, we look
676* at:
677* (+) line, which records lines added to which parents;
678* this line appears in the result.
679* (-) line, which records from what parents the line
680* was removed; this line does not appear in the result.
681* then check the set of parents the result has difference
682* from, from all lines. If there are lines that has
683* different set of parents that the result has differences
684* from, that means we have more than two versions.
685*
686* Even when we have only two versions, if the result does
687* not match any of the parents, the it should be considered
688* interesting. In such a case, we would have all '+' line.
689* After passing the above "two versions" test, that would
690* appear as "the same set of parents" to be "all parents".
691*/
692same_diff = 0;693has_interesting = 0;694for (j = i; j < hunk_end && !has_interesting; j++) {695unsigned long this_diff = sline[j].flag & all_mask;696struct lline *ll = sline[j].lost;697if (this_diff) {698/* This has some changes. Is it the699* same as others?
700*/
701if (!same_diff)702same_diff = this_diff;703else if (same_diff != this_diff) {704has_interesting = 1;705break;706}707}708while (ll && !has_interesting) {709/* Lost this line from these parents;710* who are they? Are they the same?
711*/
712this_diff = ll->parent_map;713if (!same_diff)714same_diff = this_diff;715else if (same_diff != this_diff) {716has_interesting = 1;717}718ll = ll->next;719}720}721
722if (!has_interesting && same_diff != all_mask) {723/* This hunk is not that interesting after all */724for (j = hunk_begin; j < hunk_end; j++)725sline[j].flag &= ~mark;726}727i = hunk_end;728}729
730has_interesting = give_context(sline, cnt, num_parent);731return has_interesting;732}
733
734static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n, unsigned long null_context)735{
736l0 = sline[l0].p_lno[n];737l1 = sline[l1].p_lno[n];738printf(" -%lu,%lu", l0, l1-l0-null_context);739}
740
741static int hunk_comment_line(const char *bol)742{
743int ch;744
745if (!bol)746return 0;747ch = *bol & 0xff;748return (isalpha(ch) || ch == '_' || ch == '$');749}
750
751static void show_line_to_eol(const char *line, int len, const char *reset)752{
753int saw_cr_at_eol = 0;754if (len < 0)755len = strlen(line);756saw_cr_at_eol = (len && line[len-1] == '\r');757
758printf("%.*s%s%s\n", len - saw_cr_at_eol, line,759reset,760saw_cr_at_eol ? "\r" : "");761}
762
763static void dump_sline(struct sline *sline, const char *line_prefix,764unsigned long cnt, int num_parent,765int use_color, int result_deleted)766{
767unsigned long mark = (1UL<<num_parent);768unsigned long no_pre_delete = (2UL<<num_parent);769int i;770unsigned long lno = 0;771const char *c_frag = diff_get_color(use_color, DIFF_FRAGINFO);772const char *c_func = diff_get_color(use_color, DIFF_FUNCINFO);773const char *c_new = diff_get_color(use_color, DIFF_FILE_NEW);774const char *c_old = diff_get_color(use_color, DIFF_FILE_OLD);775const char *c_context = diff_get_color(use_color, DIFF_CONTEXT);776const char *c_reset = diff_get_color(use_color, DIFF_RESET);777
778if (result_deleted)779return; /* result deleted */780
781while (1) {782unsigned long hunk_end;783unsigned long rlines;784const char *hunk_comment = NULL;785unsigned long null_context = 0;786
787while (lno <= cnt && !(sline[lno].flag & mark)) {788if (hunk_comment_line(sline[lno].bol))789hunk_comment = sline[lno].bol;790lno++;791}792if (cnt < lno)793break;794else {795for (hunk_end = lno + 1; hunk_end <= cnt; hunk_end++)796if (!(sline[hunk_end].flag & mark))797break;798}799rlines = hunk_end - lno;800if (cnt < hunk_end)801rlines--; /* pointing at the last delete hunk */802
803if (!context) {804/*805* Even when running with --unified=0, all
806* lines in the hunk needs to be processed in
807* the loop below in order to show the
808* deletion recorded in lost_head. However,
809* we do not want to show the resulting line
810* with all blank context markers in such a
811* case. Compensate.
812*/
813unsigned long j;814for (j = lno; j < hunk_end; j++)815if (!(sline[j].flag & (mark-1)))816null_context++;817rlines -= null_context;818}819
820printf("%s%s", line_prefix, c_frag);821for (i = 0; i <= num_parent; i++) putchar(combine_marker);822for (i = 0; i < num_parent; i++)823show_parent_lno(sline, lno, hunk_end, i, null_context);824printf(" +%lu,%lu ", lno+1, rlines);825for (i = 0; i <= num_parent; i++) putchar(combine_marker);826
827if (hunk_comment) {828int comment_end = 0;829for (i = 0; i < 40; i++) {830int ch = hunk_comment[i] & 0xff;831if (!ch || ch == '\n')832break;833if (!isspace(ch))834comment_end = i;835}836if (comment_end)837printf("%s%s %s%s", c_reset,838c_context, c_reset,839c_func);840for (i = 0; i < comment_end; i++)841putchar(hunk_comment[i]);842}843
844printf("%s\n", c_reset);845while (lno < hunk_end) {846struct lline *ll;847int j;848unsigned long p_mask;849struct sline *sl = &sline[lno++];850ll = (sl->flag & no_pre_delete) ? NULL : sl->lost;851while (ll) {852printf("%s%s", line_prefix, c_old);853for (j = 0; j < num_parent; j++) {854if (ll->parent_map & (1UL<<j))855putchar('-');856else857putchar(' ');858}859show_line_to_eol(ll->line, -1, c_reset);860ll = ll->next;861}862if (cnt < lno)863break;864p_mask = 1;865fputs(line_prefix, stdout);866if (!(sl->flag & (mark-1))) {867/*868* This sline was here to hang the
869* lost lines in front of it.
870*/
871if (!context)872continue;873fputs(c_context, stdout);874}875else876fputs(c_new, stdout);877for (j = 0; j < num_parent; j++) {878if (p_mask & sl->flag)879putchar('+');880else881putchar(' ');882p_mask <<= 1;883}884show_line_to_eol(sl->bol, sl->len, c_reset);885}886}887}
888
889static void reuse_combine_diff(struct sline *sline, unsigned long cnt,890int i, int j)891{
892/* We have already examined parent j and we know parent i893* and parent j are the same, so reuse the combined result
894* of parent j for parent i.
895*/
896unsigned long lno, imask, jmask;897imask = (1UL<<i);898jmask = (1UL<<j);899
900for (lno = 0; lno <= cnt; lno++) {901struct lline *ll = sline->lost;902sline->p_lno[i] = sline->p_lno[j];903while (ll) {904if (ll->parent_map & jmask)905ll->parent_map |= imask;906ll = ll->next;907}908if (sline->flag & jmask)909sline->flag |= imask;910sline++;911}912/* the overall size of the file (sline[cnt]) */913sline->p_lno[i] = sline->p_lno[j];914}
915
916static void dump_quoted_path(const char *head,917const char *prefix,918const char *path,919const char *line_prefix,920const char *c_meta, const char *c_reset)921{
922static struct strbuf buf = STRBUF_INIT;923
924strbuf_reset(&buf);925strbuf_addstr(&buf, line_prefix);926strbuf_addstr(&buf, c_meta);927strbuf_addstr(&buf, head);928quote_two_c_style(&buf, prefix, path, 0);929strbuf_addstr(&buf, c_reset);930puts(buf.buf);931}
932
933static void show_combined_header(struct combine_diff_path *elem,934int num_parent,935struct rev_info *rev,936const char *line_prefix,937int mode_differs,938int show_file_header)939{
940struct diff_options *opt = &rev->diffopt;941int abbrev = opt->flags.full_index ? the_hash_algo->hexsz : DEFAULT_ABBREV;942const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";943const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";944const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);945const char *c_reset = diff_get_color_opt(opt, DIFF_RESET);946const char *abb;947int added = 0;948int deleted = 0;949int i;950int dense = rev->dense_combined_merges;951
952if (rev->loginfo && !rev->no_commit_id)953show_log(rev);954
955dump_quoted_path(dense ? "diff --cc " : "diff --combined ",956"", elem->path, line_prefix, c_meta, c_reset);957printf("%s%sindex ", line_prefix, c_meta);958for (i = 0; i < num_parent; i++) {959abb = repo_find_unique_abbrev(the_repository,960&elem->parent[i].oid, abbrev);961printf("%s%s", i ? "," : "", abb);962}963abb = repo_find_unique_abbrev(the_repository, &elem->oid, abbrev);964printf("..%s%s\n", abb, c_reset);965
966if (mode_differs) {967deleted = !elem->mode;968
969/* We say it was added if nobody had it */970added = !deleted;971for (i = 0; added && i < num_parent; i++)972if (elem->parent[i].status !=973DIFF_STATUS_ADDED)974added = 0;975if (added)976printf("%s%snew file mode %06o",977line_prefix, c_meta, elem->mode);978else {979if (deleted)980printf("%s%sdeleted file ",981line_prefix, c_meta);982printf("mode ");983for (i = 0; i < num_parent; i++) {984printf("%s%06o", i ? "," : "",985elem->parent[i].mode);986}987if (elem->mode)988printf("..%06o", elem->mode);989}990printf("%s\n", c_reset);991}992
993if (!show_file_header)994return;995
996if (rev->combined_all_paths) {997for (i = 0; i < num_parent; i++) {998char *path = filename_changed(elem->parent[i].status)999? elem->parent[i].path.buf : elem->path;1000if (elem->parent[i].status == DIFF_STATUS_ADDED)1001dump_quoted_path("--- ", "", "/dev/null",1002line_prefix, c_meta, c_reset);1003else1004dump_quoted_path("--- ", a_prefix, path,1005line_prefix, c_meta, c_reset);1006}1007} else {1008if (added)1009dump_quoted_path("--- ", "", "/dev/null",1010line_prefix, c_meta, c_reset);1011else1012dump_quoted_path("--- ", a_prefix, elem->path,1013line_prefix, c_meta, c_reset);1014}1015if (deleted)1016dump_quoted_path("+++ ", "", "/dev/null",1017line_prefix, c_meta, c_reset);1018else1019dump_quoted_path("+++ ", b_prefix, elem->path,1020line_prefix, c_meta, c_reset);1021}
1022
1023static void show_patch_diff(struct combine_diff_path *elem, int num_parent,1024int working_tree_file,1025struct rev_info *rev)1026{
1027struct diff_options *opt = &rev->diffopt;1028unsigned long result_size, cnt, lno;1029int result_deleted = 0;1030char *result, *cp;1031struct sline *sline; /* survived lines */1032int mode_differs = 0;1033int i, show_hunks;1034mmfile_t result_file;1035struct userdiff_driver *userdiff;1036struct userdiff_driver *textconv = NULL;1037int is_binary;1038const char *line_prefix = diff_line_prefix(opt);1039
1040context = opt->context;1041userdiff = userdiff_find_by_path(opt->repo->index, elem->path);1042if (!userdiff)1043userdiff = userdiff_find_by_name("default");1044if (opt->flags.allow_textconv)1045textconv = userdiff_get_textconv(opt->repo, userdiff);1046
1047/* Read the result of merge first */1048if (!working_tree_file)1049result = grab_blob(opt->repo, &elem->oid, elem->mode, &result_size,1050textconv, elem->path);1051else {1052/* Used by diff-tree to read from the working tree */1053struct stat st;1054int fd = -1;1055
1056if (lstat(elem->path, &st) < 0)1057goto deleted_file;1058
1059if (S_ISLNK(st.st_mode)) {1060struct strbuf buf = STRBUF_INIT;1061
1062if (strbuf_readlink(&buf, elem->path, st.st_size) < 0) {1063error_errno("readlink(%s)", elem->path);1064return;1065}1066result_size = buf.len;1067result = strbuf_detach(&buf, NULL);1068elem->mode = canon_mode(st.st_mode);1069} else if (S_ISDIR(st.st_mode)) {1070struct object_id oid;1071if (repo_resolve_gitlink_ref(the_repository, elem->path,1072"HEAD", &oid) < 0)1073result = grab_blob(opt->repo, &elem->oid,1074elem->mode, &result_size,1075NULL, NULL);1076else1077result = grab_blob(opt->repo, &oid, elem->mode,1078&result_size, NULL, NULL);1079} else if (textconv) {1080struct diff_filespec *df = alloc_filespec(elem->path);1081fill_filespec(df, null_oid(), 0, st.st_mode);1082result_size = fill_textconv(opt->repo, textconv, df, &result);1083free_filespec(df);1084} else if (0 <= (fd = open(elem->path, O_RDONLY))) {1085size_t len = xsize_t(st.st_size);1086ssize_t done;1087int is_file, i;1088
1089elem->mode = canon_mode(st.st_mode);1090/* if symlinks don't work, assume symlink if all parents1091* are symlinks
1092*/
1093is_file = has_symlinks;1094for (i = 0; !is_file && i < num_parent; i++)1095is_file = !S_ISLNK(elem->parent[i].mode);1096if (!is_file)1097elem->mode = canon_mode(S_IFLNK);1098
1099result_size = len;1100result = xmallocz(len);1101
1102done = read_in_full(fd, result, len);1103if (done < 0)1104die_errno("read error '%s'", elem->path);1105else if (done < len)1106die("early EOF '%s'", elem->path);1107
1108/* If not a fake symlink, apply filters, e.g. autocrlf */1109if (is_file) {1110struct strbuf buf = STRBUF_INIT;1111
1112if (convert_to_git(rev->diffopt.repo->index,1113elem->path, result, len, &buf, global_conv_flags_eol)) {1114free(result);1115result = strbuf_detach(&buf, &len);1116result_size = len;1117}1118}1119}1120else {1121deleted_file:1122result_deleted = 1;1123result_size = 0;1124elem->mode = 0;1125result = xcalloc(1, 1);1126}1127
1128if (0 <= fd)1129close(fd);1130}1131
1132for (i = 0; i < num_parent; i++) {1133if (elem->parent[i].mode != elem->mode) {1134mode_differs = 1;1135break;1136}1137}1138
1139if (textconv)1140is_binary = 0;1141else if (userdiff->binary != -1)1142is_binary = userdiff->binary;1143else {1144is_binary = buffer_is_binary(result, result_size);1145for (i = 0; !is_binary && i < num_parent; i++) {1146char *buf;1147unsigned long size;1148buf = grab_blob(opt->repo,1149&elem->parent[i].oid,1150elem->parent[i].mode,1151&size, NULL, NULL);1152if (buffer_is_binary(buf, size))1153is_binary = 1;1154free(buf);1155}1156}1157if (is_binary) {1158show_combined_header(elem, num_parent, rev,1159line_prefix, mode_differs, 0);1160printf("Binary files differ\n");1161free(result);1162return;1163}1164
1165for (cnt = 0, cp = result; cp < result + result_size; cp++) {1166if (*cp == '\n')1167cnt++;1168}1169if (result_size && result[result_size-1] != '\n')1170cnt++; /* incomplete line */1171
1172CALLOC_ARRAY(sline, st_add(cnt, 2));1173sline[0].bol = result;1174for (lno = 0, cp = result; cp < result + result_size; cp++) {1175if (*cp == '\n') {1176sline[lno].len = cp - sline[lno].bol;1177lno++;1178if (lno < cnt)1179sline[lno].bol = cp + 1;1180}1181}1182if (result_size && result[result_size-1] != '\n')1183sline[cnt-1].len = result_size - (sline[cnt-1].bol - result);1184
1185result_file.ptr = result;1186result_file.size = result_size;1187
1188/* Even p_lno[cnt+1] is valid -- that is for the end line number1189* for deletion hunk at the end.
1190*/
1191CALLOC_ARRAY(sline[0].p_lno, st_mult(st_add(cnt, 2), num_parent));1192for (lno = 0; lno <= cnt; lno++)1193sline[lno+1].p_lno = sline[lno].p_lno + num_parent;1194
1195for (i = 0; i < num_parent; i++) {1196int j;1197for (j = 0; j < i; j++) {1198if (oideq(&elem->parent[i].oid,1199&elem->parent[j].oid)) {1200reuse_combine_diff(sline, cnt, i, j);1201break;1202}1203}1204if (i <= j)1205combine_diff(opt->repo,1206&elem->parent[i].oid,1207elem->parent[i].mode,1208&result_file, sline,1209cnt, i, num_parent, result_deleted,1210textconv, elem->path, opt->xdl_opts);1211}1212
1213show_hunks = make_hunks(sline, cnt, num_parent, rev->dense_combined_merges);1214
1215if (show_hunks || mode_differs || working_tree_file) {1216show_combined_header(elem, num_parent, rev,1217line_prefix, mode_differs, 1);1218dump_sline(sline, line_prefix, cnt, num_parent,1219opt->use_color, result_deleted);1220}1221free(result);1222
1223for (lno = 0; lno < cnt; lno++) {1224if (sline[lno].lost) {1225struct lline *ll = sline[lno].lost;1226while (ll) {1227struct lline *tmp = ll;1228ll = ll->next;1229free(tmp);1230}1231}1232}1233free(sline[0].p_lno);1234free(sline);1235}
1236
1237static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct rev_info *rev)1238{
1239struct diff_options *opt = &rev->diffopt;1240int line_termination, inter_name_termination, i;1241const char *line_prefix = diff_line_prefix(opt);1242
1243line_termination = opt->line_termination;1244inter_name_termination = '\t';1245if (!line_termination)1246inter_name_termination = 0;1247
1248if (rev->loginfo && !rev->no_commit_id)1249show_log(rev);1250
1251
1252if (opt->output_format & DIFF_FORMAT_RAW) {1253printf("%s", line_prefix);1254
1255/* As many colons as there are parents */1256for (i = 0; i < num_parent; i++)1257putchar(':');1258
1259/* Show the modes */1260for (i = 0; i < num_parent; i++)1261printf("%06o ", p->parent[i].mode);1262printf("%06o", p->mode);1263
1264/* Show sha1's */1265for (i = 0; i < num_parent; i++)1266printf(" %s", diff_aligned_abbrev(&p->parent[i].oid,1267opt->abbrev));1268printf(" %s ", diff_aligned_abbrev(&p->oid, opt->abbrev));1269}1270
1271if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) {1272for (i = 0; i < num_parent; i++)1273putchar(p->parent[i].status);1274putchar(inter_name_termination);1275}1276
1277for (i = 0; i < num_parent; i++)1278if (rev->combined_all_paths) {1279if (filename_changed(p->parent[i].status))1280write_name_quoted(p->parent[i].path.buf, stdout,1281inter_name_termination);1282else1283write_name_quoted(p->path, stdout,1284inter_name_termination);1285}1286write_name_quoted(p->path, stdout, line_termination);1287}
1288
1289/*
1290* The result (p->elem) is from the working tree and their
1291* parents are typically from multiple stages during a merge
1292* (i.e. diff-files) or the state in HEAD and in the index
1293* (i.e. diff-index).
1294*/
1295void show_combined_diff(struct combine_diff_path *p,1296int num_parent,1297struct rev_info *rev)1298{
1299struct diff_options *opt = &rev->diffopt;1300
1301if (opt->output_format & (DIFF_FORMAT_RAW |1302DIFF_FORMAT_NAME |1303DIFF_FORMAT_NAME_STATUS))1304show_raw_diff(p, num_parent, rev);1305else if (opt->output_format & DIFF_FORMAT_PATCH)1306show_patch_diff(p, num_parent, 1, rev);1307}
1308
1309static void free_combined_pair(struct diff_filepair *pair)1310{
1311free(pair->two);1312free(pair);1313}
1314
1315/*
1316* A combine_diff_path expresses N parents on the LHS against 1 merge
1317* result. Synthesize a diff_filepair that has N entries on the "one"
1318* side and 1 entry on the "two" side.
1319*
1320* In the future, we might want to add more data to combine_diff_path
1321* so that we can fill fields we are ignoring (most notably, size) here,
1322* but currently nobody uses it, so this should suffice for now.
1323*/
1324static struct diff_filepair *combined_pair(struct combine_diff_path *p,1325int num_parent)1326{
1327int i;1328struct diff_filepair *pair;1329struct diff_filespec *pool;1330
1331pair = xmalloc(sizeof(*pair));1332CALLOC_ARRAY(pool, st_add(num_parent, 1));1333pair->one = pool + 1;1334pair->two = pool;1335
1336for (i = 0; i < num_parent; i++) {1337pair->one[i].path = p->path;1338pair->one[i].mode = p->parent[i].mode;1339oidcpy(&pair->one[i].oid, &p->parent[i].oid);1340pair->one[i].oid_valid = !is_null_oid(&p->parent[i].oid);1341pair->one[i].has_more_entries = 1;1342}1343pair->one[num_parent - 1].has_more_entries = 0;1344
1345pair->two->path = p->path;1346pair->two->mode = p->mode;1347oidcpy(&pair->two->oid, &p->oid);1348pair->two->oid_valid = !is_null_oid(&p->oid);1349return pair;1350}
1351
1352static void handle_combined_callback(struct diff_options *opt,1353struct combine_diff_path *paths,1354int num_parent,1355int num_paths)1356{
1357struct combine_diff_path *p;1358struct diff_queue_struct q;1359int i;1360
1361CALLOC_ARRAY(q.queue, num_paths);1362q.alloc = num_paths;1363q.nr = num_paths;1364for (i = 0, p = paths; p; p = p->next)1365q.queue[i++] = combined_pair(p, num_parent);1366opt->format_callback(&q, opt, opt->format_callback_data);1367for (i = 0; i < num_paths; i++)1368free_combined_pair(q.queue[i]);1369free(q.queue);1370}
1371
1372static const char *path_path(void *obj)1373{
1374struct combine_diff_path *path = (struct combine_diff_path *)obj;1375
1376return path->path;1377}
1378
1379/*
1380* Diff stat formats which we always compute solely against the first parent.
1381*/
1382#define STAT_FORMAT_MASK (DIFF_FORMAT_NUMSTAT \1383| DIFF_FORMAT_SHORTSTAT \1384| DIFF_FORMAT_SUMMARY \1385| DIFF_FORMAT_DIRSTAT \1386| DIFF_FORMAT_DIFFSTAT)1387
1388/* find set of paths that every parent touches */
1389static struct combine_diff_path *find_paths_generic(const struct object_id *oid,1390const struct oid_array *parents,1391struct diff_options *opt,1392int combined_all_paths)1393{
1394struct combine_diff_path *paths = NULL;1395int i, num_parent = parents->nr;1396
1397int output_format = opt->output_format;1398const char *orderfile = opt->orderfile;1399
1400opt->output_format = DIFF_FORMAT_NO_OUTPUT;1401/* tell diff_tree to emit paths in sorted (=tree) order */1402opt->orderfile = NULL;1403
1404/* D(A,P1...Pn) = D(A,P1) ^ ... ^ D(A,Pn) (wrt paths) */1405for (i = 0; i < num_parent; i++) {1406/*1407* show stat against the first parent even when doing
1408* combined diff.
1409*/
1410int stat_opt = output_format & STAT_FORMAT_MASK;1411if (i == 0 && stat_opt)1412opt->output_format = stat_opt;1413else1414opt->output_format = DIFF_FORMAT_NO_OUTPUT;1415diff_tree_oid(&parents->oid[i], oid, "", opt);1416diffcore_std(opt);1417paths = intersect_paths(paths, i, num_parent,1418combined_all_paths);1419
1420/* if showing diff, show it in requested order */1421if (opt->output_format != DIFF_FORMAT_NO_OUTPUT &&1422orderfile) {1423diffcore_order(orderfile);1424}1425
1426diff_flush(opt);1427}1428
1429opt->output_format = output_format;1430opt->orderfile = orderfile;1431return paths;1432}
1433
1434
1435/*
1436* find set of paths that everybody touches, assuming diff is run without
1437* rename/copy detection, etc, comparing all trees simultaneously (= faster).
1438*/
1439static struct combine_diff_path *find_paths_multitree(1440const struct object_id *oid, const struct oid_array *parents,1441struct diff_options *opt)1442{
1443int i, nparent = parents->nr;1444const struct object_id **parents_oid;1445struct combine_diff_path paths_head;1446struct strbuf base;1447
1448ALLOC_ARRAY(parents_oid, nparent);1449for (i = 0; i < nparent; i++)1450parents_oid[i] = &parents->oid[i];1451
1452/* fake list head, so worker can assume it is non-NULL */1453paths_head.next = NULL;1454
1455strbuf_init(&base, PATH_MAX);1456diff_tree_paths(&paths_head, oid, parents_oid, nparent, &base, opt);1457
1458strbuf_release(&base);1459free(parents_oid);1460return paths_head.next;1461}
1462
1463static int match_objfind(struct combine_diff_path *path,1464int num_parent,1465const struct oidset *set)1466{
1467int i;1468if (oidset_contains(set, &path->oid))1469return 1;1470for (i = 0; i < num_parent; i++) {1471if (oidset_contains(set, &path->parent[i].oid))1472return 1;1473}1474return 0;1475}
1476
1477static struct combine_diff_path *combined_objfind(struct diff_options *opt,1478struct combine_diff_path *paths,1479int num_parent)1480{
1481struct combine_diff_path *ret = NULL, **tail = &ret;1482struct combine_diff_path *p = paths;1483
1484while (p) {1485struct combine_diff_path *next = p->next;1486
1487if (match_objfind(p, num_parent, opt->objfind)) {1488p->next = NULL;1489*tail = p;1490tail = &p->next;1491} else {1492free(p);1493}1494p = next;1495}1496
1497return ret;1498}
1499
1500void diff_tree_combined(const struct object_id *oid,1501const struct oid_array *parents,1502struct rev_info *rev)1503{
1504struct diff_options *opt = &rev->diffopt;1505struct diff_options diffopts;1506struct combine_diff_path *p, *paths;1507int i, num_paths, needsep, show_log_first, num_parent = parents->nr;1508int need_generic_pathscan;1509
1510if (opt->ignore_regex_nr)1511die("combined diff and '%s' cannot be used together",1512"--ignore-matching-lines");1513if (opt->close_file)1514die("combined diff and '%s' cannot be used together",1515"--output");1516
1517/* nothing to do, if no parents */1518if (!num_parent)1519return;1520
1521show_log_first = !!rev->loginfo && !rev->no_commit_id;1522needsep = 0;1523if (show_log_first) {1524show_log(rev);1525
1526if (rev->verbose_header && opt->output_format &&1527opt->output_format != DIFF_FORMAT_NO_OUTPUT &&1528!commit_format_is_empty(rev->commit_format))1529printf("%s%c", diff_line_prefix(opt),1530opt->line_termination);1531}1532
1533diffopts = *opt;1534copy_pathspec(&diffopts.pathspec, &opt->pathspec);1535diffopts.flags.recursive = 1;1536diffopts.flags.allow_external = 0;1537
1538/* find set of paths that everybody touches1539*
1540* NOTE
1541*
1542* Diffcore transformations are bound to diff_filespec and logic
1543* comparing two entries - i.e. they do not apply directly to combine
1544* diff.
1545*
1546* If some of such transformations is requested - we launch generic
1547* path scanning, which works significantly slower compared to
1548* simultaneous all-trees-in-one-go scan in find_paths_multitree().
1549*
1550* TODO some of the filters could be ported to work on
1551* combine_diff_paths - i.e. all functionality that skips paths, so in
1552* theory, we could end up having only multitree path scanning.
1553*
1554* NOTE please keep this semantically in sync with diffcore_std()
1555*/
1556need_generic_pathscan = opt->skip_stat_unmatch ||1557opt->flags.follow_renames ||1558opt->break_opt != -1 ||1559opt->detect_rename ||1560(opt->pickaxe_opts &1561(DIFF_PICKAXE_KINDS_MASK & ~DIFF_PICKAXE_KIND_OBJFIND)) ||1562opt->filter;1563
1564if (need_generic_pathscan) {1565/*1566* NOTE generic case also handles --stat, as it computes
1567* diff(sha1,parent_i) for all i to do the job, specifically
1568* for parent0.
1569*/
1570paths = find_paths_generic(oid, parents, &diffopts,1571rev->combined_all_paths);1572}1573else {1574int stat_opt;1575paths = find_paths_multitree(oid, parents, &diffopts);1576
1577if (opt->pickaxe_opts & DIFF_PICKAXE_KIND_OBJFIND)1578paths = combined_objfind(opt, paths, num_parent);1579
1580/*1581* show stat against the first parent even
1582* when doing combined diff.
1583*/
1584stat_opt = opt->output_format & STAT_FORMAT_MASK;1585if (stat_opt) {1586diffopts.output_format = stat_opt;1587
1588diff_tree_oid(&parents->oid[0], oid, "", &diffopts);1589diffcore_std(&diffopts);1590if (opt->orderfile)1591diffcore_order(opt->orderfile);1592diff_flush(&diffopts);1593}1594}1595
1596/* find out number of surviving paths */1597for (num_paths = 0, p = paths; p; p = p->next)1598num_paths++;1599
1600/* order paths according to diffcore_order */1601if (opt->orderfile && num_paths) {1602struct obj_order *o;1603
1604ALLOC_ARRAY(o, num_paths);1605for (i = 0, p = paths; p; p = p->next, i++)1606o[i].obj = p;1607order_objects(opt->orderfile, path_path, o, num_paths);1608for (i = 0; i < num_paths - 1; i++) {1609p = o[i].obj;1610p->next = o[i+1].obj;1611}1612
1613p = o[num_paths-1].obj;1614p->next = NULL;1615paths = o[0].obj;1616free(o);1617}1618
1619
1620if (num_paths) {1621if (opt->output_format & (DIFF_FORMAT_RAW |1622DIFF_FORMAT_NAME |1623DIFF_FORMAT_NAME_STATUS)) {1624for (p = paths; p; p = p->next)1625show_raw_diff(p, num_parent, rev);1626needsep = 1;1627}1628else if (opt->output_format & STAT_FORMAT_MASK)1629needsep = 1;1630else if (opt->output_format & DIFF_FORMAT_CALLBACK)1631handle_combined_callback(opt, paths, num_parent, num_paths);1632
1633if (opt->output_format & DIFF_FORMAT_PATCH) {1634if (needsep)1635printf("%s%c", diff_line_prefix(opt),1636opt->line_termination);1637for (p = paths; p; p = p->next)1638show_patch_diff(p, num_parent, 0, rev);1639}1640}1641
1642/* Clean things up */1643while (paths) {1644struct combine_diff_path *tmp = paths;1645paths = paths->next;1646for (i = 0; i < num_parent; i++)1647if (rev->combined_all_paths &&1648filename_changed(tmp->parent[i].status))1649strbuf_release(&tmp->parent[i].path);1650free(tmp);1651}1652
1653clear_pathspec(&diffopts.pathspec);1654}
1655
1656void diff_tree_combined_merge(const struct commit *commit,1657struct rev_info *rev)1658{
1659struct commit_list *parent = get_saved_parents(rev, commit);1660struct oid_array parents = OID_ARRAY_INIT;1661
1662while (parent) {1663oid_array_append(&parents, &parent->item->object.oid);1664parent = parent->next;1665}1666diff_tree_combined(&commit->object.oid, &parents, rev);1667oid_array_clear(&parents);1668}
1669