diff3: Add support for -m

diff3 in -m mode generates a complete file with changes bracketed with
conflict markers. This adds support for diff3 to generate version
control style three way merge output.

The output format was inferred from looking at the gnu diff3 output on a
selection of test files as a specification of what diff3 -m should
output is not available. It is likely there are cases where the -m
output differs from other tools and I am happy to update diff3 to
address these.

Discussed with:	pstef, kevans
Sponsored by:	Klara, Inc.
This commit is contained in:
Tom Jones 2022-04-19 16:20:24 +01:00
parent f45f90c5d6
commit 034dd2d54f
5 changed files with 137 additions and 3 deletions

View File

@ -38,7 +38,7 @@
.Nd 3-way differential file comparison
.Sh SYNOPSIS
.Nm diff3
.Op Fl 3AaEeiTXx
.Op Fl 3AaEeimTXx
.Op Fl Fl diff-program Ar program
.Op Fl Fl strip-trailing-cr
.Op Fl L | Fl Fl label Ar label1
@ -117,6 +117,8 @@ Defines labels to print instead of file names
.Ar file2
and
.Ar file3 .
.It Fl m, Fl Fl merge
Merge output instead of generating ed script.
.It Fl T, Fl Fl initial-tab
In the normal listing,
use a tab instead of two spaces

View File

@ -153,6 +153,7 @@ static void prange(struct range *, bool);
static void repos(int);
static void edscript(int) __dead2;
static void Ascript(int) __dead2;
static void mergescript(int) __dead2;
static void increase(void);
static void usage(void) __dead2;
static void printrange(FILE *, struct range *);
@ -389,7 +390,9 @@ merge(int m1, int m2)
}
}
if (Aflag)
if (mflag)
mergescript(j);
else if (Aflag)
Ascript(j);
else if (eflag)
edscript(j);
@ -687,6 +690,86 @@ Ascript(int n)
exit(overlapcnt > 0);
}
/*
* Output the merged file directly (don't generate an ed script). When
* regurgitating diffs we need to walk forward through the file and print any
* inbetween lines.
*/
static void
mergescript(int i)
{
struct range r;
int n;
r.from = 1;
r.to = 1;
for (n = 1; n < i+1; n++) {
/* print any lines leading up to here */
r.to = de[n].old.from;
printrange(fp[0], &r);
if (de[n].type == DIFF_TYPE2) {
printf("%s %s\n", oldmark, f2mark);
printrange(fp[1], &de[n].old);
printf("=======\n");
printrange(fp[2], &de[n].new);
printf("%s %s\n", newmark, f3mark);
} else if (de[n].type == DIFF_TYPE3) {
if (!oflag || !overlap[n]) {
printrange(fp[2], &de[n].new);
} else {
printf("%s %s\n", oldmark, f1mark);
printrange(fp[0], &de[n].old);
printf("%s %s\n", orgmark, f2mark);
if (de[n].old.from == de[n].old.to) {
struct range or;
or.from = de[n].old.from -1;
or.to = de[n].new.to;
printrange(fp[1], &or);
} else
printrange(fp[1], &de[n].old);
printf("=======\n");
printrange(fp[2], &de[n].new);
printf("%s %s\n", newmark, f3mark);
}
}
if (de[n].old.from == de[n].old.to)
r.from = de[n].new.to;
else
r.from = de[n].old.to;
}
/*
* Print from the final range to the end of 'myfile'. Any deletions or
* additions to this file should have been handled by now.
*
* If the ranges are the same we need to rewind a line.
* If the new range is 0 length (from == to), we need to use the old
* range.
*/
if ((de[n-1].old.from == de[n-1].new.from) &&
(de[n-1].old.to == de[n-1].new.to))
r.from--;
else if (de[n-1].new.from == de[n-1].new.to)
r.from = de[n-1].old.from;
/*
* If the range is a 3 way merge then we need to skip a line in the
* trailing output.
*/
if (de[n-1].type == DIFF_TYPE3)
r.from++;
r.to = INT_MAX;
printrange(fp[0], &r);
exit(overlapcnt > 0);
}
static void
increase(void)
{

View File

@ -22,6 +22,7 @@ ${PACKAGE}FILES+= \
8.out \
9.out \
long-ed.out \
long-A.out
long-A.out \
long-merge.out \
.include <bsd.test.mk>

View File

@ -4,6 +4,7 @@ atf_test_case diff3
atf_test_case diff3_lesssimple
atf_test_case diff3_ed
atf_test_case diff3_A
atf_test_case diff3_merge
diff3_body()
{
@ -56,10 +57,22 @@ diff3_A_body()
diff3 -A -L long-m.txt -L long-o.txt -L long-y.txt $(atf_get_srcdir)/long-m.txt $(atf_get_srcdir)/long-o.txt $(atf_get_srcdir)/long-y.txt
}
diff3_merge_body()
{
atf_check -s exit:1 -o file:$(atf_get_srcdir)/9.out \
diff3 -m -L 1 -L 2 -L 3 $(atf_get_srcdir)/1.txt $(atf_get_srcdir)/2.txt $(atf_get_srcdir)/3.txt
atf_check -s exit:1 -o file:$(atf_get_srcdir)/tao-merge.out \
diff3 -m -L lao.txt -L tzu.txt -L tao.txt $(atf_get_srcdir)/lao.txt $(atf_get_srcdir)/tzu.txt $(atf_get_srcdir)/tao.txt
atf_check -s exit:1 -o file:$(atf_get_srcdir)/long-merge.out \
diff3 -m -L long-m.txt -L long-o.txt -L long-y.txt $(atf_get_srcdir)/long-m.txt $(atf_get_srcdir)/long-o.txt $(atf_get_srcdir)/long-y.txt
}
atf_init_test_cases()
{
atf_add_test_case diff3
# atf_add_test_case diff3_lesssimple
atf_add_test_case diff3_ed
atf_add_test_case diff3_A
atf_add_test_case diff3_merge
}

View File

@ -0,0 +1,35 @@
This is a long file
These lines are the same in all three files
These lines are the same in all three files
This line is different in mine, not better
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
This line is different in yours, much butter
These lines are the same in all three files
These lines are the same in all three files
<<<<<<< long-o.txt
This line is different in yours and mine, but the same
=======
This line is different in yours and mine, the same is in both
>>>>>>> long-y.txt
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
<<<<<<< long-m.txt
This line is different in yours and mine, best change in mine
||||||| long-o.txt
This line is different in yours and mine, but the different in each
=======
This line is different in yours and mine, but the best in yours
>>>>>>> long-y.txt
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files
These lines are the same in all three files