#include "diffcore.h"
#include "quote.h"
-struct path_list {
- struct path_list *next;
- int len;
- char *path;
- unsigned char sha1[20];
- unsigned char parent_sha1[FLEX_ARRAY][20];
-};
-
static int uninteresting(struct diff_filepair *p)
{
if (diff_unmodified_pair(p))
return 0;
}
-static struct path_list *intersect_paths(struct path_list *curr,
- int n, int num_parent)
+static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
{
struct diff_queue_struct *q = &diff_queued_diff;
- struct path_list *p;
+ struct combine_diff_path *p;
int i;
if (!n) {
- struct path_list *list = NULL, **tail = &list;
+ struct combine_diff_path *list = NULL, **tail = &list;
for (i = 0; i < q->nr; i++) {
int len;
const char *path;
return ((sline->flag & all_mask) != all_mask || sline->lost_head);
}
-static unsigned long line_diff_parents(struct sline *sline, unsigned long all_mask)
+static unsigned long line_common_diff(struct sline *sline, unsigned long all_mask)
{
/*
- * Look at the line and see from which parents we have difference.
- * Lower bits of sline->flag records if the parent had this line,
- * so XOR with all_mask gives us on-bits for parents we have
- * differences with.
+ * Look at the line and see from which parents we have the
+ * same difference.
+ */
+
+ /* Lower bits of sline->flag records if the parent had this
+ * line, so XOR with all_mask gives us on-bits for parents we
+ * have differences with.
*/
- unsigned long parents = (sline->flag ^ all_mask);
+ unsigned long common_adds = (sline->flag ^ all_mask) & all_mask;
+ unsigned long common_removes = all_mask;
+
+ /* If all the parents have this line, that also counts as
+ * having the same difference.
+ */
+ if (!common_adds)
+ common_adds = all_mask;
+
if (sline->lost_head) {
+ /* Lost head list records the lines removed from
+ * the parents, and parent_map records from which
+ * parent the line was removed.
+ */
struct lline *ll;
- for (ll = sline->lost_head; ll; ll = ll->next)
- parents |= ll->parent_map;
+ for (ll = sline->lost_head; ll; ll = ll->next) {
+ common_removes &= ll->parent_map;
+ }
}
- return parents & all_mask;
+ return common_adds & common_removes;
}
-static void make_hunks(struct sline *sline, unsigned long cnt,
- int num_parent, int dense)
+static unsigned long line_all_diff(struct sline *sline, unsigned long all_mask)
+{
+ /*
+ * Look at the line and see from which parents we have some difference.
+ */
+ unsigned long different = (sline->flag ^ all_mask) & all_mask;
+ if (sline->lost_head) {
+ /* Lost head list records the lines removed from
+ * the parents, and parent_map records from which
+ * parent the line was removed.
+ */
+ struct lline *ll;
+ for (ll = sline->lost_head; ll; ll = ll->next) {
+ different |= ll->parent_map;
+ }
+ }
+ return different;
+}
+
+static unsigned long adjust_hunk_tail(struct sline *sline,
+ unsigned long all_mask,
+ unsigned long hunk_begin,
+ unsigned long i)
+{
+ /* i points at the first uninteresting line.
+ * If the last line of the hunk was interesting
+ * only because it has some deletion, then
+ * it is not all that interesting for the
+ * purpose of giving trailing context lines.
+ */
+ if ((hunk_begin + 1 <= i) &&
+ ((sline[i-1].flag & all_mask) == all_mask))
+ i--;
+ return i;
+}
+
+static unsigned long next_interesting(struct sline *sline,
+ unsigned long mark,
+ unsigned long i,
+ unsigned long cnt,
+ int uninteresting)
+{
+ while (i < cnt)
+ if (uninteresting ?
+ !(sline[i].flag & mark) :
+ (sline[i].flag & mark))
+ return i;
+ else
+ i++;
+ return cnt;
+}
+
+static int give_context(struct sline *sline, unsigned long cnt, int num_parent)
{
unsigned long all_mask = (1UL<<num_parent) - 1;
unsigned long mark = (1UL<<num_parent);
unsigned long i;
- i = 0;
+ i = next_interesting(sline, mark, 0, cnt, 0);
+ if (cnt <= i)
+ return 0;
+
while (i < cnt) {
- if (interesting(&sline[i], all_mask)) {
- unsigned long j = (context < i) ? i - context : 0;
- while (j <= i)
+ unsigned long j = (context < i) ? (i - context) : 0;
+ unsigned long k;
+ while (j < i)
+ sline[j++].flag |= mark;
+
+ again:
+ j = next_interesting(sline, mark, i, cnt, 1);
+ if (cnt <= j)
+ break; /* the rest are all interesting */
+
+ /* lookahead context lines */
+ k = next_interesting(sline, mark, j, cnt, 0);
+ j = adjust_hunk_tail(sline, all_mask, i, j);
+
+ if (k < j + context) {
+ /* k is interesting and [j,k) are not, but
+ * paint them interesting because the gap is small.
+ */
+ while (j < k)
sline[j++].flag |= mark;
- while (++i < cnt) {
- if (!interesting(&sline[i], all_mask))
- break;
- sline[i].flag |= mark;
- }
- j = (i + context < cnt) ? i + context : cnt;
- while (i < j)
- sline[i++].flag |= mark;
- continue;
+ i = k;
+ goto again;
}
- i++;
+
+ /* j is the first uninteresting line and there is
+ * no overlap beyond it within context lines.
+ */
+ i = k;
+ k = (j + context < cnt) ? j + context : cnt;
+ while (j < k)
+ sline[j++].flag |= mark;
+ }
+ return 1;
+}
+
+static int make_hunks(struct sline *sline, unsigned long cnt,
+ int num_parent, int dense)
+{
+ unsigned long all_mask = (1UL<<num_parent) - 1;
+ unsigned long mark = (1UL<<num_parent);
+ unsigned long i;
+ int has_interesting = 0;
+
+ for (i = 0; i < cnt; i++) {
+ if (interesting(&sline[i], all_mask))
+ sline[i].flag |= mark;
+ else
+ sline[i].flag &= ~mark;
}
if (!dense)
- return;
+ return give_context(sline, cnt, num_parent);
- /* Look at each hunk, and if it contains changes from only
- * one parent, mark that uninteresting.
+ /* Look at each hunk, and if we have changes from only one
+ * parent, or the changes are the same from all but one
+ * parent, mark that uninteresting.
*/
i = 0;
while (i < cnt) {
- int j, hunk_end, diffs;
- unsigned long parents;
+ unsigned long j, hunk_begin, hunk_end;
+ int same, diff;
+ unsigned long same_diff, all_diff;
while (i < cnt && !(sline[i].flag & mark))
i++;
if (cnt <= i)
break; /* No more interesting hunks */
- for (hunk_end = i + 1; hunk_end < cnt; hunk_end++)
- if (!(sline[hunk_end].flag & mark))
- break;
- /* [i..hunk_end) are interesting. Now is it from
- * only one parent?
- * If lost lines are only from one parent and
- * remaining lines existed in parents other than
- * that parent, then the hunk is not that interesting.
+ hunk_begin = i;
+ for (j = i + 1; j < cnt; j++) {
+ if (!(sline[j].flag & mark)) {
+ /* Look beyond the end to see if there
+ * is an interesting line after this
+ * hunk within context span.
+ */
+ unsigned long la; /* lookahead */
+ int contin = 0;
+ la = adjust_hunk_tail(sline, all_mask,
+ hunk_begin, j);
+ la = (la + context < cnt) ?
+ (la + context) : cnt;
+ while (j <= --la) {
+ if (sline[la].flag & mark) {
+ contin = 1;
+ break;
+ }
+ }
+ if (!contin)
+ break;
+ j = la;
+ }
+ }
+ hunk_end = j;
+
+ /* [i..hunk_end) are interesting. Now does it have
+ * the same change with all but one parent?
*/
- parents = 0;
- diffs = 0;
- for (j = i; j < hunk_end; j++)
- parents |= line_diff_parents(sline + j, all_mask);
- /* Now, how many bits from [0..num_parent) are on? */
+ same_diff = all_mask;
+ all_diff = 0;
+ for (j = i; j < hunk_end; j++) {
+ same_diff &= line_common_diff(sline + j, all_mask);
+ all_diff |= line_all_diff(sline + j, all_mask);
+ }
+ diff = same = 0;
for (j = 0; j < num_parent; j++) {
- if (parents & (1UL<<j))
- diffs++;
+ if (same_diff & (1UL<<j))
+ same++;
+ if (all_diff & (1UL<<j))
+ diff++;
}
- if (diffs < 2) {
+ if ((num_parent - 1 <= same) || (diff == 1)) {
/* This hunk is not that interesting after all */
- for (j = i; j < hunk_end; j++)
+ for (j = hunk_begin; j < hunk_end; j++)
sline[j].flag &= ~mark;
}
i = hunk_end;
}
+
+ has_interesting = give_context(sline, cnt, num_parent);
+ return has_interesting;
}
static void dump_sline(struct sline *sline, int cnt, int num_parent)
else
putchar(' ');
}
- putchar(' ');
puts(ll->line);
ll = ll->next;
}
else
putchar('+');
}
- printf(" %.*s\n", sl->len, sl->bol);
+ printf("%.*s\n", sl->len, sl->bol);
}
}
}
-static void show_combined_diff(struct path_list *elem, int num_parent,
- int dense)
+int show_combined_diff(struct combine_diff_path *elem, int num_parent,
+ int dense, const char *header, int show_empty)
{
unsigned long size, cnt, lno;
char *result, *cp, *ep;
struct sline *sline; /* survived lines */
- int i;
- char ourtmp[TMPPATHLEN];
+ int i, show_hunks, shown_header = 0;
+ char ourtmp_buf[TMPPATHLEN];
+ char *ourtmp = ourtmp_buf;
/* Read the result of merge first */
- result = grab_blob(elem->sha1, &size);
- write_to_temp_file(ourtmp, result, size);
+ if (memcmp(elem->sha1, null_sha1, 20)) {
+ result = grab_blob(elem->sha1, &size);
+ write_to_temp_file(ourtmp, result, size);
+ }
+ else {
+ struct stat st;
+ int fd;
+ ourtmp = elem->path;
+ if (0 <= (fd = open(ourtmp, O_RDONLY)) &&
+ !fstat(fd, &st)) {
+ int len = st.st_size;
+ int cnt = 0;
+
+ size = len;
+ result = xmalloc(len + 1);
+ while (cnt < len) {
+ int done = xread(fd, result+cnt, len-cnt);
+ if (done == 0)
+ break;
+ if (done < 0)
+ die("read error '%s'", ourtmp);
+ cnt += done;
+ }
+ result[len] = 0;
+ }
+ else {
+ /* deleted file */
+ size = 0;
+ result = xmalloc(1);
+ result[0] = 0;
+ ourtmp = "/dev/null";
+ }
+ if (0 <= fd)
+ close(fd);
+ }
for (cnt = 0, cp = result; cp - result < size; cp++) {
if (*cp == '\n')
for (i = 0; i < num_parent; i++)
combine_diff(elem->parent_sha1[i], ourtmp, sline, cnt, i);
- make_hunks(sline, cnt, num_parent, dense);
+ show_hunks = make_hunks(sline, cnt, num_parent, dense);
- dump_sline(sline, cnt, num_parent);
- unlink(ourtmp);
+ if (header && (show_hunks || show_empty)) {
+ shown_header++;
+ puts(header);
+ }
+ if (show_hunks) {
+ printf("diff --%s ", dense ? "cc" : "combined");
+ if (quote_c_style(elem->path, NULL, NULL, 0))
+ quote_c_style(elem->path, NULL, stdout, 0);
+ else
+ printf("%s", elem->path);
+ putchar('\n');
+ dump_sline(sline, cnt, num_parent);
+ }
+ if (ourtmp == ourtmp_buf)
+ unlink(ourtmp);
free(result);
for (i = 0; i < cnt; i++) {
}
}
free(sline);
+ return shown_header;
}
int diff_tree_combined_merge(const unsigned char *sha1,
struct commit *commit = lookup_commit(sha1);
struct diff_options diffopts;
struct commit_list *parents;
- struct path_list *p, *paths = NULL;
+ struct combine_diff_path *p, *paths = NULL;
int num_parent, i, num_paths;
diff_setup(&diffopts);
num_paths++;
}
if (num_paths || show_empty_merge) {
- puts(header);
for (p = paths; p; p = p->next) {
if (!p->len)
continue;
- printf("diff --combined ");
- if (quote_c_style(p->path, NULL, NULL, 0))
- quote_c_style(p->path, NULL, stdout, 0);
- else
- printf("%s", p->path);
- putchar('\n');
- show_combined_diff(p, num_parent, dense);
+ if (show_combined_diff(p, num_parent, dense, header,
+ show_empty_merge))
+ header = NULL;
}
}
/* Clean things up */
while (paths) {
- struct path_list *tmp = paths;
+ struct combine_diff_path *tmp = paths;
paths = paths->next;
free(tmp);
}