Index: Makefile =================================================================== --- Makefile (revision 0) +++ Makefile (working copy) @@ -0,0 +1,8 @@ +.include + +PROG= merge +SRCS= merge.c merge3.c diff.c + +LIBADD= sbuf + +.include Property changes on: Makefile ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: diff.c =================================================================== --- diff.c (revision 0) +++ diff.c (working copy) @@ -0,0 +1,491 @@ +/* +** Copyright (c) 2007 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** 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. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to compute a "diff" between two +** text files. +*/ +#include +#include + +#include +#include +#include + +#include "merge3.h" + +/* +** Maximum length of a line in a text file, in bytes. (2**13 = 8192 bytes) +*/ +#define LENGTH_MASK_SZ 13 +#define LENGTH_MASK ((1<n) + +/* +** A context for running a raw diff. +** +** The aEdit[] array describes the raw diff. Each triple of integers in +** aEdit[] means: +** +** (1) COPY: Number of lines aFrom and aTo have in common +** (2) DELETE: Number of lines found only in aFrom +** (3) INSERT: Number of lines found only in aTo +** +** The triples repeat until all lines of both aFrom and aTo are accounted +** for. +*/ +typedef struct DContext DContext; +struct DContext { + int *aEdit; /* Array of copy/delete/insert triples */ + int nEdit; /* Number of integers (3x num of triples) in aEdit[] */ + int nEditAlloc; /* Space allocated for aEdit[] */ + DLine *aFrom; /* File on left side of the diff */ + int nFrom; /* Number of lines in aFrom[] */ + DLine *aTo; /* File on right side of the diff */ + int nTo; /* Number of lines in aTo[] */ + int (*same_fn)(const DLine *, const DLine *); /* Function to be used for comparing */ +}; + +/* +** Return an array of DLine objects containing a pointer to the +** start of each line and a hash of that line. The lower +** bits of the hash store the length of each line. +** +** Trailing whitespace is removed from each line. 2010-08-20: Not any +** more. If trailing whitespace is ignored, the "patch" command gets +** confused by the diff output. Ticket [a9f7b23c2e376af5b0e5b] +** +** Return 0 if the file is binary or contains a line that is +** too long. +** +** Profiling show that in most cases this routine consumes the bulk of +** the CPU time on a diff. +*/ +static DLine *break_into_lines(const char *z, int *pnLine){ + int nLine, i, j, k, s, x; + unsigned int h, h2; + DLine *a; + + int n = strlen(z); + /* Count the number of lines. Allocate space to hold + ** the returned array. + */ + for(i=j=0, nLine=1; iLENGTH_MASK ){ + return 0; + } + j = 0; + } + } + if( j>LENGTH_MASK ){ + return 0; + } + a = calloc(nLine, sizeof(a[0]) ); + if( n==0 ){ + *pnLine = 0; + return a; + } + + /* Fill in the array */ + for(i=0; ih==pB->h && memcmp(pA->z,pB->z, pA->h&LENGTH_MASK)==0; +} + + +/* +** Minimum of two values +*/ +static int minInt(int a, int b){ return aaFrom[] */ + int iS2, int iE2, /* Range of lines in p->aTo[] */ + int *piSX, int *piEX, /* Write p->aFrom[] common segment here */ + int *piSY, int *piEY /* Write p->aTo[] common segment here */ +){ + int mxLength = 0; /* Length of longest common subsequence */ + int i, j; /* Loop counters */ + int k; /* Length of a candidate subsequence */ + int iSXb = iS1; /* Best match so far */ + int iSYb = iS2; /* Best match so far */ + + for(i=iS1; isame_fn(&p->aFrom[i], &p->aTo[j]) ) continue; + if( mxLength && !p->same_fn(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){ + continue; + } + k = 1; + while( i+ksame_fn(&p->aFrom[i+k],&p->aTo[j+k]) ){ + k++; + } + if( k>mxLength ){ + iSXb = i; + iSYb = j; + mxLength = k; + } + } + } + *piSX = iSXb; + *piEX = iSXb + mxLength; + *piSY = iSYb; + *piEY = iSYb + mxLength; +} + +/* +** Compare two blocks of text on lines iS1 through iE1-1 of the aFrom[] +** file and lines iS2 through iE2-1 of the aTo[] file. Locate a sequence +** of lines in these two blocks that are exactly the same. Return +** the bounds of the matching sequence. +** +** If there are two or more possible answers of the same length, the +** returned sequence should be the one closest to the center of the +** input range. +** +** Ideally, the common sequence should be the longest possible common +** sequence. However, an exact computation of LCS is O(N*N) which is +** way too slow for larger files. So this routine uses an O(N) +** heuristic approximation based on hashing that usually works about +** as well. But if the O(N) algorithm doesn't get a good solution +** and N is not too large, we fall back to an exact solution by +** calling optimalLCS(). +*/ +static void longestCommonSequence( + DContext *p, /* Two files being compared */ + int iS1, int iE1, /* Range of lines in p->aFrom[] */ + int iS2, int iE2, /* Range of lines in p->aTo[] */ + int *piSX, int *piEX, /* Write p->aFrom[] common segment here */ + int *piSY, int *piEY /* Write p->aTo[] common segment here */ +){ + int i, j, k; /* Loop counters */ + int n; /* Loop limit */ + DLine *pA, *pB; /* Pointers to lines */ + int iSX, iSY, iEX, iEY; /* Current match */ + int skew = 0; /* How lopsided is the match */ + int dist = 0; /* Distance of match from center */ + int mid; /* Center of the span */ + int iSXb, iSYb, iEXb, iEYb; /* Best match so far */ + int iSXp, iSYp, iEXp, iEYp; /* Previous match */ + int64_t bestScore; /* Best score so far */ + int64_t score; /* Score for current candidate LCS */ + int span; /* combined width of the input sequences */ + + span = (iE1 - iS1) + (iE2 - iS2); + bestScore = -10000; + score = 0; + iSXb = iSXp = iS1; + iEXb = iEXp = iS1; + iSYb = iSYp = iS2; + iEYb = iEYp = iS2; + mid = (iE1 + iS1)/2; + for(i=iS1; iaTo[p->aFrom[i].h % p->nTo].iHash; + while( j>0 + && (j-1=iE2 || !p->same_fn(&p->aFrom[i], &p->aTo[j-1])) + ){ + if( limit++ > 10 ){ + j = 0; + break; + } + j = p->aTo[j-1].iNext; + } + if( j==0 ) continue; + if( i=iSYb && j=iSYp && jaFrom[iSX-1]; + pB = &p->aTo[iSY-1]; + n = minInt(iSX-iS1, iSY-iS2); + for(k=0; ksame_fn(pA,pB); k++, pA--, pB--){} + iSX -= k; + iSY -= k; + iEX = i+1; + iEY = j; + pA = &p->aFrom[iEX]; + pB = &p->aTo[iEY]; + n = minInt(iE1-iEX, iE2-iEY); + for(k=0; ksame_fn(pA,pB); k++, pA++, pB++){} + iEX += k; + iEY += k; + skew = (iSX-iS1) - (iSY-iS2); + if( skew<0 ) skew = -skew; + dist = (iSX+iEX)/2 - mid; + if( dist<0 ) dist = -dist; + score = (iEX - iSX)*(int64_t)span - (skew + dist); + if( score>bestScore ){ + bestScore = score; + iSXb = iSX; + iSYb = iSY; + iEXb = iEX; + iEYb = iEY; + }else if( iEX>iEXp ){ + iSXp = iSX; + iSYp = iSY; + iEXp = iEX; + iEYp = iEY; + } + } + if( iSXb==iEXb && (iE1-iS1)*(iE2-iS2)<400 ){ + /* If no common sequence is found using the hashing heuristic and + ** the input is not too big, use the expensive exact solution */ + optimalLCS(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY); + }else{ + *piSX = iSXb; + *piSY = iSYb; + *piEX = iEXb; + *piEY = iEYb; + } +} + +/* +** Expand the size of aEdit[] array to hold at least nEdit elements. +*/ +static void expandEdit(DContext *p, int nEdit){ + p->aEdit = realloc(p->aEdit, nEdit*sizeof(int)); + p->nEditAlloc = nEdit; +} + +/* +** Append a new COPY/DELETE/INSERT triple. +*/ +static void appendTriple(DContext *p, int nCopy, int nDel, int nIns){ + /* printf("APPEND %d/%d/%d\n", nCopy, nDel, nIns); */ + if( p->nEdit>=3 ){ + if( p->aEdit[p->nEdit-1]==0 ){ + if( p->aEdit[p->nEdit-2]==0 ){ + p->aEdit[p->nEdit-3] += nCopy; + p->aEdit[p->nEdit-2] += nDel; + p->aEdit[p->nEdit-1] += nIns; + return; + } + if( nCopy==0 ){ + p->aEdit[p->nEdit-2] += nDel; + p->aEdit[p->nEdit-1] += nIns; + return; + } + } + if( nCopy==0 && nDel==0 ){ + p->aEdit[p->nEdit-1] += nIns; + return; + } + } + if( p->nEdit+3>p->nEditAlloc ){ + expandEdit(p, p->nEdit*2 + 15); + if( p->aEdit==0 ) return; + } + p->aEdit[p->nEdit++] = nCopy; + p->aEdit[p->nEdit++] = nDel; + p->aEdit[p->nEdit++] = nIns; +} + +/* +** Do a single step in the difference. Compute a sequence of +** copy/delete/insert steps that will convert lines iS1 through iE1-1 of +** the input into lines iS2 through iE2-1 of the output and write +** that sequence into the difference context. +** +** The algorithm is to find a block of common text near the middle +** of the two segments being diffed. Then recursively compute +** differences on the blocks before and after that common segment. +** Special cases apply if either input segment is empty or if the +** two segments have no text in common. +*/ +static void diff_step(DContext *p, int iS1, int iE1, int iS2, int iE2){ + int iSX, iEX, iSY, iEY; + + if( iE1<=iS1 ){ + /* The first segment is empty */ + if( iE2>iS2 ){ + appendTriple(p, 0, 0, iE2-iS2); + } + return; + } + if( iE2<=iS2 ){ + /* The second segment is empty */ + appendTriple(p, 0, iE1-iS1, 0); + return; + } + + /* Find the longest matching segment between the two sequences */ + longestCommonSequence(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY); + + if( iEX>iSX ){ + /* A common segment has been found. + ** Recursively diff either side of the matching segment */ + diff_step(p, iS1, iSX, iS2, iSY); + if( iEX>iSX ){ + appendTriple(p, iEX - iSX, 0, 0); + } + diff_step(p, iEX, iE1, iEY, iE2); + }else{ + /* The two segments have nothing in common. Delete the first then + ** insert the second. */ + appendTriple(p, 0, iE1-iS1, iE2-iS2); + } +} + +/* +** Compute the differences between two files already loaded into +** the DContext structure. +** +** A divide and conquer technique is used. We look for a large +** block of common text that is in the middle of both files. Then +** compute the difference on those parts of the file before and +** after the common block. This technique is fast, but it does +** not necessarily generate the minimum difference set. On the +** other hand, we do not need a minimum difference set, only one +** that makes sense to human readers, which this algorithm does. +** +** Any common text at the beginning and end of the two files is +** removed before starting the divide-and-conquer algorithm. +*/ +static void diff_all(DContext *p){ + int mnE, iS, iE1, iE2; + + /* Carve off the common header and footer */ + iE1 = p->nFrom; + iE2 = p->nTo; + while( iE1>0 && iE2>0 && p->same_fn(&p->aFrom[iE1-1], &p->aTo[iE2-1]) ){ + iE1--; + iE2--; + } + mnE = iE1same_fn(&p->aFrom[iS],&p->aTo[iS]); iS++){} + + /* do the difference */ + if( iS>0 ){ + appendTriple(p, iS, 0, 0); + } + diff_step(p, iS, iE1, iS, iE2); + if( iE1nFrom ){ + appendTriple(p, p->nFrom - iE1, 0, 0); + } + + /* Terminate the COPY/DELETE/INSERT triples with three zeros */ + expandEdit(p, p->nEdit+3); + if( p->aEdit ){ + p->aEdit[p->nEdit++] = 0; + p->aEdit[p->nEdit++] = 0; + p->aEdit[p->nEdit++] = 0; + } +} + +/* +** Generate a report of the differences between files pA and pB. +** If pOut is not NULL then a unified diff is appended there. It +** is assumed that pOut has already been initialized. If pOut is +** NULL, then a pointer to an array of integers is returned. +** The integers come in triples. For each triple, +** the elements are the number of lines copied, the number of +** lines deleted, and the number of lines inserted. The vector +** is terminated by a triple of all zeros. +** +** This diff utility does not work on binary files. If a binary +** file is encountered, 0 is returned and pOut is written with +** text "cannot compute difference between binary files". +*/ +int * +text_diff( + const char *pA, /* FROM file */ + const char *pB /* TO file */ +){ + DContext c; + + /* Prepare the input files */ + memset(&c, 0, sizeof(c)); + c.same_fn = same_dline; + c.aFrom = break_into_lines(pA, &c.nFrom); + c.aTo = break_into_lines(pB, &c.nTo); + if( c.aFrom==0 || c.aTo==0 ){ + free(c.aFrom); + free(c.aTo); + return 0; + } + + /* Compute the difference */ + diff_all(&c); + /* If a context diff is not requested, then return the + ** array of COPY/DELETE/INSERT triples. + */ + free(c.aFrom); + free(c.aTo); + return c.aEdit; +} Property changes on: diff.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: merge.1 =================================================================== --- merge.1 (revision 0) +++ merge.1 (working copy) @@ -0,0 +1,112 @@ +.\" Copyright (c) 2015 Baptiste Daroussin +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.Dd Jul 26, 2015 +.Dt MERGE 1 +.Os +.Sh NAME +.Nm merge +.Nd 3-way file merge +.Sh SYNOPSIS +.Nm +.Op Fl EepqV +.Op Fl L Ar label1 +.Op Fl L Ar label2 +.Op Fl L Ar label3 +.Ar file1 file2 file3 +.Sh DESCRIPTION +The +.Nm +utility merges changes that leadds from +.Ar file2 +to +.Ar file3 +into +.Ar file1 . +.Pp +The following options are supported: +.Bl -tag -width Ds +.It Fl A +Verbose merge policy. In case of conflicts more informations are added to the +output. +.It Fl E +Default merge policy. +.It Fl e +Same as +.Fl E +but does not warn about conflicts. +.It Fl L Ar label +Specifies labels to be used in place of corresponding file names +in conflict reports. +.It Fl p +Print result to standard output. +.It Fl q +Quiet mode: suppress warnings about conflicts. +.It Fl V +Does nothing. Added for compatibility with GNU merge. +.El +.Sh EXAMPLES +Using labels: +.Bd -literal -offset indent +$ merge -q -p -L A -L B -L C fileA fileB fileC +<<<<<<< A +foo +======= +bar +>>>>>>> C +.Ed +.Pp +Verbose output: +.Bd -literal -offset ident +$ merge -A -q -p fileA fileB fileC +<<<<<<< fileA +foo +||||||| fileB +lines from B +======= +bar +>>>>>>> fileC +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It 0 +No overlaps. +.It 1 +Overlaps were found. +.It 2 +An error occurred. +.El +.Sh SEE ALSO +.Xr diff 1 , +.Xr diff3 1 , +.Sh AUTHORS +This version of the +.Nm +utility was written by +.An Baptiste Daroussin Aq Mt bapt@FreeBSD.org . + Property changes on: merge.1 ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: merge.c =================================================================== --- merge.c (revision 0) +++ merge.c (working copy) @@ -0,0 +1,191 @@ +/*- + * Copyright (c) 2015 Baptiste Daroussin + * All rights reserved. + *~ + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + *~ + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "merge3.h" + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-AeEpq] [-L label] [-L label] [-L label] " + "file1 file2 file3\n", getprogname()); + exit(EXIT_FAILURE); +} + +static char * +file_to_sbuf(const char *file) +{ + int fd, r; + char buffer[4096]; + char *out; + struct sbuf *buf = sbuf_new_auto(); + + if ((fd = open(file, O_RDONLY)) == -1) + err(2, "Unable to open %s", file); + + while ((r = read(fd, buffer, sizeof(buffer))) > 0) + sbuf_bcat(buf, buffer, r); + + close(fd); + sbuf_finish(buf); + + if ((out = strdup(sbuf_data(buf))) == NULL) + err(2, "strdup()"); + sbuf_delete(buf); + + return (out); +} + +static int +merge(bool tostdout, char *labels[], const char *f1, const char *f2, + const char *f3, int flag, bool quiet) +{ + char *pivot, *local, *new; + const char *markerpivot, *markerlocal, *markernew; + struct sbuf *out = sbuf_new_auto(); + int res; + FILE *f; + + local = file_to_sbuf(f1); + pivot = file_to_sbuf(f2); + new = file_to_sbuf(f3); + + markerlocal = labels[0] ? labels[0] : f1; + markerpivot = labels[1] ? labels[1] : f2; + markernew = labels[2] ? labels[2] : f3; + + res = merge_3way(pivot, new, local, out, markerpivot, markernew, + markerlocal, flag == 2); + sbuf_finish(out); + if (tostdout) { + printf("%s", sbuf_data(out)); + } else { + f = fopen(f1, "w"); + if (f == NULL) + err(2, "Unable to write to '%s'", f1); + fputs(sbuf_data(out), f); + fclose(f); + } + if (res > 0) { + if (!quiet) + warnx("warning: conflicts during merge"); + return (EXIT_FAILURE); + } + + return (EXIT_SUCCESS); +} + +int +main(int argc, char **argv) +{ + int ch; + bool tostdout = false; + bool quiet = false; + bool Aflag = false; + bool eflag = false; + bool Eflag = false; + int flag = 0; + int nblabels = 0; + char *labels[3] = { NULL, NULL, NULL }; + FILE *f; + + f = fopen("/tmp/merge.cmd", "w"); + for (int i = 0; i < argc; i++) { + fprintf(f, "%s\n", argv[i]); + } + fclose(f); + + while ((ch = getopt(argc, argv, "AeEpqL:")) != -1) { + switch (ch) { + case 'A': + if (eflag || Eflag) { + errx(2, "-A is incompatible with -e " + "and -E"); + } + Aflag = true; + break; + case 'e': + if (Aflag || Eflag) { + errx(2, "-e is incompatible with -A " + "and -E"); + } + eflag = true; + break; + case 'E': + if (eflag || Aflag) { + errx(2, "-E is incompatible with -e " + "and -A"); + } + Eflag = true; + break; + case 'p': + tostdout = true; + break; + case 'q': + quiet = true; + break; + case 'L': + if (nblabels >= 3) + errx(2, "too many -L options"); + labels[nblabels++] = optarg; + break; + case 'V': + return(EXIT_SUCCESS); + break; + default: + printf("%d\n", ch); + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + if (argc != 3) + usage(); + + if (Eflag) + flag = 0; + else if (eflag) + flag = 1; + else if (Aflag) + flag = 2; + + return (merge(tostdout, labels, argv[0], argv[1], argv[2], flag, + quiet)); +} Property changes on: merge.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: merge3.c =================================================================== --- merge3.c (revision 0) +++ merge3.c (working copy) @@ -0,0 +1,326 @@ +/*- + * Copyright (c) 2014 Baptiste Daroussin + * All rights reserved. + *~ + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + *~ + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code has been extracted from the fossil scm code + * and modified + */ + +/* +** Copyright (c) 2007 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** 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. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This module implements a 3-way merge +*/ + +#include +#include + +#include +#include +#include +#include + +#include "merge3.h" + +/* The minimum of two integers */ +#ifndef min +# define min(A,B) (A0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){ + if( aC[0]>=sz ) return 1; + sz -= aC[0]; + if( aC[1]>sz ) return 0; + sz -= aC[1]; + aC += 3; + } + return 1; +} + +/* + +** Do a three-way merge. Initialize pOut to contain the result. +** +** The merge is an edit against pV2. Both pV1 and pV2 have a +** common origin at pPivot. Apply the changes of pPivot ==> pV1 +** to pV2. +** +** The return is 0 upon complete success. If any input file is binary, +** -1 is returned and pOut is unmodified. If there are merge +** conflicts, the merge proceeds as best as it can and the number +** of conflicts is returns +*/ +static int +sbuf_merge(const char *pPivot, const char *pV1, const char *pV2, + struct sbuf *pOut, const char *marker1, const char *marker2, + const char *marker3, bool showall){ + int *aC1; /* Changes from pPivot to pV1 */ + int *aC2; /* Changes from pPivot to pV2 */ + int i1, i2; /* Index into aC1[] and aC2[] */ + int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */ + int limit1, limit2; /* Sizes of aC1[] and aC2[] */ + int nConflict = 0; /* Number of merge conflicts seen so far */ + + sbuf_clear(pOut); /* Merge results stored in pOut */ + + /* Compute the edits that occur from pPivot => pV1 (into aC1) + ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is + ** an array of integer triples. Within each triple, the first integer + ** is the number of lines of text to copy directly from the pivot, + ** the second integer is the number of lines of text to omit from the + ** pivot, and the third integer is the number of lines of text that are + ** inserted. The edit array ends with a triple of 0,0,0. + */ + aC1 = text_diff(pPivot, pV1); + aC2 = text_diff(pPivot, pV2); + if( aC1==0 || aC2==0 ){ + free(aC1); + free(aC2); + return -1; + } + + /* Determine the length of the aC1[] and aC2[] change vectors */ + for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){} + limit1 = i1; + for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){} + limit2 = i2; + + /* Loop over the two edit vectors and use them to compute merged text + ** which is written into pOut. i1 and i2 are multiples of 3 which are + ** indices into aC1[] and aC2[] to the edit triple currently being + ** processed + */ + i1 = i2 = 0; + while( i10 && aC2[i2]>0 ){ + /* Output text that is unchanged in both V1 and V2 */ + nCpy = min(aC1[i1], aC2[i2]); + pPivot += sbuf_copy_lines(pOut, pPivot, nCpy); + pV1 += sbuf_copy_lines(NULL, pV1, nCpy); + pV2 += sbuf_copy_lines(NULL, pV2, nCpy); + aC1[i1] -= nCpy; + aC2[i2] -= nCpy; + }else + if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){ + /* Output edits to V2 that occurs within unchanged regions of V1 */ + nDel = aC2[i2+1]; + nIns = aC2[i2+2]; + pPivot += sbuf_copy_lines(NULL, pPivot, nDel); + pV1 += sbuf_copy_lines(NULL, pV1, nDel); + pV2 += sbuf_copy_lines(pOut, pV2, nIns); + aC1[i1] -= nDel; + i2 += 3; + }else + if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){ + /* Output edits to V1 that occur within unchanged regions of V2 */ + nDel = aC1[i1+1]; + nIns = aC1[i1+2]; + pPivot += sbuf_copy_lines(NULL, pPivot, nDel); + pV2 += sbuf_copy_lines(NULL, pV2, nDel); + pV1 += sbuf_copy_lines(pOut, pV1, nIns); + aC2[i2] -= nDel; + i1 += 3; + }else + if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){ + /* Output edits that are identical in both V1 and V2. */ + nDel = aC1[i1+1]; + nIns = aC1[i1+2]; + pPivot += sbuf_copy_lines(NULL, pPivot, nDel); + pV1 += sbuf_copy_lines(pOut, pV1, nIns); + pV2 += sbuf_copy_lines(NULL, pV2, nIns); + i1 += 3; + i2 += 3; + }else + { + int sz = 1; + nConflict++; + while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){ + sz++; + } + sbuf_printf(pOut, "<<<<<<< %s\n", marker3); + pV2 += sbuf_copy_lines(pOut, pV2, sz); + if (showall) { + sbuf_printf(pOut, "||||||| %s\n", marker1); + pPivot += sbuf_copy_lines(pOut, pPivot, sz); + } else { + pPivot += sbuf_copy_lines(NULL, pPivot, sz); + } + i2+= 3;//sz + 1; + sbuf_cat(pOut, "=======\n"); + pV1 += sbuf_copy_lines(pOut, pV1, sz); + i1+= 3;//sz + 1; + sbuf_printf(pOut, ">>>>>>> %s\n", marker2); + } + + /* If we are finished with an edit triple, advance to the next + ** triple. + */ + if( i10 ){ + sbuf_copy_lines(pOut, pV1, aC1[i1+2]); + }else if( i20 ){ + sbuf_copy_lines(pOut, pV2, aC2[i2+2]); + } + + free(aC1); + free(aC2); + return nConflict; +} + +/* +** This routine is a wrapper around blob_merge() with the following +** enhancements: +** +** (1) If the merge-command is defined, then use the external merging +** program specified instead of the built-in blob-merge to do the +** merging. Panic if the external merger fails. +** ** Not currently implemented ** +** +** (2) If gmerge-command is defined and there are merge conflicts in +** blob_merge() then invoke the external graphical merger to resolve +** the conflicts. +** +** (3) If a merge conflict occurs and gmerge-command is not defined, +** then write the pivot, original, and merge-in files to the +** filesystem. +*/ +int merge_3way( + const char *pPivot, /* Common ancestor (older) */ + const char *pV1, /* Name of file for version merging into (mine) */ + const char *pV2, /* Version merging from (yours) */ + struct sbuf *pOut, /* Output written here */ + const char *m1, + const char *m2, + const char *m3, + bool showall +){ + int rc; /* Return code of subroutines and this routine */ + + rc = sbuf_merge(pPivot, pV1, pV2, pOut, m1, m2, m3, showall); + sbuf_finish(pOut); + return rc; +} Property changes on: merge3.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: merge3.h =================================================================== --- merge3.h (revision 0) +++ merge3.h (working copy) @@ -0,0 +1,3 @@ +int merge_3way(const char *, const char *, const char *, struct sbuf *, const char *, const char *, const char *, bool showall); +int *text_diff(const char *, const char *); + Property changes on: merge3.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property