diff3: fix merge mode

Make the merge mode compatible with GNU diff3
Add tests for all the changes, those tests are extracted from the
etcupdate testsuite.

This version passes the etcupdate testsuite and the diffutils diff3
test suite.

MFC After:	1 week
This commit is contained in:
Baptiste Daroussin
2026-02-13 17:10:44 +01:00
parent a8b8feced9
commit 2cfca8e710
15 changed files with 168 additions and 30 deletions
+25 -26
View File
@@ -150,7 +150,7 @@ static void repos(int);
static void separate(const char *); static void separate(const char *);
static void edscript(int) __dead2; static void edscript(int) __dead2;
static void Ascript(int) __dead2; static void Ascript(int) __dead2;
static void mergescript(int) __dead2; static void mergescript(int, int) __dead2;
static void increase(void); static void increase(void);
static void usage(void); static void usage(void);
static void printrange(FILE *, struct range *); static void printrange(FILE *, struct range *);
@@ -361,12 +361,13 @@ merge(int m1, int m2)
{ {
struct diff *d1, *d2, *d3; struct diff *d1, *d2, *d3;
int j, t1, t2; int j, t1, t2;
int f1f3delta;
bool dup = false; bool dup = false;
d1 = d13; d1 = d13;
d2 = d23; d2 = d23;
j = 0; j = 0;
int f1f3delta = 0; f1f3delta = 0;
for (;;) { for (;;) {
t1 = (d1 < d13 + m1); t1 = (d1 < d13 + m1);
@@ -382,6 +383,12 @@ merge(int m1, int m2)
change(1, &d1->old, false); change(1, &d1->old, false);
keep(2, &d1->new); keep(2, &d1->new);
change(3, &d1->new, false); change(3, &d1->new, false);
} else if (mflag) {
j++;
de[j].type = DIFF_TYPE1;
de[j].old = d1->old;
de[j].new = d1->new;
overlap[j] = 0;
} else if (eflag == EFLAG_OVERLAP) { } else if (eflag == EFLAG_OVERLAP) {
j = edit(d2, dup, j, DIFF_TYPE1); j = edit(d2, dup, j, DIFF_TYPE1);
} }
@@ -437,6 +444,14 @@ merge(int m1, int m2)
change(2, &d2->old, false); change(2, &d2->old, false);
d3 = d1->old.to > d1->old.from ? d1 : d2; d3 = d1->old.to > d1->old.from ? d1 : d2;
change(3, &d3->new, false); change(3, &d3->new, false);
} else if (mflag) {
j++;
de[j].type = DIFF_TYPE3;
de[j].old = d1->old;
de[j].new = d1->new;
overlap[j] = !dup;
if (!dup)
overlapcnt++;
} else { } else {
j = edit(d1, dup, j, DIFF_TYPE3); j = edit(d1, dup, j, DIFF_TYPE3);
} }
@@ -468,7 +483,7 @@ merge(int m1, int m2)
} }
if (mflag) if (mflag)
mergescript(j); mergescript(j, f1f3delta);
else if (Aflag) else if (Aflag)
Ascript(j); Ascript(j);
else if (eflag) else if (eflag)
@@ -701,7 +716,7 @@ edscript(int n)
if (iflag) if (iflag)
printf("w\nq\n"); printf("w\nq\n");
exit(eflag == EFLAG_NONE ? overlapcnt : 0); exit(oflag ? overlapcnt > 0 : 0);
} }
/* /*
@@ -795,11 +810,10 @@ Ascript(int n)
* inbetween lines. * inbetween lines.
*/ */
static void static void
mergescript(int i) mergescript(int i, int f1f3delta)
{ {
struct range r, *new, *old; struct range r, *new, *old;
int n; int n;
bool delete = false;
r.from = 1; r.from = 1;
r.to = 1; r.to = 1;
@@ -812,13 +826,9 @@ mergescript(int i)
* Print any lines leading up to here. If we are merging don't * Print any lines leading up to here. If we are merging don't
* print deleted ranges. * print deleted ranges.
*/ */
delete = (new->from == new->to); if (de[n].type == DIFF_TYPE1)
if (de[n].type == DIFF_TYPE1 && delete) r.to = old->to;
r.to = new->from - 1; else if (de[n].type == DIFF_TYPE2)
else if (de[n].type == DIFF_TYPE3 && (old->from == old->to)) {
r.from = old->from - 1;
r.to = new->from;
} else if (de[n].type == DIFF_TYPE2)
r.to = new->from + de_delta[n]; r.to = new->from + de_delta[n];
else else
r.to = old->from; r.to = old->from;
@@ -826,9 +836,7 @@ mergescript(int i)
printrange(fp[0], &r); printrange(fp[0], &r);
switch (de[n].type) { switch (de[n].type) {
case DIFF_TYPE1: case DIFF_TYPE1:
/* If this isn't a delete print it */ /* Content included in "between" printing from fp[0] */
if (!delete)
printrange(fp[2], new);
break; break;
case DIFF_TYPE2: case DIFF_TYPE2:
printf("%s %s\n", oldmark, f2mark); printf("%s %s\n", oldmark, f2mark);
@@ -870,8 +878,6 @@ mergescript(int i)
if (de[n].type == DIFF_TYPE2) if (de[n].type == DIFF_TYPE2)
r.from = new->to + de_delta[n]; r.from = new->to + de_delta[n];
else if (old->from == old->to)
r.from = new->to;
else else
r.from = old->to; r.from = old->to;
} }
@@ -879,18 +885,11 @@ mergescript(int i)
/* /*
* Print from the final range to the end of 'myfile'. Any deletions or * Print from the final range to the end of 'myfile'. Any deletions or
* additions to this file should have been handled by now. * 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 new
* range.
*/ */
new = &de[n-1].new; new = &de[n-1].new;
old = &de[n-1].old; old = &de[n-1].old;
if (old->from == new->from && old->to == new->to) r.from -= f1f3delta;
r.from--;
else if (new->from == new->to)
r.from = new->from;
r.to = INT_MAX; r.to = INT_MAX;
printrange(fp[2], &r); printrange(fp[2], &r);
+15 -1
View File
@@ -23,6 +23,20 @@ ${PACKAGE}FILES+= \
long-A.out \ long-A.out \
long-merge.out \ long-merge.out \
fbsdid1.txt \ fbsdid1.txt \
fbsdid2.txt fbsdid2.txt \
conflict1.txt \
conflict2.txt \
conflict3.txt \
conflict-merge.out \
simple1.txt \
simple2.txt \
simple3.txt \
simple-merge.out \
simple-Em.out \
conflict-Em.out \
passwd-test.txt \
passwd-old.txt \
passwd-new.txt \
passwd-Em.out
.include <bsd.test.mk> .include <bsd.test.mk>
+5
View File
@@ -0,0 +1,5 @@
# root: me@my.domain
# Basic system aliases -- these MUST be present
MAILER-DAEMON: postmaster
postmaster: root
+9
View File
@@ -0,0 +1,9 @@
#root:me@my.domain
#Basicsystemaliases--theseMUSTbepresent
MAILER-DAEMON:postmaster
postmaster:root
#Generalredirectionsforpseudoaccounts
_dhcp:root
_pflogd:root
+5
View File
@@ -0,0 +1,5 @@
root:someone@example.com
#Basicsystemaliases--theseMUSTbepresent
MAILER-DAEMON:postmaster
postmaster:root
+42 -3
View File
@@ -5,6 +5,11 @@ atf_test_case diff3_ed
atf_test_case diff3_A atf_test_case diff3_A
atf_test_case diff3_merge atf_test_case diff3_merge
atf_test_case diff3_E_merge atf_test_case diff3_E_merge
atf_test_case diff3_merge_conflict
atf_test_case diff3_merge_simple
atf_test_case diff3_Em_simple
atf_test_case diff3_Em_conflict
atf_test_case diff3_Em_insert
diff3_body() diff3_body()
{ {
@@ -20,10 +25,10 @@ diff3_body()
atf_check -o file:$(atf_get_srcdir)/2.out \ atf_check -o file:$(atf_get_srcdir)/2.out \
diff3 -e $(atf_get_srcdir)/1.txt $(atf_get_srcdir)/2.txt $(atf_get_srcdir)/3.txt diff3 -e $(atf_get_srcdir)/1.txt $(atf_get_srcdir)/2.txt $(atf_get_srcdir)/3.txt
atf_check -o file:$(atf_get_srcdir)/3.out \ atf_check -s exit:1 -o file:$(atf_get_srcdir)/3.out \
diff3 -E -L 1 -L 2 -L 3 $(atf_get_srcdir)/1.txt $(atf_get_srcdir)/2.txt $(atf_get_srcdir)/3.txt diff3 -E -L 1 -L 2 -L 3 $(atf_get_srcdir)/1.txt $(atf_get_srcdir)/2.txt $(atf_get_srcdir)/3.txt
atf_check -o file:$(atf_get_srcdir)/4.out \ atf_check -s exit:1 -o file:$(atf_get_srcdir)/4.out \
diff3 -X -L 1 -L 2 -L 3 $(atf_get_srcdir)/1.txt $(atf_get_srcdir)/2.txt $(atf_get_srcdir)/3.txt diff3 -X -L 1 -L 2 -L 3 $(atf_get_srcdir)/1.txt $(atf_get_srcdir)/2.txt $(atf_get_srcdir)/3.txt
atf_check -o file:$(atf_get_srcdir)/5.out \ atf_check -o file:$(atf_get_srcdir)/5.out \
@@ -75,7 +80,6 @@ expected="<<<<<<< 2
======= =======
# \$FreeBSD: head/local 12345 jhb \$ # \$FreeBSD: head/local 12345 jhb \$
>>>>>>> 3 >>>>>>> 3
# \$FreeBSD: head/local 12345 jhb \$
this is a file this is a file
@@ -97,6 +101,36 @@ these are some local mods to the file
} }
diff3_merge_conflict_body()
{
atf_check -s exit:1 -o file:$(atf_get_srcdir)/conflict-merge.out \
diff3 -m -L conflict3.txt -L conflict1.txt -L conflict2.txt $(atf_get_srcdir)/conflict3.txt $(atf_get_srcdir)/conflict1.txt $(atf_get_srcdir)/conflict2.txt
}
diff3_merge_simple_body()
{
atf_check -s exit:0 -o file:$(atf_get_srcdir)/simple-merge.out \
diff3 -m $(atf_get_srcdir)/simple3.txt $(atf_get_srcdir)/simple1.txt $(atf_get_srcdir)/simple2.txt
}
diff3_Em_simple_body()
{
atf_check -s exit:0 -o file:$(atf_get_srcdir)/simple-Em.out \
diff3 -E -m $(atf_get_srcdir)/simple3.txt $(atf_get_srcdir)/simple1.txt $(atf_get_srcdir)/simple2.txt
}
diff3_Em_conflict_body()
{
atf_check -s exit:1 -o file:$(atf_get_srcdir)/conflict-Em.out \
diff3 -E -m -L conflict3.txt -L conflict1.txt -L conflict2.txt $(atf_get_srcdir)/conflict3.txt $(atf_get_srcdir)/conflict1.txt $(atf_get_srcdir)/conflict2.txt
}
diff3_Em_insert_body()
{
atf_check -s exit:0 -o file:$(atf_get_srcdir)/passwd-Em.out \
diff3 -E -m $(atf_get_srcdir)/passwd-test.txt $(atf_get_srcdir)/passwd-old.txt $(atf_get_srcdir)/passwd-new.txt
}
atf_init_test_cases() atf_init_test_cases()
{ {
atf_add_test_case diff3 atf_add_test_case diff3
@@ -105,4 +139,9 @@ atf_init_test_cases()
atf_add_test_case diff3_A atf_add_test_case diff3_A
atf_add_test_case diff3_merge atf_add_test_case diff3_merge
atf_add_test_case diff3_E_merge atf_add_test_case diff3_E_merge
atf_add_test_case diff3_merge_conflict
atf_add_test_case diff3_merge_simple
atf_add_test_case diff3_Em_simple
atf_add_test_case diff3_Em_conflict
atf_add_test_case diff3_Em_insert
} }
+16
View File
@@ -0,0 +1,16 @@
#
root:<rpass>:0:0::0:0:Charlie &:/root:/bin/csh
toor:*:0:0::0:0:Bourne-again Superuser:/root:
daemon:*:1:1::0:0:Owner of many system processes:/root:/usr/sbin/nologin
operator:*:2:5::0:0:System &:/:/usr/sbin/nologin
_dhcp:*:65:65::0:0:dhcp programs:/var/empty:/usr/sbin/nologin
uucp:*:66:66::0:0:UUCP pseudo-user:/var/spool/uucppublic:/usr/local/libexec/uucp/uucico
pop:*:68:6::0:0:Post Office Owner:/nonexistent:/usr/sbin/nologin
auditdistd:*:78:77::0:0:Auditdistd unprivileged user:/var/empty:/usr/sbin/nologin
www:*:80:80::0:0:World Wide Web Owner:/nonexistent:/usr/sbin/nologin
hast:*:845:845::0:0:HAST unprivileged user:/var/empty:/usr/sbin/nologin
nobody:*:65534:65534::0:0:Unprivileged user:/nonexistent:/usr/sbin/nologin
john:<password>:1001:1001::0:0:John Baldwin:/home/john:/bin/tcsh
messagebus:*:556:556::0:0:D-BUS Daemon User:/nonexistent:/usr/sbin/nologin
polkit:*:562:562::0:0:PolicyKit User:/nonexistent:/usr/sbin/nologin
haldaemon:*:560:560::0:0:HAL Daemon User:/nonexistent:/usr/sbin/nologin
+12
View File
@@ -0,0 +1,12 @@
#
root::0:0::0:0:Charlie &:/root:/bin/csh
toor:*:0:0::0:0:Bourne-again Superuser:/root:
daemon:*:1:1::0:0:Owner of many system processes:/root:/usr/sbin/nologin
operator:*:2:5::0:0:System &:/:/usr/sbin/nologin
_dhcp:*:65:65::0:0:dhcp programs:/var/empty:/usr/sbin/nologin
uucp:*:66:66::0:0:UUCP pseudo-user:/var/spool/uucppublic:/usr/local/libexec/uucp/uucico
pop:*:68:6::0:0:Post Office Owner:/nonexistent:/usr/sbin/nologin
auditdistd:*:78:77::0:0:Auditdistd unprivileged user:/var/empty:/usr/sbin/nologin
www:*:80:80::0:0:World Wide Web Owner:/nonexistent:/usr/sbin/nologin
hast:*:845:845::0:0:HAST unprivileged user:/var/empty:/usr/sbin/nologin
nobody:*:65534:65534::0:0:Unprivileged user:/nonexistent:/usr/sbin/nologin
+11
View File
@@ -0,0 +1,11 @@
#
root::0:0::0:0:Charlie &:/root:/bin/csh
toor:*:0:0::0:0:Bourne-again Superuser:/root:
daemon:*:1:1::0:0:Owner of many system processes:/root:/usr/sbin/nologin
operator:*:2:5::0:0:System &:/:/usr/sbin/nologin
_dhcp:*:65:65::0:0:dhcp programs:/var/empty:/usr/sbin/nologin
uucp:*:66:66::0:0:UUCP pseudo-user:/var/spool/uucppublic:/usr/local/libexec/uucp/uucico
pop:*:68:6::0:0:Post Office Owner:/nonexistent:/usr/sbin/nologin
www:*:80:80::0:0:World Wide Web Owner:/nonexistent:/usr/sbin/nologin
hast:*:845:845::0:0:HAST unprivileged user:/var/empty:/usr/sbin/nologin
nobody:*:65534:65534::0:0:Unprivileged user:/nonexistent:/usr/sbin/nologin
+15
View File
@@ -0,0 +1,15 @@
#
root:<rpass>:0:0::0:0:Charlie &:/root:/bin/csh
toor:*:0:0::0:0:Bourne-again Superuser:/root:
daemon:*:1:1::0:0:Owner of many system processes:/root:/usr/sbin/nologin
operator:*:2:5::0:0:System &:/:/usr/sbin/nologin
_dhcp:*:65:65::0:0:dhcp programs:/var/empty:/usr/sbin/nologin
uucp:*:66:66::0:0:UUCP pseudo-user:/var/spool/uucppublic:/usr/local/libexec/uucp/uucico
pop:*:68:6::0:0:Post Office Owner:/nonexistent:/usr/sbin/nologin
www:*:80:80::0:0:World Wide Web Owner:/nonexistent:/usr/sbin/nologin
hast:*:845:845::0:0:HAST unprivileged user:/var/empty:/usr/sbin/nologin
nobody:*:65534:65534::0:0:Unprivileged user:/nonexistent:/usr/sbin/nologin
john:<password>:1001:1001::0:0:John Baldwin:/home/john:/bin/tcsh
messagebus:*:556:556::0:0:D-BUS Daemon User:/nonexistent:/usr/sbin/nologin
polkit:*:562:562::0:0:PolicyKit User:/nonexistent:/usr/sbin/nologin
haldaemon:*:560:560::0:0:HAL Daemon User:/nonexistent:/usr/sbin/nologin
+3
View File
@@ -0,0 +1,3 @@
this is an new line
this is a local line
+3
View File
@@ -0,0 +1,3 @@
this is an new line
this is a local line
+2
View File
@@ -0,0 +1,2 @@
this is an old line
+2
View File
@@ -0,0 +1,2 @@
this is an new line
+3
View File
@@ -0,0 +1,3 @@
this is an old line
this is a local line