Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -25,15 +25,18 @@ #if INTERFACE /* ** Allowed flag parameters to the text_diff() and html_sbsdiff() funtions: */ -#define DIFF_CONTEXT_MASK 0x0000fff /* Lines of context. Default if 0 */ -#define DIFF_WIDTH_MASK 0x00ff000 /* side-by-side column width */ -#define DIFF_IGNORE_EOLWS 0x0100000 /* Ignore end-of-line whitespace */ -#define DIFF_SIDEBYSIDE 0x0200000 /* Generate a side-by-side diff */ -#define DIFF_NEWFILE 0x0400000 /* Missing files are as empty files */ +#define DIFF_CONTEXT_MASK 0x0000ffff /* Lines of context. Default if 0 */ +#define DIFF_WIDTH_MASK 0x00ff0000 /* side-by-side column width */ +#define DIFF_IGNORE_EOLWS 0x01000000 /* Ignore end-of-line whitespace */ +#define DIFF_SIDEBYSIDE 0x02000000 /* Generate a side-by-side diff */ +#define DIFF_NEWFILE 0x04000000 /* Missing files are as empty files */ +#define DIFF_INLINE 0x08000000 /* Inline (not side-by-side) diff */ +#define DIFF_HTML 0x10000000 /* Render for HTML */ +#define DIFF_LINENO 0x20000000 /* Show line numbers in context diff */ #endif /* INTERFACE */ /* ** Maximum length of a line in a text file. (8192) @@ -146,15 +149,49 @@ } /* ** Append a single line of "diff" output to pOut. */ -static void appendDiffLine(Blob *pOut, char *zPrefix, DLine *pLine){ - blob_append(pOut, zPrefix, 1); - blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK); +static void appendDiffLine(Blob *pOut, char cPrefix, DLine *pLine, int html){ + blob_append(pOut, &cPrefix, 1); + if( html ){ + char *zHtml; + if( cPrefix=='+' ){ + blob_append(pOut, "<span class=\"diffadd\">", -1); + }else if( cPrefix=='-' ){ + blob_append(pOut, "<span class=\"diffrm\">", -1); + } + zHtml = htmlize(pLine->z, (pLine->h & LENGTH_MASK)); + blob_append(pOut, zHtml, -1); + fossil_free(zHtml); + if( cPrefix!=' ' ){ + blob_append(pOut, "</span>", -1); + } + }else{ + blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK); + } blob_append(pOut, "\n", 1); } + +/* +** Append line numbers to the context diff output. Zero or negative numbers +** are blanks. +*/ +static void appendDiffLineno(Blob *pOut, int lnA, int lnB, int html){ + if( html ) blob_append(pOut, "<span class=\"diffln\">", -1); + if( lnA>0 ){ + blob_appendf(pOut, "%6d ", lnA); + }else{ + blob_append(pOut, " ", 7); + } + if( lnB>0 ){ + blob_appendf(pOut, "%6d ", lnB); + }else{ + blob_append(pOut, " ", 8); + } + if( html ) blob_append(pOut, "</span>", -1); +} /* ** Expand the size of aEdit[] array to hold nEdit elements. */ static void expandEdit(DContext *p, int nEdit){ @@ -198,11 +235,17 @@ /* ** Given a diff context in which the aEdit[] array has been filled ** in, compute a context diff into pOut. */ -static void contextDiff(DContext *p, Blob *pOut, int nContext){ +static void contextDiff( + DContext *p, /* The difference */ + Blob *pOut, /* Output a context diff to here */ + int nContext, /* Number of lines of context */ + int showLn, /* Show line numbers */ + int html /* Render as HTML */ +){ DLine *A; /* Left side of the diff */ DLine *B; /* Right side of the diff */ int a = 0; /* Index of next line in A[] */ int b = 0; /* Index of next line in B[] */ int *R; /* Array of COPY/DELETE/INSERT triples */ @@ -252,40 +295,57 @@ /* * If the patch changes an empty file or results in an empty file, * the block header must use 0,0 as position indicator and not 1,0. * Otherwise, patch would be confused and may reject the diff. */ - blob_appendf(pOut,"@@ -%d,%d +%d,%d @@\n", - na ? a+skip+1 : 0, na, - nb ? b+skip+1 : 0, nb); + if( showLn ){ + if( r==0 ){ + /* Do not show a top divider */ + }else if( html ){ + blob_appendf(pOut, "<span class=\"diffhr\">%.80c</span>\n", '.'); + }else{ + blob_appendf(pOut, "%.80c\n", '.'); + } + }else{ + if( html ) blob_appendf(pOut, "<span class=\"diffln\">"); + blob_appendf(pOut,"@@ -%d,%d +%d,%d @@", + na ? a+skip+1 : 0, na, + nb ? b+skip+1 : 0, nb); + if( html ) blob_appendf(pOut, "</span>"); + blob_append(pOut, "\n", 1); + } /* Show the initial common area */ a += skip; b += skip; m = R[r] - skip; for(j=0; j<m; j++){ - appendDiffLine(pOut, " ", &A[a+j]); + if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html); + appendDiffLine(pOut, ' ', &A[a+j], html); } a += m; b += m; /* Show the differences */ for(i=0; i<nr; i++){ m = R[r+i*3+1]; for(j=0; j<m; j++){ - appendDiffLine(pOut, "-", &A[a+j]); + if( showLn ) appendDiffLineno(pOut, a+j+1, 0, html); + appendDiffLine(pOut, '-', &A[a+j], html); } a += m; m = R[r+i*3+2]; for(j=0; j<m; j++){ - appendDiffLine(pOut, "+", &B[b+j]); + if( showLn ) appendDiffLineno(pOut, 0, b+j+1, html); + appendDiffLine(pOut, '+', &B[b+j], html); } b += m; if( i<nr-1 ){ m = R[r+i*3+3]; for(j=0; j<m; j++){ - appendDiffLine(pOut, " ", &B[b+j]); + if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html); + appendDiffLine(pOut, ' ', &B[b+j], html); } b += m; a += m; } } @@ -293,57 +353,124 @@ /* Show the final common area */ assert( nr==i ); m = R[r+nr*3]; if( m>nContext ) m = nContext; for(j=0; j<m; j++){ - appendDiffLine(pOut, " ", &B[b+j]); + if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html); + appendDiffLine(pOut, ' ', &B[b+j], html); } } } /* -** Write a 6-digit line number into the buffer z[]. z[] is guaranteed to -** have space for at least 7 characters. +** Status of a single output line */ -static void sbsWriteLineno(char *z, int ln){ - sqlite3_snprintf(7, z, "%6d", ln+1); - z[6] = ' '; -} +typedef struct SbsLine SbsLine; +struct SbsLine { + char *zLine; /* The output line under construction */ + int n; /* Index of next unused slot in the zLine[] */ + int width; /* Maximum width of a column in the output */ + unsigned char escHtml; /* True to escape html characters */ +}; + +/* +** Flags for sbsWriteText() +*/ +#define SBS_NEWLINE 0x0001 /* End with \n\000 */ +#define SBS_PAD 0x0002 /* Pad output to width spaces */ +#define SBS_ENDSPAN 0x0004 /* Write a </span> after text */ /* ** Write up to width characters of pLine into z[]. Translate tabs into -** spaces. If trunc is true, then append \n\000 after the last character -** written. +** spaces. Add a newline if SBS_NEWLINE is set. Translate HTML characters +** if SBS_HTML is set. Pad the rendering out width bytes if SBS_PAD is set. */ -static int sbsWriteText(char *z, DLine *pLine, int width, int trunc){ +static void sbsWriteText(SbsLine *p, DLine *pLine, unsigned flags){ int n = pLine->h & LENGTH_MASK; int i, j; const char *zIn = pLine->z; - for(i=j=0; i<n && j<width; i++){ + char *z = &p->zLine[p->n]; + int w = p->width; + if( n>w ) n = w; + for(i=j=0; i<n; i++){ char c = zIn[i]; if( c=='\t' ){ z[j++] = ' '; - while( (j&7)!=0 && j<width ) z[j++] = ' '; + while( (j&7)!=0 && j<n ) z[j++] = ' '; }else if( c=='\r' || c=='\f' ){ z[j++] = ' '; + }else if( c=='<' && p->escHtml ){ + memcpy(&z[j], "<", 4); + j += 4; + }else if( c=='&' && p->escHtml ){ + memcpy(&z[j], "&", 5); + j += 5; + }else if( c=='>' && p->escHtml ){ + memcpy(&z[j], ">", 4); + j += 4; }else{ z[j++] = c; } } - if( trunc ){ + if( (flags & SBS_ENDSPAN) && p->escHtml ){ + memcpy(&z[j], "</span>", 7); + j += 7; + } + if( (flags & SBS_PAD)!=0 ){ + while( i<w ){ i++; z[j++] = ' '; } + } + if( flags & SBS_NEWLINE ){ z[j++] = '\n'; - z[j] = 0; } - return j; + p->n += j; +} + +/* +** Append a string to an SbSLine with coding, interpretation, or padding. +*/ +static void sbsWrite(SbsLine *p, const char *zIn, int nIn){ + memcpy(p->zLine+p->n, zIn, nIn); + p->n += nIn; +} + +/* +** Append n spaces to the string. +*/ +static void sbsWriteSpace(SbsLine *p, int n){ + while( n-- ) p->zLine[p->n++] = ' '; +} + +/* +** Append a string to the output only if we are rendering HTML. +*/ +static void sbsWriteHtml(SbsLine *p, const char *zIn){ + if( p->escHtml ) sbsWrite(p, zIn, strlen(zIn)); +} + +/* +** Write a 6-digit line number followed by a single space onto the line. +*/ +static void sbsWriteLineno(SbsLine *p, int ln){ + sbsWriteHtml(p, "<span class=\"diffln\">"); + sqlite3_snprintf(7, &p->zLine[p->n], "%5d ", ln+1); + p->n += 6; + sbsWriteHtml(p, "</span>"); + p->zLine[p->n++] = ' '; } /* ** Given a diff context in which the aEdit[] array has been filled ** in, compute a side-by-side diff into pOut. */ -static void sbsDiff(DContext *p, Blob *pOut, int nContext, int width){ +static void sbsDiff( + DContext *p, /* The computed diff */ + Blob *pOut, /* Write the results here */ + int nContext, /* Number of lines of context around each change */ + int width, /* Width of each column of output */ + int escHtml /* True to generate HTML output */ +){ DLine *A; /* Left side of the diff */ DLine *B; /* Right side of the diff */ int a = 0; /* Index of next line in A[] */ int b = 0; /* Index of next line in B[] */ int *R; /* Array of COPY/DELETE/INSERT triples */ @@ -352,18 +479,16 @@ int mxr; /* Maximum value for r */ int na, nb; /* Number of lines shown from A and B */ int i, j; /* Loop counters */ int m, ma, mb;/* Number of lines to output */ int skip; /* Number of lines to skip */ - int mxLine; /* Length of a line of text */ - char *zLine; /* A line of text being formatted */ - int len; /* Length of an output line */ + SbsLine s; /* Output line buffer */ - mxLine = width*2 + 2*7 + 3 + 1; - zLine = fossil_malloc( mxLine + 1 ); - if( zLine==0 ) return; - zLine[mxLine] = 0; + s.zLine = fossil_malloc( 10*width + 100 ); + if( s.zLine==0 ) return; + s.width = width; + s.escHtml = escHtml; A = p->aFrom; B = p->aTo; R = p->aEdit; mxr = p->nEdit; while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } @@ -400,23 +525,31 @@ /* * If the patch changes an empty file or results in an empty file, * the block header must use 0,0 as position indicator and not 1,0. * Otherwise, patch would be confused and may reject the diff. */ - if( r>0 ) blob_appendf(pOut,"%.*c\n", width*2+16, '.'); + if( r>0 ){ + if( escHtml ){ + blob_appendf(pOut, "<span class=\"diffhr\">%.*c</span>\n", + width*2+16, '.'); + }else{ + blob_appendf(pOut, "%.*c\n", width*2+16, '.'); + } + } /* Show the initial common area */ a += skip; b += skip; m = R[r] - skip; for(j=0; j<m; j++){ - memset(zLine, ' ', mxLine); - sbsWriteLineno(zLine, a+j); - sbsWriteText(&zLine[7], &A[a+j], width, 0); - sbsWriteLineno(&zLine[width+10], b+j); - len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1); - blob_append(pOut, zLine, len+width+17); + s.n = 0; + sbsWriteLineno(&s, a+j); + sbsWriteText(&s, &A[a+j], SBS_PAD); + sbsWrite(&s, " ", 3); + sbsWriteLineno(&s, b+j); + sbsWriteText(&s, &B[b+j], SBS_NEWLINE); + blob_append(pOut, s.zLine, s.n); } a += m; b += m; /* Show the differences */ @@ -423,49 +556,53 @@ for(i=0; i<nr; i++){ ma = R[r+i*3+1]; mb = R[r+i*3+2]; m = ma<mb ? ma : mb; for(j=0; j<m; j++){ - memset(zLine, ' ', mxLine); - sbsWriteLineno(zLine, a+j); - sbsWriteText(&zLine[7], &A[a+j], width, 0); - zLine[width+8] = '|'; - sbsWriteLineno(&zLine[width+10], b+j); - len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1); - blob_append(pOut, zLine, len+width+17); + s.n = 0; + sbsWriteLineno(&s, a+j); + sbsWriteHtml(&s, "<span class=\"diffchng\">"); + sbsWriteText(&s, &A[a+j], SBS_PAD | SBS_ENDSPAN); + sbsWrite(&s, " | ", 3); + sbsWriteLineno(&s, b+j); + sbsWriteHtml(&s, "<span class=\"diffchng\">"); + sbsWriteText(&s, &B[b+j], SBS_NEWLINE | SBS_ENDSPAN); + blob_append(pOut, s.zLine, s.n); } a += m; b += m; ma -= m; mb -= m; for(j=0; j<ma; j++){ - memset(zLine, ' ', width+7); - sbsWriteLineno(zLine, a+j); - sbsWriteText(&zLine[7], &A[a+j], width, 0); - zLine[width+8] = '<'; - zLine[width+9] = '\n'; - zLine[width+10] = 0; - blob_append(pOut, zLine, width+10); + s.n = 0; + sbsWriteLineno(&s, a+j); + sbsWriteHtml(&s, "<span class=\"diffrm\">"); + sbsWriteText(&s, &A[a+j], SBS_PAD | SBS_ENDSPAN); + sbsWrite(&s, " <\n", 3); + blob_append(pOut, s.zLine, s.n); } a += ma; for(j=0; j<mb; j++){ - memset(zLine, ' ', mxLine); - zLine[width+8] = '>'; - sbsWriteLineno(&zLine[width+10], b+j); - len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1); - blob_append(pOut, zLine, len+width+17); + s.n = 0; + sbsWriteSpace(&s, width + 7); + sbsWrite(&s, " > ", 3); + sbsWriteLineno(&s, b+j); + sbsWriteHtml(&s, "<span class=\"diffadd\">"); + sbsWriteText(&s, &B[b+j], SBS_NEWLINE | SBS_ENDSPAN); + blob_append(pOut, s.zLine, s.n); } b += mb; if( i<nr-1 ){ m = R[r+i*3+3]; for(j=0; j<m; j++){ - memset(zLine, ' ', mxLine); - sbsWriteLineno(zLine, a+j); - sbsWriteText(&zLine[7], &A[a+j], width, 0); - sbsWriteLineno(&zLine[width+10], b+j); - len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1); - blob_append(pOut, zLine, len+width+17); + s.n = 0; + sbsWriteLineno(&s, a+j); + sbsWriteText(&s, &A[a+j], SBS_PAD); + sbsWrite(&s, " ", 3); + sbsWriteLineno(&s, b+j); + sbsWriteText(&s, &B[b+j], SBS_NEWLINE); + blob_append(pOut, s.zLine, s.n); } b += m; a += m; } } @@ -473,19 +610,20 @@ /* Show the final common area */ assert( nr==i ); m = R[r+nr*3]; if( m>nContext ) m = nContext; for(j=0; j<m; j++){ - memset(zLine, ' ', mxLine); - sbsWriteLineno(zLine, a+j); - sbsWriteText(&zLine[7], &A[a+j], width, 0); - sbsWriteLineno(&zLine[width+10], b+j); - len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1); - blob_append(pOut, zLine, len+width+17); + s.n = 0; + sbsWriteLineno(&s, a+j); + sbsWriteText(&s, &A[a+j], SBS_PAD); + sbsWrite(&s, " ", 3); + sbsWriteLineno(&s, b+j); + sbsWriteText(&s, &B[b+j], SBS_NEWLINE); + blob_append(pOut, s.zLine, s.n); } } - free(zLine); + free(s.zLine); } /* ** Compute the optimal longest common subsequence (LCS) using an ** exhaustive search. This version of the LCS is only used for @@ -786,15 +924,17 @@ /* Compute the difference */ diff_all(&c); if( pOut ){ /* Compute a context or side-by-side diff into pOut */ + int escHtml = (diffFlags & DIFF_HTML)!=0; if( diffFlags & DIFF_SIDEBYSIDE ){ int width = diff_width(diffFlags); - sbsDiff(&c, pOut, nContext, width); + sbsDiff(&c, pOut, nContext, width, escHtml); }else{ - contextDiff(&c, pOut, nContext); + int showLn = (diffFlags & DIFF_LINENO)!=0; + contextDiff(&c, pOut, nContext, showLn, escHtml); } free(c.aFrom); free(c.aTo); free(c.aEdit); return 0; @@ -805,194 +945,10 @@ free(c.aFrom); free(c.aTo); return c.aEdit; } } - -/* -** Copy a line with a limit. Used for side-by-side diffs to enforce a maximum -** line length limit. -*/ -static char *copylimline(char *out, DLine *dl, int lim){ - int len; - len = dl->h & LENGTH_MASK; - if( lim && len > lim ){ - memcpy(out, dl->z, lim-3); - memcpy(&out[lim-3], "...", 4); - }else{ - memcpy(out, dl->z, len); - out[len] = '\0'; - } - return out; -} - -/* -** Output table body of a side-by-side diff. Prior to the call, the caller -** should have output: -** <table class="sbsdiff"> -** <tr><th colspan="2" class="diffhdr">Old title</th><th/> -** <th colspan="2" class="diffhdr">New title</th></tr> -** -** And after the call, it should output: -** </table> -** -** Some good reference diffs in the fossil repository for testing: -** /vdiff?from=080d27a&to=4b0f813&detail=1 -** /vdiff?from=636804745b&to=c1d78e0556&detail=1 -** /vdiff?from=c0b6c28d29&to=25169506b7&detail=1 -** /vdiff?from=e3d022dffa&to=48bcfbd47b&detail=1 -*/ -int html_sbsdiff( - Blob *pA_Blob, /* FROM file */ - Blob *pB_Blob, /* TO file */ - int nContext, /* Amount of context to unified diff */ - int ignoreEolWs /* Ignore whitespace at the end of lines */ -){ - DContext c; - int i; - int iFrom, iTo; - char *linebuf; - int collim=0; /* Currently not settable; allows a column limit for diffs */ - int allowExp=0; /* Currently not settable; (dis)allow expansion of rows */ - - /* Prepare the input files */ - memset(&c, 0, sizeof(c)); - c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), - &c.nFrom, ignoreEolWs); - c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), - &c.nTo, ignoreEolWs); - if( c.aFrom==0 || c.aTo==0 ){ - free(c.aFrom); - free(c.aTo); - /* Note: This would be generated within a table. */ - @ <p class="generalError" style="white-space: nowrap">cannot compute - @ difference between binary files</p> - return 0; - } - - collim = collim < 4 ? 0 : collim; - - /* Compute the difference */ - diff_all(&c); - - linebuf = fossil_malloc(LENGTH_MASK+1); - if( !linebuf ){ - free(c.aFrom); - free(c.aTo); - free(c.aEdit); - return 0; - } - - iFrom=iTo=0; - i=0; - while( i<c.nEdit ){ - int j; - /* Copied lines */ - for( j=0; j<c.aEdit[i]; j++){ - /* Hide lines which are copied and are further away from block boundaries - ** than nContext lines. For each block with hidden lines, show a row - ** notifying the user about the hidden rows. - */ - if( j<nContext || j>c.aEdit[i]-nContext-1 ){ - @ <tr> - }else if( j==nContext && j<c.aEdit[i]-nContext-1 ){ - @ <tr> - @ <td class="meta" colspan="5" style="white-space: nowrap;"> - @ %d(c.aEdit[i]-2*nContext) hidden lines</td> - @ </tr> - if( !allowExp ) - continue; - @ <tr style="display:none;"> - }else{ - if( !allowExp ) - continue; - @ <tr style="display:none;"> - } - - copylimline(linebuf, &c.aFrom[iFrom+j], collim); - @ <td class="lineno">%d(iFrom+j+1)</td> - @ <td class="srcline">%h(linebuf)</td> - - @ <td> </td> - - copylimline(linebuf, &c.aTo[iTo+j], collim); - @ <td class="lineno">%d(iTo+j+1)</td> - @ <td class="srcline">%h(linebuf)</td> - - @ </tr> - } - iFrom+=c.aEdit[i]; - iTo+=c.aEdit[i]; - - if( c.aEdit[i+1]!=0 && c.aEdit[i+2]!=0 ){ - int lim; - lim = c.aEdit[i+1] > c.aEdit[i+2] ? c.aEdit[i+1] : c.aEdit[i+2]; - - /* Assume changed lines */ - for( j=0; j<lim; j++ ){ - @ <tr> - - if( j<c.aEdit[i+1] ){ - copylimline(linebuf, &c.aFrom[iFrom+j], collim); - @ <td class="changed lineno">%d(iFrom+j+1)</td> - @ <td class="changed srcline">%h(linebuf)</td> - }else{ - @ <td colspan="2" class="changedvoid"/> - } - - @ <td class="changed">|</td> - - if( j<c.aEdit[i+2] ){ - copylimline(linebuf, &c.aTo[iTo+j], collim); - @ <td class="changed lineno">%d(iTo+j+1)</td> - @ <td class="changed srcline">%h(linebuf)</td> - }else{ - @ <td colspan="2" class="changedvoid"/> - } - - @ </tr> - } - iFrom+=c.aEdit[i+1]; - iTo+=c.aEdit[i+2]; - }else{ - - /* Process deleted lines */ - for( j=0; j<c.aEdit[i+1]; j++ ){ - @ <tr> - - copylimline(linebuf, &c.aFrom[iFrom+j], collim); - @ <td class="removed lineno">%d(iFrom+j+1)</td> - @ <td class="removed srcline">%h(linebuf)</td> - @ <td><</td> - @ <td colspan="2" class="removedvoid"/> - @ </tr> - } - iFrom+=c.aEdit[i+1]; - - /* Process inserted lines */ - for( j=0; j<c.aEdit[i+2]; j++ ){ - @ <tr> - @ <td colspan="2" class="addedvoid"/> - @ <td>></td> - copylimline(linebuf, &c.aTo[iTo+j], collim); - @ <td class="added lineno">%d(iTo+j+1)</td> - @ <td class="added srcline">%h(linebuf)</td> - @ </tr> - } - iTo+=c.aEdit[i+2]; - } - - i+=3; - } - - free(linebuf); - free(c.aFrom); - free(c.aTo); - free(c.aEdit); - return 1; -} - /* ** COMMAND: test-rawdiff */ void test_rawdiff_cmd(void){ @@ -1019,10 +975,12 @@ ** "diffFlags" integer. ** ** --side-by-side|-y Side-by-side diff. DIFF_SIDEBYSIDE ** --context|-c N N lines of context. DIFF_CONTEXT_MASK ** --width|-W N N character lines. DIFF_WIDTH_MASK +** --html Format for HTML DIFF_HTML +** --linenum|-n Show line numbers DIFF_LINENO */ int diff_options(void){ int diffFlags = 0; const char *z; int f; @@ -1034,10 +992,12 @@ if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){ f *= DIFF_CONTEXT_MASK+1; if( f > DIFF_WIDTH_MASK ) f = DIFF_CONTEXT_MASK; diffFlags |= f; } + if( find_option("html",0,0)!=0 ) diffFlags |= DIFF_HTML; + if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO; return diffFlags; } /* ** COMMAND: test-udiff Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -251,11 +251,11 @@ /* ** Append the difference between two RIDs to the output */ -static void append_diff(const char *zFrom, const char *zTo){ +static void append_diff(const char *zFrom, const char *zTo, int diffFlags){ int fromid; int toid; Blob from, to, out; if( zFrom ){ fromid = uuid_to_rid(zFrom, 0); @@ -268,46 +268,26 @@ content_get(toid, &to); }else{ blob_zero(&to); } blob_zero(&out); - text_diff(&from, &to, &out, DIFF_IGNORE_EOLWS | 5); - @ %h(blob_str(&out)) + if( diffFlags & DIFF_SIDEBYSIDE ){ + text_diff(&from, &to, &out, diffFlags | DIFF_HTML); + @ <div class="sbsdiff"> + @ %s(blob_str(&out)) + @ </div> + }else{ + text_diff(&from, &to, &out, diffFlags | DIFF_LINENO | DIFF_HTML); + @ <div class="udiff"> + @ %s(blob_str(&out)) + @ </div> + } blob_reset(&from); blob_reset(&to); blob_reset(&out); } - -/* -** Write the difference between two RIDs to the output -*/ -static void generate_sbsdiff(const char *zFrom, const char *zTo){ - int fromid; - int toid; - Blob from, to; - if( zFrom ){ - fromid = uuid_to_rid(zFrom, 0); - content_get(fromid, &from); - }else{ - blob_zero(&from); - } - if( zTo ){ - toid = uuid_to_rid(zTo, 0); - content_get(toid, &to); - }else{ - blob_zero(&to); - } - @ <table class="sbsdiff"> - @ <tr><th colspan="2" class="diffhdr">Old (%S(zFrom))</th><th/> - @ <th colspan="2" class="diffhdr">New (%S(zTo))</th></tr> - html_sbsdiff(&from, &to, 5, 1); - @ </table> - blob_reset(&from); - blob_reset(&to); -} - /* ** Write a line of web-page output that shows changes that have occurred ** to a file between two check-ins. */ @@ -314,12 +294,11 @@ static void append_file_change_line( const char *zName, /* Name of the file that has changed */ const char *zOld, /* blob.uuid before change. NULL for added files */ const char *zNew, /* blob.uuid after change. NULL for deletes */ const char *zOldName, /* Prior name. NULL if no name change. */ - int showDiff, /* Show edit diffs if true */ - int sideBySide, /* Show diffs side-by-side */ + int diffFlags, /* Flags for text_diff(). Zero to omit diffs */ int mperm /* executable or symlink permission for zNew */ ){ if( !g.perm.History ){ if( zNew==0 ){ @ <p>Deleted %h(zName)</p> @@ -331,18 +310,14 @@ @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") @ for %h(zName)</p> }else{ @ <p>Changes to %h(zName)</p> } - if( showDiff ){ - if( sideBySide ){ - generate_sbsdiff(zOld, zNew); - }else{ - @ <blockquote><pre> - append_diff(zOld, zNew); - @ </pre></blockquote> - } + if( diffFlags ){ + @ <pre style="white-space:pre;"> + append_diff(zOld, zNew, diffFlags); + @ </pre> } }else{ if( zOld && zNew ){ if( fossil_strcmp(zOld, zNew)!=0 ){ @ <p>Modified <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> @@ -361,25 +336,50 @@ @ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a> }else{ @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a> } - if( showDiff ){ - if( sideBySide ){ - generate_sbsdiff(zOld, zNew); - }else{ - @ <blockquote><pre> - append_diff(zOld, zNew); - @ </pre></blockquote> - } + if( diffFlags ){ + @ <pre style="white-space:pre;"> + append_diff(zOld, zNew, diffFlags); + @ </pre> }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ @ @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&v2=%S(zNew)">[diff]</a> } @ </p> } } + +/* +** Construct an appropriate diffFlag for text_diff() based on query +** parameters and the to boolean arguments. +*/ +static int construct_diff_flags(int showDiff, int sideBySide){ + int diffFlags; + if( showDiff==0 ){ + diffFlags = 0; /* Zero means do not show any diff */ + }else{ + int x; + if( sideBySide ){ + diffFlags = DIFF_SIDEBYSIDE | DIFF_IGNORE_EOLWS; + + /* "dw" query parameter determines width of each column */ + x = atoi(PD("dw","80"))*(DIFF_CONTEXT_MASK+1); + if( x<0 || x>DIFF_WIDTH_MASK ) x = DIFF_WIDTH_MASK; + diffFlags += x; + }else{ + diffFlags = DIFF_INLINE | DIFF_IGNORE_EOLWS; + } + + /* "dc" query parameter determines lines of context */ + x = atoi(PD("dc","7")); + if( x<0 || x>DIFF_CONTEXT_MASK ) x = DIFF_CONTEXT_MASK; + diffFlags += x; + } + return diffFlags; +} /* ** WEBPAGE: vinfo ** WEBPAGE: ci @@ -397,12 +397,13 @@ */ void ci_page(void){ Stmt q; int rid; int isLeaf; - int showDiff; - int sideBySide; + int showDiff; /* True to show diffs */ + int sideBySide; /* True for side-by-side diffs */ + int diffFlags; /* Flag parameter for text_diff() */ const char *zName; /* Name of the checkin to be displayed */ const char *zUuid; /* UUID of zName */ const char *zParent; /* UUID of the parent checkin (if any) */ login_check_credentials(); @@ -595,18 +596,18 @@ " FROM mlink JOIN filename ON filename.fnid=mlink.fnid" " WHERE mlink.mid=%d" " ORDER BY name /*sort*/", rid ); + diffFlags = construct_diff_flags(showDiff, sideBySide); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q,0); int mperm = db_column_int(&q, 1); const char *zOld = db_column_text(&q,2); const char *zNew = db_column_text(&q,3); const char *zOldName = db_column_text(&q, 4); - append_file_change_line(zName, zOld, zNew, zOldName, showDiff, - sideBySide, mperm); + append_file_change_line(zName, zOld, zNew, zOldName, diffFlags, mperm); } db_finalize(&q); } style_footer(); } @@ -760,10 +761,11 @@ */ void vdiff_page(void){ int ridFrom, ridTo; int showDetail = 0; int sideBySide = 0; + int diffFlags = 0; Manifest *pFrom, *pTo; ManifestFile *pFileFrom, *pFileTo; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } @@ -771,12 +773,13 @@ pFrom = vdiff_parse_manifest("from", &ridFrom); if( pFrom==0 ) return; pTo = vdiff_parse_manifest("to", &ridTo); if( pTo==0 ) return; - showDetail = atoi(PD("detail","0")); sideBySide = atoi(PD("sbs","1")); + showDetail = atoi(PD("detail","0")); + if( !showDetail && sideBySide ) showDetail = 1; if( !sideBySide ){ style_submenu_element("Side-by-side Diff", "sbsdiff", "%s/vdiff?from=%T&to=%T&detail=%d&sbs=1", g.zTop, P("from"), P("to"), showDetail); }else{ @@ -793,10 +796,11 @@ manifest_file_rewind(pFrom); pFileFrom = manifest_file_next(pFrom, 0); manifest_file_rewind(pTo); pFileTo = manifest_file_next(pTo, 0); + diffFlags = construct_diff_flags(showDetail, sideBySide); while( pFileFrom || pFileTo ){ int cmp; if( pFileFrom==0 ){ cmp = +1; }else if( pFileTo==0 ){ @@ -804,25 +808,25 @@ }else{ cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); } if( cmp<0 ){ append_file_change_line(pFileFrom->zName, - pFileFrom->zUuid, 0, 0, 0, 0, 0); + pFileFrom->zUuid, 0, 0, 0, 0); pFileFrom = manifest_file_next(pFrom, 0); }else if( cmp>0 ){ append_file_change_line(pFileTo->zName, - 0, pFileTo->zUuid, 0, 0, 0, + 0, pFileTo->zUuid, 0, 0, manifest_file_mperm(pFileTo)); pFileTo = manifest_file_next(pTo, 0); }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ /* No changes */ pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); }else{ append_file_change_line(pFileFrom->zName, pFileFrom->zUuid, - pFileTo->zUuid, 0, showDetail, sideBySide, + pFileTo->zUuid, 0, diffFlags, manifest_file_mperm(pFileTo)); pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); } } @@ -1069,10 +1073,11 @@ int isPatch; int sideBySide; Blob c1, c2, diff, *pOut; char *zV1; char *zV2; + int diffFlags; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } v1 = name_to_rid_www("v1"); v2 = name_to_rid_www("v2"); @@ -1082,21 +1087,25 @@ zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2); isPatch = P("patch")!=0; if( isPatch ){ pOut = cgi_output_blob(); cgi_set_content_type("text/plain"); + diffFlags = 4; }else{ blob_zero(&diff); pOut = &diff; + if( sideBySide ){ + diffFlags = DIFF_IGNORE_EOLWS | DIFF_SIDEBYSIDE | 7; + }else{ + diffFlags = DIFF_IGNORE_EOLWS | 7; + } } - if( !sideBySide || isPatch ){ - content_get(v1, &c1); - content_get(v2, &c2); - text_diff(&c1, &c2, pOut, 4 | 0); - blob_reset(&c1); - blob_reset(&c2); - } + content_get(v1, &c1); + content_get(v2, &c2); + text_diff(&c1, &c2, pOut, diffFlags); + blob_reset(&c1); + blob_reset(&c2); if( !isPatch ){ style_header("Diff"); style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch", g.zTop, P("v1"), P("v2")); if( !sideBySide ){ @@ -1113,17 +1122,13 @@ @ Artifact <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a>:</h2> object_description(v1, 0, 0); @ <h2>To Artifact <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>:</h2> object_description(v2, 0, 0); @ <hr /> - if( sideBySide ){ - generate_sbsdiff(zV1, zV2); - }else{ - @ <blockquote><pre> - @ %h(blob_str(&diff)) - @ </pre></blockquote> - } + @ <pre style="while-space:pre;"> + @ %h(blob_str(&diff)) + @ </pre> blob_reset(&diff); style_footer(); } } Index: src/manifest.c ================================================================== --- src/manifest.c +++ src/manifest.c @@ -1245,12 +1245,12 @@ if( p->zBaseline==0 ) return 0; fetch_baseline(p, 1); pBase = p->pBaseline; if( pBase==0 ) return 0; for(i=0; i<pBase->nFile; i++){ - if( fossil_stricmp(zName, p->aFile[i].zName)==0 ){ - return &p->aFile[i]; + if( fossil_stricmp(zName, pBase->aFile[i].zName)==0 ){ + return &pBase->aFile[i]; } } return 0; } Index: src/skins.c ================================================================== --- src/skins.c +++ src/skins.c @@ -153,55 +153,10 @@ @ /* The label/value pairs on (for example) the vinfo page */ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; -@ } -@ -@ /* Side-by-side diff */ -@ table.sbsdiff { -@ background-color: white; -@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; -@ font-size: 8pt; -@ border-collapse:collapse; -@ white-space: pre; -@ width: 98%; -@ border: 1px #000 dashed; -@ } -@ -@ table.sbsdiff th.diffhdr { -@ border-bottom: dotted; -@ border-width: 1px; -@ } -@ -@ table.sbsdiff tr td { -@ white-space: pre; -@ padding-left: 3px; -@ padding-right: 3px; -@ margin: 0px; -@ } -@ -@ table.sbsdiff tr td.lineno { -@ text-align: right; -@ } -@ -@ table.sbsdiff tr td.meta { -@ color: white; -@ background-color: rgb(20, 20, 20); -@ text-align: center; -@ } -@ -@ table.sbsdiff tr td.added { -@ background-color: rgb(230, 230, 230); -@ } -@ -@ table.sbsdiff tr td.removed { -@ background-color: rgb(200, 200, 200); -@ } -@ -@ table.sbsdiff tr td.changed { -@ background-color: rgb(220, 220, 220); @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" @@ -400,54 +355,10 @@ @ /* The label/value pairs on (for example) the ci page */ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; -@ } -@ -@ /* Side-by-side diff */ -@ table.sbsdiff { -@ background-color: #ffffc5; -@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; -@ font-size: 8pt; -@ border-collapse:collapse; -@ white-space: pre; -@ width: 98%; -@ border: 1px #000 dashed; -@ } -@ -@ table.sbsdiff th.diffhdr { -@ border-bottom: dotted; -@ border-width: 1px; -@ } -@ -@ table.sbsdiff tr td { -@ white-space: pre; -@ padding-left: 3px; -@ padding-right: 3px; -@ margin: 0px; -@ } -@ -@ table.sbsdiff tr td.lineno { -@ text-align: right; -@ } -@ -@ table.sbsdiff tr td.meta { -@ background-color: #a09048; -@ text-align: center; -@ } -@ -@ table.sbsdiff tr td.added { -@ background-color: rgb(210, 210, 100); -@ } -@ -@ table.sbsdiff tr td.removed { -@ background-color: rgb(190, 200, 110); -@ } -@ -@ table.sbsdiff tr td.changed { -@ background-color: rgb(200, 210, 120); @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" @@ -680,56 +591,10 @@ @ /* The label/value pairs on (for example) the ci page */ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; -@ } -@ -@ /* Side-by-side diff */ -@ table.sbsdiff { -@ background-color: white; -@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; -@ font-size: 6pt; -@ border-collapse:collapse; -@ white-space: pre; -@ width: 98%; -@ border: 1px #000 dashed; -@ } -@ -@ table.sbsdiff th.diffhdr { -@ border-bottom: dotted; -@ border-width: 1px; -@ } -@ -@ table.sbsdiff tr td { -@ white-space: pre; -@ padding-left: 3px; -@ padding-right: 3px; -@ margin: 0px; -@ } -@ -@ table.sbsdiff tr td.lineno { -@ text-align: right; -@ } -@ -@ table.sbsdiff tr td.meta { -@ color: white; -@ background-color: black; -@ text-align: center; -@ } -@ -@ table.sbsdiff tr td.added { -@ background-color: white; -@ } -@ -@ table.sbsdiff tr td.removed { -@ background-color: white; -@ text-decoration: line-through; -@ } -@ -@ table.sbsdiff tr td.changed { -@ background-color: white; @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" @@ -1024,77 +889,10 @@ @ padding: 3px 5px; @ } @ @ textarea { @ font-size: 1em; -@ } -@ -@ /* Side-by-side diff */ -@ table.sbsdiff { -@ background-color: white; -@ font-family: Dejavu Sans Mono, Monaco, Lucida Console, monospace; -@ font-size: 6pt; -@ border-collapse:collapse; -@ width: 98%; -@ border: 1px #000 dashed; -@ margin-left: auto; -@ margin-right: auto; -@ } -@ -@ table.sbsdiff th.diffhdr { -@ border-bottom: dotted; -@ border-width: 1px; -@ } -@ -@ table.sbsdiff tr td { -@ padding-left: 3px; -@ padding-right: 3px; -@ margin: 0px; -@ vertical-align: top; -@ white-space: pre-wrap; -@ } -@ -@ table.sbsdiff tr td.lineno { -@ text-align: right; -@ /* border-bottom: 1px solid rgb(220, 220, 220); */ -@ } -@ -@ table.sbsdiff tr td.srcline { -@ /* max-width: 400px; */ -@ /* Note: May partially hide long lines without whitespaces */ -@ /* overflow: hidden; */ -@ /* border-bottom: 1px solid rgb(220, 220, 220); */ -@ } -@ -@ table.sbsdiff tr td.meta { -@ background-color: rgb(170, 160, 255); -@ padding-top: 0.25em; -@ padding-bottom: 0.25em; -@ text-align: center; -@ -moz-border-radius: 5px; -@ -moz-border-radius: 5px; -@ -webkit-border-radius: 5px; -@ -webkit-border-radius: 5px; -@ -border-radius: 5px; -@ -border-radius: 5px; -@ border-radius: 5px; -@ border-radius: 5px; -@ } -@ -@ table.sbsdiff tr td.added { -@ background-color: rgb(180, 250, 180); -@ /* border-bottom: 1px solid rgb(160, 230, 160); */ -@ } -@ -@ table.sbsdiff tr td.removed { -@ background-color: rgb(250, 130, 130); -@ /* border-bottom: 1px solid rgb(230, 110, 110); */ -@ } -@ -@ table.sbsdiff tr td.changed { -@ background-color: rgb(210, 210, 200); -@ /* border-bottom: 1px solid rgb(190, 190, 180); */ @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -398,70 +398,10 @@ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; @ } -@ -@ /* Side-by-side diff */ -@ table.sbsdiff { -@ background-color: white; -@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; -@ font-size: 8pt; -@ border-collapse:collapse; -@ white-space: pre; -@ width: 98%; -@ border: 1px #000 dashed; -@ margin-left: auto; -@ margin-right: auto; -@ } -@ -@ table.sbsdiff th.diffhdr { -@ border-bottom: dotted; -@ border-width: 1px; -@ } -@ -@ table.sbsdiff tr td { -@ white-space: pre; -@ padding-left: 3px; -@ padding-right: 3px; -@ margin: 0px; -@ vertical-align: top; -@ } -@ -@ table.sbsdiff tr td.lineno { -@ text-align: right; -@ } -@ -@ table.sbsdiff tr td.srcline { -@ } -@ -@ table.sbsdiff tr td.meta { -@ background-color: rgb(170, 160, 255); -@ text-align: center; -@ } -@ -@ table.sbsdiff tr td.added { -@ background-color: rgb(180, 250, 180); -@ } -@ table.sbsdiff tr td.addedvoid { -@ background-color: rgb(190, 190, 180); -@ } -@ -@ table.sbsdiff tr td.removed { -@ background-color: rgb(250, 130, 130); -@ } -@ table.sbsdiff tr td.removedvoid { -@ background-color: rgb(190, 190, 180); -@ } -@ -@ table.sbsdiff tr td.changed { -@ background-color: rgb(210, 210, 200); -@ } -@ table.sbsdiff tr td.changedvoid { -@ background-color: rgb(190, 190, 180); -@ } -@ ; /* The following table contains bits of default CSS that must ** be included if they are not found in the application-defined @@ -811,10 +751,41 @@ { "ul.filelist", "List of files in a timeline", @ margin-top: 3px; @ line-height: 100%; }, + { "div.sbsdiff", + "side-by-side diff display", + @ font-family: monospace; + @ font-size: smaller; + @ white-space: pre; + }, + { "div.udiff", + "context diff display", + @ font-family: monospace; + @ white-space: pre; + }, + { "span.diffchng", + "changes in a diff", + @ background-color: #ffffc8; + }, + { "span.diffadd", + "added code in a diff", + @ background-color: #e0ffe0; + }, + { "span.diffrm", + "deleted in a diff", + @ background-color: #ffe0e0; + }, + { "span.diffhr", + "suppressed lines in a diff", + @ color: #0000ff; + }, + { "span.diffln", + "line nubmers in a diff", + @ color: #a0a0a0; + }, { 0, 0, 0 } };