/* * 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 #include #include #include #include #include #include #include #include #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 }