/*
* fhist - file history and comparison tools
* Copyright (C) 1991-1994, 1997-2000, 2002, 2005 Peter Miller;
* All rights reserved.
*
* Derived from a work
* Copyright (C) 1990 David I. Bell.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
*
* MANIFEST: functions to merge differences from a base file.
*/
#include <ac/stdio.h>
#include <ac/string.h>
#include <ac/libintl.h>
#include <cmalloc.h>
#include <compare.h>
#include <error_intl.h>
#include <fcheck.h>
#include <fileio.h>
#include <work.h>
#define CONFLICT_BEGIN \
"/-/-/-/-/-/-/-/-/-/ BEGIN CONFLICT [O%ld A%ld B%ld] /-/-/-/-/-/-/-/-/-/-/\n"
#define CONFLICT_MIDDLE \
"/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/\n"
#define CONFLICT_END \
"/-/-/-/-/-/-/-/-/-/-/-/-/ END CONFLICT /-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/\n"
#define F_OLDDATA 0x0000 /* lines the same as the old ones */
#define F_DELETEA 0x0001 /* lines deleted by file A */
#define F_INSERTA 0x0002 /* lines inserted by file A */
#define F_DELETEB 0x0004 /* lines deleted by file B */
#define F_INSERTB 0x0008 /* lines inserted by file B */
#define F_CONFLICT 0x0010 /* this line is a conflict */
#define MAXSHORT 0x7fff /* highest positive short value */
typedef struct MERGE MERGE;
struct MERGE
{
long m_baseline; /* line number in base file */
long m_fileAline; /* line number in file A */
long m_fileBline; /* line number in file B */
short m_basecount; /* lines in base file used or deleted */
short m_fileAcount; /* number of lines inserted by file A */
short m_fileBcount; /* number of lines inserted by file B */
short m_flags; /* what is happening to this line */
MERGE *m_next; /* next line */
};
long conflicts; /* number of conflicts */
long failcount; /* maximum conflicts allowable */
long unchangecount; /* maximum unchanged lines to output */
short ignoreflag; /* ignore conflicts */
short ignore_identical; /* ignore identical conflicts */
static MERGE *begmergelist; /* head of merge list */
static MERGE *endmergelist; /* end of merge list */
static LINE **baselines; /* lines of base file */
static LINE **fileAlines; /* lines of file A */
static LINE **fileBlines; /* lines of file B */
/*
* Allocate a new merge structure and append it to the end of the merge list.
*/
static MERGE *
newmerge(void)
{
MERGE *mp; /* new merge structure */
mp = (MERGE *)cm_alloc_and_check(sizeof(MERGE));
mp->m_next = NULL;
mp->m_flags = 0;
mp->m_baseline = 0;
mp->m_fileAline = 0;
mp->m_fileBline = 0;
mp->m_basecount = 0;
mp->m_fileAcount = 0;
mp->m_fileBcount = 0;
if (begmergelist == NULL)
begmergelist = mp;
else
endmergelist->m_next = mp;
endmergelist = mp;
return mp;
}
/*
* Merge differences between two divergent files, based on a common
* precursor file. The resulting output is saved in memory so that it
* can be examined or output later.
*/
void
fmerge(char *basename, char *nameA, char *nameB)
{
SNAKE *spA; /* snake for edits of first file */
SNAKE *spB; /* snake for edits of second file */
MERGE *mp; /* current merge structure */
SNAKE *fileAsnakes; /* snake for first edit */
SNAKE *fileBsnakes; /* snake for second edit */
long baseline; /* current line of base file */
long lineA; /* current line of file A */
long lineB; /* current line of file B */
long deletesA; /* deletes needed for file A */
long insertsA; /* inserts needed for file A */
long deletesB; /* deletes needed for file B */
long insertsB; /* inserts needed for file B */
long count; /* number of lines being examined */
long elements; /* number of elements used */
int bad; /* 1 if bad merge detected */
fc.maxjoin = 0;
fc.maxchanges = INFINITY;
fcomp(basename, nameA);
baselines = fc.fileA.f_lines;
fileAlines = fc.fileB.f_lines;
fc.fileB.f_lines = NULL;
fileAsnakes = fc.snakelist;
fc.snakelist = NULL;
fcomp(NULL, nameB);
fileBsnakes = fc.snakelist;
fileBlines = fc.fileB.f_lines;
fc.fileB.f_lines = NULL;
fc.snakelist = NULL;
/*
* Now walk through the snakes and merge the lines.
*/
#if 0
if (fc.verbosity > VERBOSE_DEFAULT)
error_raw("[Creating merge list]");
#endif
spA = fileAsnakes;
spB = fileBsnakes;
baseline = 0;
lineA = 0;
lineB = 0;
conflicts = 0;
elements = 0;
begmergelist = NULL;
endmergelist = NULL;
for (;;)
{
int det;
deletesA = spA->line1 - baseline;
insertsA = spA->line2 - lineA;
deletesB = spB->line1 - baseline;
insertsB = spB->line2 - lineB;
if (deletesA > MAXSHORT)
deletesA = MAXSHORT;
if (insertsA > MAXSHORT)
insertsA = MAXSHORT;
if (deletesB > MAXSHORT)
deletesB = MAXSHORT;
if (insertsB > MAXSHORT)
insertsB = MAXSHORT;
mp = newmerge();
mp->m_baseline = baseline;
mp->m_fileAline = lineA;
mp->m_fileBline = lineB;
mp->m_fileAcount = insertsA;
mp->m_fileBcount = insertsB;
#if 0
if ((fc.verbosity > VERBOSE_DEFAULT) && ((++elements % 1000) == 0))
error_raw("[%ld merge elements]", elements);
#endif
bad = ((deletesA < 0) || (insertsA < 0) ||
(deletesB < 0) || (insertsB < 0));
det =
(
(deletesA > 0 ? F_DELETEA : 0)
+
(insertsA > 0 ? F_INSERTA : 0)
+
(deletesB > 0 ? F_DELETEB : 0)
+
(insertsB > 0 ? F_INSERTB : 0)
);
if (fc.debugflag || bad)
{
#if 0
error_raw
(
"SA %ld %ld %ld, SB %ld %ld %ld, Lines %ld %ld %ld, Case %ld",
spA->line1,
spA->line2,
spA->count,
spB->line1,
spB->line2,
spB->count,
baseline,
lineA,
lineB,
det
);
#endif
if (bad)
fatal_intl(0, i18n("bad merge calculation"));
}
switch (det)
{
case 0:
/* no inserts or deletes at all */
if ((spA->next == NULL) && (spB->next == NULL))
{
#if 0
if (fc.verbosity > VERBOSE_DEFAULT)
error_raw("[%ld merge elements used]", elements);
#endif
if (ignoreflag)
conflicts = 0;
return;
}
count = spA->count;
if (count > spB->count)
count = spB->count;
if (count > MAXSHORT)
count = MAXSHORT;
mp->m_flags = F_OLDDATA;
mp->m_basecount = count;
mp->m_fileAcount = count;
mp->m_fileBcount = count;
spA->line1 += count;
spA->line2 += count;
spA->count -= count;
spB->line1 += count;
spB->line2 += count;
spB->count -= count;
break;
case F_INSERTA:
/* insert for file A */
mp->m_flags = F_INSERTA;
break;
case (F_INSERTA | F_DELETEA):
/* insert and delete for file A */
mp->m_flags = F_INSERTA;
/* Fall through... */
case F_DELETEA:
/* delete for file A */
count = deletesA;
if (count > spB->count)
count = spB->count;
mp->m_flags |= F_DELETEA;
mp->m_basecount = count;
mp->m_fileBcount = count;
spB->line1 += count;
spB->line2 += count;
spB->count -= count;
break;
case (F_INSERTB | F_DELETEB):
/* insert and delete for file B */
mp->m_flags = F_INSERTB;
/* Fall through... */
case F_DELETEB:
/* delete for file B */
count = deletesB;
if (count > spA->count)
count = spA->count;
mp->m_flags |= F_DELETEB;
mp->m_basecount = count;
mp->m_fileAcount = count;
spA->line1 += count;
spA->line2 += count;
spA->count -= count;
break;
case (F_DELETEA | F_DELETEB):
/* files A and B both delete */
count = deletesA;
if (count > deletesB)
count = deletesB;
mp->m_flags |= F_DELETEA | F_DELETEB;
mp->m_basecount = count;
if (ignore_identical)
{
/*
* This is not an actual conflict, even though it
* is a logical conflict, according to most users'
* expectations.
*/
break;
}
mp->m_flags |= F_CONFLICT;
conflicts += count;
break;
case (F_INSERTA | F_DELETEA | F_DELETEB):
/* file A inserts and deletes, file B deletes */
count = deletesA;
if (count > deletesB)
count = deletesB;
mp->m_flags = (F_INSERTA | F_DELETEA | F_DELETEB | F_CONFLICT);
mp->m_basecount = count;
conflicts += count;
break;
case F_INSERTB:
/* insert for file B */
mp->m_flags = F_INSERTB;
break;
case (F_INSERTA | F_INSERTB):
/* file A and file B both insert */
if (ignore_identical)
{
for
(
count = 0;
count < mp->m_fileAcount && count < mp->m_fileBcount;
++count
)
{
LINE *lp_a = fileAlines[mp->m_fileAline + count];
LINE *lp_b = fileBlines[mp->m_fileBline + count];
if (0 != strcmp(lp_a->l_data, lp_b->l_data))
break;
}
if (count > 0)
{
/*
* This is not an actual conflict according to most
* users' expectations, even though it is a logical
* conflict.
*/
mp->m_flags = F_INSERTA;
mp->m_basecount = 0;
mp->m_fileAcount = count;
mp->m_fileBcount = count;
break;
}
}
mp->m_flags = F_INSERTA | F_INSERTB | F_CONFLICT;
conflicts++;
break;
case (F_INSERTA | F_DELETEA | F_INSERTB):
/* insert and delete for A, insert for B */
mp->m_flags = F_INSERTA;
/* Fall through... */
case (F_DELETEA | F_INSERTB):
/* delete for file A and insert for file B */
mp->m_flags |= (F_DELETEA | F_INSERTB | F_CONFLICT);
mp->m_basecount = 1;
spB->line1++;
spB->line2++;
spB->count--;
lineB++;
conflicts++;
break;
case (F_DELETEA | F_INSERTB | F_DELETEB):
/* file A deletes, file B inserts and deletes */
count = deletesA;
if (count > deletesB)
count = deletesB;
mp->m_flags = (F_DELETEA | F_INSERTB | F_DELETEB | F_CONFLICT);
mp->m_basecount = count;
conflicts += count;
break;
case (F_INSERTA | F_INSERTB | F_DELETEB):
/* insert for A, insert and delete for B */
mp->m_flags = F_INSERTB;
/* Fall through... */
case (F_INSERTA | F_DELETEB):
/* insert for file A and delete for file B */
mp->m_flags |= (F_INSERTA | F_DELETEB | F_CONFLICT);
mp->m_basecount = 1;
spA->line1++;
spA->line2++;
spA->count--;
lineA++;
conflicts++;
break;
case (F_INSERTA | F_DELETEA | F_INSERTB | F_DELETEB):
/* insert and delete for A and for B */
count = deletesA;
if (count > deletesB)
count = deletesB;
mp->m_basecount = count;
if (deletesA == deletesB && ignore_identical)
{
for
(
count = 0;
count < mp->m_fileAcount && count < mp->m_fileBcount;
++count
)
{
LINE *lp_a = fileAlines[mp->m_fileAline + count];
LINE *lp_b = fileBlines[mp->m_fileBline + count];
if (0 != strcmp(lp_a->l_data, lp_b->l_data))
break;
}
if (count > 0)
{
/*
* This is not an actual conflict according to most
* users' expectations, even though it is a logical
* conflict.
*/
mp->m_flags = F_INSERTA | F_DELETEA | F_DELETEB;
break;
}
}
mp->m_flags =
(F_INSERTA | F_DELETEA | F_INSERTB | F_DELETEB | F_CONFLICT);
conflicts += count;
break;
}
if ((conflicts > failcount) && !ignoreflag)
{
sub_context_ty *scp;
scp = sub_context_new();
sub_var_set_long(scp, "Number", failcount);
fatal_intl
(
scp,
i18n("more than $number conflicts found during merge")
);
}
baseline += mp->m_basecount;
lineA += mp->m_fileAcount;
lineB += mp->m_fileBcount;
if ((spA->count <= 0) && (spA->next))
spA = spA->next;
if ((spB->count <= 0) && (spB->next))
spB = spB->next;
}
}
/*
* Walk through the merge list and output the resulting file.
* Conflicts will be identified by conflict identification lines.
*/
void
dumpmergedfile(char *outputname)
{
MERGE *mp; /* current merge structure */
LINE **lp; /* current line */
FILE *fp; /* file to output to (or TERMINAL) */
long count; /* line count */
int conflict; /* 1 if inside of a conflict */
int newconflict; /* new conflict value */
#if 0
if ((fc.verbosity > VERBOSE_DEFAULT) && outputname)
error_raw("[Writing merged lines to \"%s\"]", outputname);
#endif
if (outputname)
{
fp = fopen_and_check(outputname, "w");
}
else
{
fp = stdout;
outputname = gettext("standard output");
}
conflict = 0;
for (mp = begmergelist; mp; mp = mp->m_next)
{
newconflict = ((mp->m_flags & F_CONFLICT) && !ignoreflag);
if (newconflict != conflict)
{
conflict = newconflict;
fprintf
(
fp,
conflict ? CONFLICT_BEGIN : CONFLICT_END,
mp->m_baseline + 1,
mp->m_fileAline + 1,
mp->m_fileBline + 1
);
}
if (mp->m_flags == F_OLDDATA)
{
lp = &baselines[mp->m_baseline];
for (count = mp->m_basecount; count > 0; count--)
fputs((*lp++)->l_data, fp);
continue;
}
if (mp->m_flags & F_INSERTA)
{
lp = &fileAlines[mp->m_fileAline];
for (count = mp->m_fileAcount; count > 0; count--)
fputs((*lp++)->l_data, fp);
}
if (conflict && (mp->m_flags & F_INSERTA) && (mp->m_flags & F_INSERTB))
fputs(CONFLICT_MIDDLE, fp);
if (mp->m_flags & F_INSERTB)
{
lp = &fileBlines[mp->m_fileBline];
for (count = mp->m_fileBcount; count > 0; count--)
fputs((*lp++)->l_data, fp);
}
}
if (conflict)
fputs(CONFLICT_END, fp);
fflush_and_check(fp, outputname);
fclose_and_check(fp, outputname);
}
/*
* Walk down and dump out a list of merges, either for non-conflicts,
* or for either file A or B of a conflict. Returns the first merge
* structure not processed due to the change of conflict.
*/
static MERGE *
walkmerges(FILE *fp, MERGE *mp, int flagmask, const char *filename)
{
LINE **lp; /* current line */
short count; /* number of lines to handle */
short flags; /* flags to be checked */
for (;; mp = mp->m_next)
{
if
(
(mp == NULL)
||
((flagmask & F_CONFLICT) == (mp->m_flags & F_CONFLICT))
)
return mp;
flags = mp->m_flags;
if (flags == F_OLDDATA)
{
/* show original lines */
lp = &baselines[mp->m_baseline];
count = mp->m_basecount;
if (((count - 1) / 2) < unchangecount)
{
while (--count >= 0)
{
writefx(fp, " ", 3L, filename);
fputs((*lp++)->l_data, fp);
}
continue;
}
/*
* Number of unchanged lines exceeds specified limit.
* So only show the first few and last few changes.
*/
for (count = unchangecount; count > 0; count--)
{
writefx(fp, " ", 3L, filename);
fputs((*lp++)->l_data, fp);
}
fprintf(fp, "U %ld\n", mp->m_basecount - (unchangecount * 2));
lp = &baselines[mp->m_baseline + mp->m_basecount - unchangecount];
for (count = unchangecount; count > 0; count--)
{
writefx(fp, " ", 3L, filename);
fputs((*lp++)->l_data, fp);
}
continue;
}
flags &= flagmask;
if (flags & F_INSERTA)
{
lp = &fileAlines[mp->m_fileAline];
for (count = mp->m_fileAcount; count > 0; count--)
{
writefx(fp, "IA ", 3L, filename);
fputs((*lp++)->l_data, fp);
}
}
if (flags & F_DELETEA)
{
lp = &baselines[mp->m_baseline];
for (count = mp->m_basecount; count > 0; count--)
{
writefx(fp, "DA ", 3L, filename);
fputs((*lp++)->l_data, fp);
}
}
if (flags & F_INSERTB)
{
lp = &fileBlines[mp->m_fileBline];
for (count = mp->m_fileBcount; count > 0; count--)
{
writefx(fp, "IB ", 3L, filename);
fputs((*lp++)->l_data, fp);
}
}
if (flags & F_DELETEB)
{
lp = &baselines[mp->m_baseline];
for (count = mp->m_basecount; count > 0; count--)
{
writefx(fp, "DB ", 3L, filename);
fputs((*lp++)->l_data, fp);
}
}
}
}
/*
* Dump out the possible conflicts to a file that indicates what both
* divergent files did to the base file. This is useful even if there
* are no physical conflicts in order to manually find logical conflicts.
*/
void
dumpconflicts(char *outputname)
{
MERGE *mp; /* current merge structure */
FILE *fp; /* file to output to */
#if 0
if ((fc.verbosity > VERBOSE_DEFAULT) && outputname)
error_raw("[Writing conflict lines to \"%s\"]", outputname);
#endif
if (outputname)
{
fp = fopen_and_check(outputname, "w");
}
else
{
fp = stdout;
outputname = gettext("standard output");
}
fprintf(fp, "T %ld conflict%s\n", conflicts, (conflicts == 1) ? "" : "s");
mp = begmergelist;
while (mp)
{
/* non-conflicts */
mp = walkmerges(fp, mp, -1, outputname);
if (mp == NULL)
break;
if (!ignoreflag)
{
writefx(fp, "X ", 3L, outputname);
fprintf(fp, CONFLICT_BEGIN, mp->m_baseline + 1,
mp->m_fileAline + 1, mp->m_fileBline + 1);
}
/* file A conflicts */
walkmerges(fp, mp, F_INSERTA | F_DELETEA, outputname);
if (!ignoreflag)
fprintf(fp, "X %s", CONFLICT_MIDDLE);
/* file B conflicts */
mp = walkmerges(fp, mp, F_INSERTB | F_DELETEB, outputname);
if (!ignoreflag)
fprintf(fp, "X %s", CONFLICT_END);
}
fflush_and_check(fp, outputname);
fclose_and_check(fp, outputname);
}
/*
* Read in a conflict file and produce an output file.
*/
void
convertconflicts(char *inputname, char *outputname)
{
char *cp; /* current line */
FILE *ip; /* input file */
FILE *op; /* output file */
long inputlines;
long outputlines; /* input and output line counts */
long retlen; /* length of current line */
sub_context_ty *scp;
#if 0
if (fc.verbosity > VERBOSE_DEFAULT && outputname)
error_raw("[Converting conflict file \"%s\" to output file \"%s\"]",
inputname, outputname);
#endif
inputlines = 0;
outputlines = 0;
ip = fopen_and_check(inputname, "r");
if (outputname)
{
op = fopen_and_check(outputname, "w");
}
else
{
op = stdout;
outputname = gettext("standard output");
}
for (;;)
{
int bin;
bin = 0;
cp = readlinef(ip, &retlen, 0, inputname, &bin);
if (cp == NULL)
break;
if (bin)
binary_fatal(inputname);
#if 0
if (((++inputlines % 1000) == 0) && (fc.verbosity > VERBOSE_DEFAULT))
error_raw("[%ld lines]", inputlines);
#endif
switch (*cp)
{
case 'I': /* inserted lines */
case ' ': /* original base lines */
cp += 3;
retlen -= 3;
if (retlen <= 0)
{
cp = "\n";
retlen = 1;
}
writefx(op, cp, retlen, outputname);
outputlines++;
break;
case '\n': /* original blank line */
writefx(op, "\n", 1L, outputname);
break;
case 'D': /* deleted lines */
case 'T': /* the total line */
break;
case 'X':
if (ignoreflag)
break;
scp = sub_context_new();
sub_var_set_long(scp, "Number", inputlines);
fatal_with_filename
(
inputname,
scp,
i18n("unresolved conflict remaining at line $number")
);
case 'U':
fatal_with_filename
(
inputname,
0,
i18n("incomplete due to -u option being used")
);
default:
scp = sub_context_new();
sub_var_set_long(scp, "Number", inputlines);
fatal_with_filename
(
inputname,
scp,
i18n("unknown line $number read")
);
}
}
fclose_and_check(ip, inputname);
fflush_and_check(op, outputname);
fclose_and_check(op, outputname);
#if 0
if (fc.verbosity > VERBOSE_DEFAULT)
error_raw("[%ld lines read, %ld lines written]",
inputlines, outputlines);
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1