From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/merge3.c | 1232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1232 insertions(+) create mode 100644 src/merge3.c (limited to 'src/merge3.c') diff --git a/src/merge3.c b/src/merge3.c new file mode 100644 index 0000000..fcdb62d --- /dev/null +++ b/src/merge3.c @@ -0,0 +1,1232 @@ +/* +** 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 "config.h" +#include "merge3.h" + +#if 0 +#define DEBUG(X) X +#define ISDEBUG 1 +#else +#define DEBUG(X) +#define ISDEBUG 0 +#endif + +/* The minimum of two integers */ +#ifndef min +# define min(A,B) (A>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +}; + +/* +** Return true if the input blob contains any CR/LF pairs on the first +** ten lines. This should be enough to detect files that use mainly CR/LF +** line endings without causing a performance impact for LF only files. +*/ +int contains_crlf(Blob *p){ + int i; + int j = 0; + const int maxL = 10; /* Max lines to check */ + const char *z = blob_buffer(p); + int n = blob_size(p)+1; + for(i=1; imaxL ) return 0; + } + return 0; +} + +/* +** Ensure that the text in pBlob ends with a new line. +** If useCrLf is true adds "\r\n" otherwise '\n'. +*/ +void ensure_line_end(Blob *pBlob, int useCrLf){ + if( pBlob->nUsed<=0 ) return; + if( pBlob->aData[pBlob->nUsed-1]!='\n' ){ + if( useCrLf ) blob_append_char(pBlob, '\r'); + blob_append_char(pBlob, '\n'); + } +} + +/* +** Write out one of the four merge-marks. +*/ +void append_merge_mark(Blob *pOut, int iMark, int ln, int useCrLf){ + ensure_line_end(pOut, useCrLf); + blob_append(pOut, mergeMarker[iMark], -1); + if( ln>0 ) blob_appendf(pOut, " (line %d)", ln); + ensure_line_end(pOut, useCrLf); +} + +#if INTERFACE +/* +** This is an abstract class for constructing a merge. +** Subclasses of this object format the merge output in different ways. +** +** To subclass, create an instance of the MergeBuilder object and fill +** in appropriate method implementations. +*/ +struct MergeBuilder { + void (*xStart)(MergeBuilder*); + void (*xSame)(MergeBuilder*, unsigned int); + void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int); + void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int); + void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int); + void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int); + void (*xEnd)(MergeBuilder*); + void (*xDestroy)(MergeBuilder*); + const char *zPivot; /* Label or name for the pivot */ + const char *zV1; /* Label or name for the V1 file */ + const char *zV2; /* Label or name for the V2 file */ + const char *zOut; /* Label or name for the output */ + Blob *pPivot; /* The common ancestor */ + Blob *pV1; /* First variant (local copy) */ + Blob *pV2; /* Second variant (merged in) */ + Blob *pOut; /* Write merge results here */ + int useCrLf; /* Use CRLF line endings */ + int nContext; /* Size of unchanged line boundaries */ + unsigned int mxPivot; /* Number of lines in the pivot */ + unsigned int mxV1; /* Number of lines in V1 */ + unsigned int mxV2; /* Number of lines in V2 */ + unsigned int lnPivot; /* Lines read from pivot */ + unsigned int lnV1; /* Lines read from v1 */ + unsigned int lnV2; /* Lines read from v2 */ + unsigned int lnOut; /* Lines written to out */ + unsigned int nConflict; /* Number of conflicts seen */ + u64 diffFlags; /* Flags for difference engine */ +}; +#endif /* INTERFACE */ + + +/************************* Generic MergeBuilder ******************************/ +/* These are generic methods for MergeBuilder. They just output debugging +** information. But some of them are useful as base methods for other useful +** implementations of MergeBuilder. +*/ + +/* xStart() and xEnd() are called to generate header and footer information +** in the output. This is a no-op in the generic implementation. +*/ +static void dbgStartEnd(MergeBuilder *p){ (void)p; } + +/* The next N lines of PIVOT are unchanged in both V1 and V2 +*/ +static void dbgSame(MergeBuilder *p, unsigned int N){ + blob_appendf(p->pOut, + "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n", + N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N, + p->lnV2+1, p->lnV2+N); + p->lnPivot += N; + p->lnV1 += N; + p->lnV2 += N; +} + +/* The next nPivot lines of the PIVOT are changed into nV1 lines by V1 +*/ +static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ + blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n", + nV1, p->lnV1+1, p->lnV1+nV1); + p->lnPivot += nPivot; + p->lnV2 += nPivot; + p->lnV1 += nV1; +} + +/* The next nPivot lines of the PIVOT are changed into nV2 lines by V2 +*/ +static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ + blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n", + nV2, p->lnV2+1, p->lnV2+nV2); + p->lnPivot += nPivot; + p->lnV1 += nPivot; + p->lnV2 += nV2; +} + +/* The next nPivot lines of the PIVOT are changed into nV lines from V1 and +** V2, which should be the same. In other words, the same change is found +** in both V1 and V2. +*/ +static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ + blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n", + nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV); + p->lnPivot += nPivot; + p->lnV1 += nV; + p->lnV2 += nV; +} + +/* V1 and V2 have different and overlapping changes. The next nPivot lines +** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2. +*/ +static void dbgConflict( + MergeBuilder *p, + unsigned int nPivot, + unsigned int nV1, + unsigned int nV2 +){ + blob_appendf(p->pOut, + "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n", + nPivot, nV1, nV2, + p->lnPivot+1, p->lnPivot+nPivot, + p->lnV1+1, p->lnV1+nV1, + p->lnV2+1, p->lnV2+nV2); + p->lnV1 += nV1; + p->lnPivot += nPivot; + p->lnV2 += nV2; +} + +/* Generic destructor for the MergeBuilder object +*/ +static void dbgDestroy(MergeBuilder *p){ + memset(p, 0, sizeof(*p)); +} + +/* Generic initializer for a MergeBuilder object +*/ +static void mergebuilder_init(MergeBuilder *p){ + memset(p, 0, sizeof(*p)); + p->xStart = dbgStartEnd; + p->xSame = dbgSame; + p->xChngV1 = dbgChngV1; + p->xChngV2 = dbgChngV2; + p->xChngBoth = dbgChngBoth; + p->xConflict = dbgConflict; + p->xEnd = dbgStartEnd; + p->xDestroy = dbgDestroy; +} + +/************************* MergeBuilderToken ********************************/ +/* This version of MergeBuilder actually performs a merge on file that +** are broken up into tokens instead of lines, and puts the result in pOut. +*/ +static void tokenSame(MergeBuilder *p, unsigned int N){ + blob_append(p->pOut, p->pPivot->aData+p->pPivot->iCursor, N); + p->pPivot->iCursor += N; + p->pV1->iCursor += N; + p->pV2->iCursor += N; +} +static void tokenChngV1(MergeBuilder *p, unsigned int nPivot, unsigned nV1){ + blob_append(p->pOut, p->pV1->aData+p->pV1->iCursor, nV1); + p->pPivot->iCursor += nPivot; + p->pV1->iCursor += nV1; + p->pV2->iCursor += nPivot; +} +static void tokenChngV2(MergeBuilder *p, unsigned int nPivot, unsigned nV2){ + blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2); + p->pPivot->iCursor += nPivot; + p->pV1->iCursor += nPivot; + p->pV2->iCursor += nV2; +} +static void tokenChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned nV){ + blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV); + p->pPivot->iCursor += nPivot; + p->pV1->iCursor += nV; + p->pV2->iCursor += nV; +} +static void tokenConflict( + MergeBuilder *p, + unsigned int nPivot, + unsigned int nV1, + unsigned int nV2 +){ + /* For a token-merge conflict, use the text from the merge-in */ + blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2); + p->pPivot->iCursor += nPivot; + p->pV1->iCursor += nV1; + p->pV2->iCursor += nV2; +} +static void mergebuilder_init_token(MergeBuilder *p){ + mergebuilder_init(p); + p->xSame = tokenSame; + p->xChngV1 = tokenChngV1; + p->xChngV2 = tokenChngV2; + p->xChngBoth = tokenChngBoth; + p->xConflict = tokenConflict; + p->diffFlags = DIFF_BY_TOKEN; +} + +/* +** Attempt to do a low-level merge on a conflict. The conflict is +** described by the first four parameters, which are the same as the +** arguments to the xConflict method of the MergeBuilder object. +** This routine attempts to resolve the conflict by looking at +** elements of the conflict region that are finer grain than complete +** lines of text. +** +** The result is written into Blob pOut. pOut is initialized by this +** routine. +*/ +int merge_try_to_resolve_conflict( + MergeBuilder *pMB, /* MergeBuilder that encounter conflict */ + unsigned int nPivot, /* Lines of conflict in the pivot */ + unsigned int nV1, /* Lines of conflict in V1 */ + unsigned int nV2, /* Lines of conflict in V2 */ + Blob *pOut /* Write resolution text here */ +){ + int nConflict; + MergeBuilder mb; + Blob pv, v1, v2; + mergebuilder_init_token(&mb); + blob_extract_lines(pMB->pPivot, nPivot, &pv); + blob_extract_lines(pMB->pV1, nV1, &v1); + blob_extract_lines(pMB->pV2, nV2, &v2); + blob_zero(pOut); + blob_materialize(&pv); + blob_materialize(&v1); + blob_materialize(&v2); + mb.pPivot = &pv; + mb.pV1 = &v1; + mb.pV2 = &v2; + mb.pOut = pOut; + nConflict = merge_three_blobs(&mb); + blob_reset(&pv); + blob_reset(&v1); + blob_reset(&v2); + /* mb has not allocated any resources, so we do not need to invoke + ** the xDestroy method. */ + blob_add_final_newline(pOut); + return nConflict; +} + + +/************************* MergeBuilderText **********************************/ +/* This version of MergeBuilder actually performs a merge on file and puts +** the result in pOut +*/ +static void txtStart(MergeBuilder *p){ + /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM), + ** keep it in the output. This should be secure enough not to cause + ** unintended changes to the merged file and consistent with what + ** users are using in their source files. + */ + if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){ + blob_append(p->pOut, (char*)get_utf8_bom(0), -1); + } + if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){ + p->useCrLf = 1; + } +} +static void txtSame(MergeBuilder *p, unsigned int N){ + blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N; + blob_copy_lines(0, p->pV1, N); p->lnV1 += N; + blob_copy_lines(0, p->pV2, N); p->lnV2 += N; +} +static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ + blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; + blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot; + blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1; +} +static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ + blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; + blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot; + blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2; +} +static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ + blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; + blob_copy_lines(0, p->pV1, nV); p->lnV1 += nV; + blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV; +} +static void txtConflict( + MergeBuilder *p, + unsigned int nPivot, + unsigned int nV1, + unsigned int nV2 +){ + int nRes; /* Lines in the computed conflict resolution */ + Blob res; /* Text of the conflict resolution */ + + merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res); + nRes = blob_linecount(&res); + + append_merge_mark(p->pOut, 0, p->lnV1+1, p->useCrLf); + blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1; + + if( nRes>0 ){ + append_merge_mark(p->pOut, 1, 0, p->useCrLf); + blob_copy_lines(p->pOut, &res, nRes); + } + blob_reset(&res); + + append_merge_mark(p->pOut, 2, p->lnPivot+1, p->useCrLf); + blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot; + + append_merge_mark(p->pOut, 3, p->lnV2+1, p->useCrLf); + blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2; + + append_merge_mark(p->pOut, 4, -1, p->useCrLf); +} +static void mergebuilder_init_text(MergeBuilder *p){ + mergebuilder_init(p); + p->xStart = txtStart; + p->xSame = txtSame; + p->xChngV1 = txtChngV1; + p->xChngV2 = txtChngV2; + p->xChngBoth = txtChngBoth; + p->xConflict = txtConflict; +} + +/************************* MergeBuilderTcl **********************************/ +/* Generate merge output formatted for reading by a TCL script. +** +** The output consists of lines of text, each with 4 tokens. The tokens +** represent the content for one line from baseline, v1, v2, and output +** respectively. The first character of each token provides auxiliary +** information: +** +** . This line is omitted. +** N Name of the file. +** T Literal text follows that should have a \n terminator. +** R Literal text follows that needs a \r\n terminator. +** X Merge conflict. +** Z Literal text without a line terminator. +** S Skipped lines. Followed by number of lines to skip. +** 1 Text is a copy of token 1 +** 2 Use data from data-token 2 +** 3 Use data from data-token 3 +*/ + +/* Write text that goes into the interior of a double-quoted string in TCL */ +static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){ + int j; + for(j=0; j0x7e ){ + char z[5]; + z[0] = '\\'; + z[1] = "01234567"[(c>>6)&0x3]; + z[2] = "01234567"[(c>>3)&0x7]; + z[3] = "01234567"[c&0x7]; + z[4] = 0; + blob_append(pOut, z, 4); + }else{ + blob_append_char(pOut, c); + } + } +} + +/* Copy one line of text from pIn and append to pOut, encoded as TCL */ +static void tclLineOfText(Blob *pOut, Blob *pIn, char cType){ + int i, k; + for(i=pIn->iCursor; inUsed && pIn->aData[i]!='\n'; i++){} + if( i==pIn->nUsed ){ + k = i; + }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){ + k = i-1; + i++; + }else{ + k = i; + i++; + } + blob_append_char(pOut, '"'); + blob_append_char(pOut, cType); + tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor); + pIn->iCursor = i; + blob_append_char(pOut, '"'); +} +static void tclStart(MergeBuilder *p){ + Blob *pOut = p->pOut; + blob_append(pOut, "\"N", 2); + tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot)); + blob_append(pOut, "\" \"N", 4); + tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1)); + blob_append(pOut, "\" \"N", 4); + tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2)); + blob_append(pOut, "\" \"N", 4); + if( p->zOut ){ + tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut)); + }else{ + blob_append(pOut, "(Merge Result)", -1); + } + blob_append(pOut, "\"\n", 2); +} +static void tclSame(MergeBuilder *p, unsigned int N){ + int i = 0; + int nSkip; + + if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){ + while( inContext ){ + tclLineOfText(p->pOut, p->pPivot, 'T'); + blob_append(p->pOut, " 1 1 1\n", 7); + i++; + } + nSkip = N - p->nContext*2; + }else{ + nSkip = N - p->nContext; + } + if( nSkip>0 ){ + blob_appendf(p->pOut, "\"S%d %d %d %d\" . . .\n", + nSkip, nSkip, nSkip, nSkip); + blob_copy_lines(0, p->pPivot, nSkip); + i += nSkip; + } + + p->lnPivot += N; + p->lnV1 += N; + p->lnV2 += N; + + if( p->lnPivotmxPivot || p->lnV1mxV1 || p->lnV2mxV2 ){ + while( ipOut, p->pPivot, 'T'); + blob_append(p->pOut, " 1 1 1\n", 7); + i++; + } + } + + blob_copy_lines(0, p->pV1, N); + blob_copy_lines(0, p->pV2, N); +} +static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ + int i; + for(i=0; ipOut, p->pPivot, 'T'); + blob_append_char(p->pOut, ' '); + tclLineOfText(p->pOut, p->pV1, 'T'); + blob_append(p->pOut, " 1 2\n", 5); + } + while( ipOut, p->pPivot, 'T'); + blob_append(p->pOut, " . 1 .\n", 7); + i++; + } + while( ipOut, ". ", 2); + tclLineOfText(p->pOut, p->pV1, 'T'); + blob_append(p->pOut, " . 2\n", 5); + i++; + } + p->lnPivot += nPivot; + p->lnV1 += nV1; + p->lnV2 += nPivot; + blob_copy_lines(0, p->pV2, nPivot); +} +static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ + int i; + for(i=0; ipOut, p->pPivot, 'T'); + blob_append(p->pOut, " 1 ", 3); + tclLineOfText(p->pOut, p->pV2, 'T'); + blob_append(p->pOut, " 3\n", 3); + } + while( ipOut, p->pPivot, 'T'); + blob_append(p->pOut, " 1 . .\n", 7); + i++; + } + while( ipOut, ". . ", 4); + tclLineOfText(p->pOut, p->pV2, 'T'); + blob_append(p->pOut, " 3\n", 3); + i++; + } + p->lnPivot += nPivot; + p->lnV1 += nPivot; + p->lnV2 += nV2; + blob_copy_lines(0, p->pV1, nPivot); +} +static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ + int i; + for(i=0; ipOut, p->pPivot, 'T'); + blob_append_char(p->pOut, ' '); + tclLineOfText(p->pOut, p->pV1, 'T'); + blob_append(p->pOut, " 2 2\n", 5); + } + while( ipOut, p->pPivot, 'T'); + blob_append(p->pOut, " . . .\n", 7); + i++; + } + while( ipOut, ". ", 2); + tclLineOfText(p->pOut, p->pV1, 'T'); + blob_append(p->pOut, " 2 2\n", 5); + i++; + } + p->lnPivot += nPivot; + p->lnV1 += nV; + p->lnV2 += nV; + blob_copy_lines(0, p->pV2, nV); +} +static void tclConflict( + MergeBuilder *p, + unsigned int nPivot, + unsigned int nV1, + unsigned int nV2 +){ + int mx = nPivot; + int i; + int nRes; + Blob res; + + merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res); + nRes = blob_linecount(&res); + if( nV1>mx ) mx = nV1; + if( nV2>mx ) mx = nV2; + if( nRes>mx ) mx = nRes; + if( nRes>0 ){ + blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nV2+2); + } + for(i=0; ipOut, p->pPivot, 'X'); + }else{ + blob_append_char(p->pOut, '.'); + } + blob_append_char(p->pOut, ' '); + if( ipOut, p->pV1, 'X'); + }else{ + blob_append_char(p->pOut, '.'); + } + blob_append_char(p->pOut, ' '); + if( ipOut, p->pV2, 'X'); + }else{ + blob_append_char(p->pOut, '.'); + } + if( ipOut, ' '); + tclLineOfText(p->pOut, &res, 'X'); + blob_append_char(p->pOut, '\n'); + }else{ + blob_append(p->pOut, " .\n", 3); + } + if( i==mx-1 ){ + blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nPivot+nV1+3); + } + } + blob_reset(&res); + p->lnPivot += nPivot; + p->lnV1 += nV1; + p->lnV2 += nV2; +} +void mergebuilder_init_tcl(MergeBuilder *p){ + mergebuilder_init(p); + p->xStart = tclStart; + p->xSame = tclSame; + p->xChngV1 = tclChngV1; + p->xChngV2 = tclChngV2; + p->xChngBoth = tclChngBoth; + p->xConflict = tclConflict; +} +/*****************************************************************************/ + +/* +** The aC[] array contains triples of integers. Within each triple, the +** elements are: +** +** (0) The number of lines to copy +** (1) The number of lines to delete +** (2) The number of liens to insert +** +** Suppose we want to advance over sz lines of the original file. This routine +** returns true if that advance would land us on a copy operation. It +** returns false if the advance would end on a delete. +*/ +static int ends_with_copy(int *aC, int sz){ + while( sz>0 && (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; +} + +/* +** aC[] is an "edit triple" for changes from A to B. Advance through +** this triple to determine the number of lines to bypass on B in order +** to match an advance of sz lines on A. +*/ +static int skip_conflict( + int *aC, /* Array of integer triples describing the edit */ + int i, /* Index in aC[] of current location */ + int sz, /* Lines of A that have been skipped */ + unsigned int *pLn /* OUT: Lines of B to skip to keep alignment with A */ +){ + *pLn = 0; + while( sz>0 ){ + if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break; + if( aC[i]>=sz ){ + aC[i] -= sz; + *pLn += sz; + break; + } + *pLn += aC[i]; + *pLn += aC[i+2]; + sz -= aC[i] + aC[i+1]; + i += 3; + } + return i; +} + +/* +** 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 +*/ +int merge_three_blobs(MergeBuilder *p){ + 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 */ + DiffConfig DCfg; + + /* 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. + */ + diff_config_init(&DCfg, 0); + DCfg.diffFlags = p->diffFlags; + aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg); + aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg); + if( aC1==0 || aC2==0 ){ + free(aC1); + free(aC2); + return -1; + } + + blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */ + blob_rewind(p->pV2); + blob_rewind(p->pPivot); + + /* Determine the length of the aC1[] and aC2[] change vectors */ + p->mxPivot = 0; + p->mxV1 = 0; + for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){ + p->mxPivot += aC1[i1] + aC1[i1+1]; + p->mxV1 += aC1[i1] + aC1[i1+2]; + } + limit1 = i1; + p->mxV2 = 0; + for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){ + p->mxV2 += aC2[i2] + aC2[i2+2]; + } + limit2 = i2; + + /* Output header text and do any other required initialization */ + p->xStart(p); + + /* 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]); + p->xSame(p, 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]; + p->xChngV2(p, nDel, 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]; + p->xChngV1(p, nDel, nIns); + aC2[i2] -= nDel; + i1 += 3; + }else + if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){ + /* Output edits that are identical in both V1 and V2. */ + assert( aC1[i1]==0 ); + nDel = aC1[i1+1]; + nIns = aC1[i1+2]; + p->xChngBoth(p, nDel, nIns); + i1 += 3; + i2 += 3; + }else + { + /* We have found a region where different edits to V1 and V2 overlap. + ** This is a merge conflict. Find the size of the conflict, then + ** output both possible edits separated by distinctive marks. + */ + unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */ + unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */ + nConflict++; + while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){ + sz++; + } + i1 = skip_conflict(aC1, i1, sz, &nV1); + i2 = skip_conflict(aC2, i2, sz, &nV2); + p->xConflict(p, sz, nV1, nV2); + } + + /* If we are finished with an edit triple, advance to the next + ** triple. + */ + if( i10 ){ + p->xChngV1(p, 0, aC1[i1+2]); + }else if( i20 ){ + p->xChngV2(p, 0, aC2[i2+2]); + } + + /* Output footer text */ + p->xEnd(p); + + free(aC1); + free(aC2); + return nConflict; +} + +/* +** Return true if the input string contains a merge marker on a line by +** itself. +*/ +int contains_merge_marker(Blob *p){ + int i, j; + int len = (int)strlen(mergeMarker[0]); + const char *z = blob_buffer(p); + int n = blob_size(p) - len + 1; + assert( len==(int)strlen(mergeMarker[1]) ); + assert( len==(int)strlen(mergeMarker[2]) ); + assert( len==(int)strlen(mergeMarker[3]) ); + assert( len==(int)strlen(mergeMarker[4]) ); + assert( count(mergeMarker)==5 ); + for(i=0; i0 && !noWarn ){ + fossil_warning("WARNING: %d merge conflicts", nConflict); + } +} + +/* +** aSubst is an array of string pairs. The first element of each pair is +** a string that begins with %. The second element is a replacement for that +** string. +** +** This routine makes a copy of zInput into memory obtained from malloc and +** performance all applicable substitutions on that string. +*/ +char *string_subst(const char *zInput, int nSubst, const char **azSubst){ + Blob x; + int i, j; + blob_zero(&x); + while( zInput[0] ){ + for(i=0; zInput[i] && zInput[i]!='%'; i++){} + if( i>0 ){ + blob_append(&x, zInput, i); + zInput += i; + } + if( zInput[0]==0 ) break; + for(j=0; j=nSubst ){ + blob_append(&x, "%", 1); + zInput++; + } + } + return blob_str(&x); +} + +#if INTERFACE +/* +** Flags to the 3-way merger +*/ +#define MERGE_DRYRUN 0x0001 +/* +** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain +** its temporary files on error. By default they are removed after the +** merge, regardless of success or failure. +*/ +#define MERGE_KEEP_FILES 0x0002 +#endif + + +/* +** This routine is a wrapper around merge_three_blobs() 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 +** merge_three_blobs() 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( + Blob *pPivot, /* Common ancestor (older) */ + const char *zV1, /* Name of file for version merging into (mine) */ + Blob *pV2, /* Version merging from (yours) */ + Blob *pOut, /* Output written here */ + unsigned mergeFlags /* Flags that control operation */ +){ + Blob v1; /* Content of zV1 */ + int rc; /* Return code of subroutines and this routine */ + const char *zGMerge; /* Name of the gmerge command */ + MergeBuilder s; /* The merge state */ + + mergebuilder_init_text(&s); + s.pPivot = pPivot; + s.pV1 = &v1; + s.pV2 = pV2; + blob_zero(pOut); + s.pOut = pOut; + blob_read_from_file(s.pV1, zV1, ExtFILE); + rc = merge_three_blobs(&s); + zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0); + if( (mergeFlags & MERGE_DRYRUN)==0 + && ((zGMerge!=0 && zGMerge[0]!=0) + || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){ + char *zPivot; /* Name of the pivot file */ + char *zOrig; /* Name of the original content file */ + char *zOther; /* Name of the merge file */ + + zPivot = file_newname(zV1, "baseline", 1); + blob_write_to_file(s.pPivot, zPivot); + zOrig = file_newname(zV1, "original", 1); + blob_write_to_file(s.pV1, zOrig); + zOther = file_newname(zV1, "merge", 1); + blob_write_to_file(s.pV2, zOther); + if( rc>0 ){ + if( zGMerge && zGMerge[0] ){ + char *zOut; /* Temporary output file */ + char *zCmd; /* Command to invoke */ + const char *azSubst[8]; /* Strings to be substituted */ + zOut = file_newname(zV1, "output", 1); + azSubst[0] = "%baseline"; azSubst[1] = zPivot; + azSubst[2] = "%original"; azSubst[3] = zOrig; + azSubst[4] = "%merge"; azSubst[5] = zOther; + azSubst[6] = "%output"; azSubst[7] = zOut; + zCmd = string_subst(zGMerge, 8, azSubst); + printf("%s\n", zCmd); fflush(stdout); + fossil_system(zCmd); + if( file_size(zOut, RepoFILE)>=0 ){ + blob_read_from_file(pOut, zOut, ExtFILE); + file_delete(zOut); + } + fossil_free(zCmd); + fossil_free(zOut); + } + } + if( (mergeFlags & MERGE_KEEP_FILES)==0 ){ + file_delete(zPivot); + file_delete(zOrig); + file_delete(zOther); + } + fossil_free(zPivot); + fossil_free(zOrig); + fossil_free(zOther); + } + blob_reset(&v1); + s.xDestroy(&s); + return rc; +} -- cgit v1.3.1