git: 034dd2d54f2e - main - diff3: Add support for -m

From: Tom Jones <thj_at_FreeBSD.org>
Date: Tue, 19 Apr 2022 15:25:14 UTC
The branch main has been updated by thj:

URL: https://cgit.FreeBSD.org/src/commit/?id=034dd2d54f2e7e33897cfd5ede7c50b3d67d18d3

commit 034dd2d54f2e7e33897cfd5ede7c50b3d67d18d3
Author:     Tom Jones <thj@FreeBSD.org>
AuthorDate: 2022-04-19 15:20:24 +0000
Commit:     Tom Jones <thj@FreeBSD.org>
CommitDate: 2022-04-19 15:20:24 +0000

    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.
---
 usr.bin/diff3/diff3.1              |  4 +-
 usr.bin/diff3/diff3.c              | 85 +++++++++++++++++++++++++++++++++++++-
 usr.bin/diff3/tests/Makefile       |  3 +-
 usr.bin/diff3/tests/diff3_test.sh  | 13 ++++++
 usr.bin/diff3/tests/long-merge.out | 35 ++++++++++++++++
 5 files changed, 137 insertions(+), 3 deletions(-)

diff --git a/usr.bin/diff3/diff3.1 b/usr.bin/diff3/diff3.1
index f0fd481c75f1..6968af44bfbd 100644
--- a/usr.bin/diff3/diff3.1
+++ b/usr.bin/diff3/diff3.1
@@ -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
diff --git a/usr.bin/diff3/diff3.c b/usr.bin/diff3/diff3.c
index 5df6357065fc..629e23288875 100644
--- a/usr.bin/diff3/diff3.c
+++ b/usr.bin/diff3/diff3.c
@@ -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)
 {
diff --git a/usr.bin/diff3/tests/Makefile b/usr.bin/diff3/tests/Makefile
index fc69dea260e8..0785e78aaee3 100644
--- a/usr.bin/diff3/tests/Makefile
+++ b/usr.bin/diff3/tests/Makefile
@@ -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>
diff --git a/usr.bin/diff3/tests/diff3_test.sh b/usr.bin/diff3/tests/diff3_test.sh
index 8df84b0d8ff2..c30f258128af 100755
--- a/usr.bin/diff3/tests/diff3_test.sh
+++ b/usr.bin/diff3/tests/diff3_test.sh
@@ -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
 }
diff --git a/usr.bin/diff3/tests/long-merge.out b/usr.bin/diff3/tests/long-merge.out
new file mode 100644
index 000000000000..5139a48fa429
--- /dev/null
+++ b/usr.bin/diff3/tests/long-merge.out
@@ -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