Fossil SCM
Merged importer to mainline.
Commit
0523983440b0cd26366d924a3674eab30e93e1f1
Parent
3e76f2a5f067edd…
2 files changed
+363
-8
+171
-35
+363
-8
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -42,29 +42,53 @@ | ||
| 42 | 42 | ** line. If any line is longer than 1048575 characters, |
| 43 | 43 | ** the file is considered binary. |
| 44 | 44 | */ |
| 45 | 45 | typedef struct DLine DLine; |
| 46 | 46 | struct DLine { |
| 47 | - const char *z; /* The text of the line */ | |
| 48 | - unsigned int h; /* Hash of the line */ | |
| 47 | + const char *z; /* The text of the line */ | |
| 48 | + unsigned int h; /* Hash of the line */ | |
| 49 | + unsigned int iNext; /* Index+1 of next line with same the same hash */ | |
| 50 | + | |
| 51 | + /* an array of DLine elements services two purposes. The fields | |
| 52 | + ** above are one per line of input text. But each entry is also | |
| 53 | + ** a bucket in a hash table. */ | |
| 54 | + unsigned int iHash; /* First entry+1 in the hash array */ | |
| 49 | 55 | }; |
| 50 | 56 | |
| 51 | -#define LENGTH_MASK 0x000fffff | |
| 57 | +/* | |
| 58 | +** Maximum length of a line in a text file. (8192) | |
| 59 | +*/ | |
| 60 | +#define LENGTH_MASK_SZ 13 | |
| 61 | +#define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1) | |
| 62 | + | |
| 63 | +/* | |
| 64 | +** A context for running a diff. | |
| 65 | +*/ | |
| 66 | +typedef struct DContext DContext; | |
| 67 | +struct DContext { | |
| 68 | + int *aEdit; /* Array of copy/delete/insert triples */ | |
| 69 | + int nEdit; /* Number of integers (3x num of triples) in aEdit[] */ | |
| 70 | + int nEditAlloc; /* Space allocated for aEdit[] */ | |
| 71 | + DLine *aFrom; /* File on left side of the diff */ | |
| 72 | + int nFrom; /* Number of lines in aFrom[] */ | |
| 73 | + DLine *aTo; /* File on right side of the diff */ | |
| 74 | + int nTo; /* Number of lines in aTo[] */ | |
| 75 | +}; | |
| 52 | 76 | |
| 53 | 77 | /* |
| 54 | 78 | ** Return an array of DLine objects containing a pointer to the |
| 55 | 79 | ** start of each line and a hash of that line. The lower |
| 56 | 80 | ** bits of the hash store the length of each line. |
| 57 | 81 | ** |
| 58 | 82 | ** Trailing whitespace is removed from each line. |
| 59 | 83 | ** |
| 60 | 84 | ** Return 0 if the file is binary or contains a line that is |
| 61 | -** longer than 1048575 bytes. | |
| 85 | +** too long. | |
| 62 | 86 | */ |
| 63 | 87 | static DLine *break_into_lines(char *z, int *pnLine){ |
| 64 | 88 | int nLine, i, j, k, x; |
| 65 | - unsigned int h; | |
| 89 | + unsigned int h, h2; | |
| 66 | 90 | DLine *a; |
| 67 | 91 | |
| 68 | 92 | /* Count the number of lines. Allocate space to hold |
| 69 | 93 | ** the returned array. |
| 70 | 94 | */ |
| @@ -73,31 +97,35 @@ | ||
| 73 | 97 | if( c==0 ){ |
| 74 | 98 | return 0; |
| 75 | 99 | } |
| 76 | 100 | if( c=='\n' && z[i+1]!=0 ){ |
| 77 | 101 | nLine++; |
| 78 | - if( j>1048575 ){ | |
| 102 | + if( j>LENGTH_MASK ){ | |
| 79 | 103 | return 0; |
| 80 | 104 | } |
| 81 | 105 | j = 0; |
| 82 | 106 | } |
| 83 | 107 | } |
| 84 | - if( j>1048575 ){ | |
| 108 | + if( j>LENGTH_MASK ){ | |
| 85 | 109 | return 0; |
| 86 | 110 | } |
| 87 | 111 | a = malloc( nLine*sizeof(a[0]) ); |
| 88 | 112 | if( a==0 ) fossil_panic("out of memory"); |
| 113 | + memset(a, 0, nLine*sizeof(a[0]) ); | |
| 89 | 114 | |
| 90 | 115 | /* Fill in the array */ |
| 91 | 116 | for(i=0; i<nLine; i++){ |
| 92 | 117 | a[i].z = z; |
| 93 | 118 | for(j=0; z[j] && z[j]!='\n'; j++){} |
| 94 | 119 | for(k=j; k>0 && isspace(z[k-1]); k--){} |
| 95 | 120 | for(h=0, x=0; x<k; x++){ |
| 96 | 121 | h = h ^ (h<<2) ^ z[x]; |
| 97 | 122 | } |
| 98 | - a[i].h = (h<<20) | k;; | |
| 123 | + a[i].h = h = (h<<LENGTH_MASK_SZ) | k;; | |
| 124 | + h2 = h % nLine; | |
| 125 | + a[i].iNext = a[h2].iHash; | |
| 126 | + a[h2].iHash = i+1; | |
| 99 | 127 | z += j+1; |
| 100 | 128 | } |
| 101 | 129 | |
| 102 | 130 | /* Return results */ |
| 103 | 131 | *pnLine = nLine; |
| @@ -118,10 +146,336 @@ | ||
| 118 | 146 | blob_append(pOut, zPrefix, 1); |
| 119 | 147 | blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK); |
| 120 | 148 | blob_append(pOut, "\n", 1); |
| 121 | 149 | } |
| 122 | 150 | |
| 151 | +/* | |
| 152 | +** Expand the size of aEdit[] array to hold nEdit elements. | |
| 153 | +*/ | |
| 154 | +static void expandEdit(DContext *p, int nEdit){ | |
| 155 | + int *a; | |
| 156 | + a = realloc(p->aEdit, nEdit*sizeof(int)); | |
| 157 | + if( a==0 ){ | |
| 158 | + free( p->aEdit ); | |
| 159 | + p->nEdit = 0; | |
| 160 | + nEdit = 0; | |
| 161 | + } | |
| 162 | + p->aEdit = a; | |
| 163 | + p->nEditAlloc = nEdit; | |
| 164 | +} | |
| 165 | + | |
| 166 | +/* | |
| 167 | +** Append a new COPY/DELETE/INSERT triple. | |
| 168 | +*/ | |
| 169 | +static void appendTriple(DContext *p, int nCopy, int nDel, int nIns){ | |
| 170 | + /* printf("APPEND %d/%d/%d\n", nCopy, nDel, nIns); */ | |
| 171 | + if( p->nEdit>=3 ){ | |
| 172 | + if( p->aEdit[p->nEdit-1]==0 ){ | |
| 173 | + if( p->aEdit[p->nEdit-2]==0 ){ | |
| 174 | + p->aEdit[p->nEdit-3] += nCopy; | |
| 175 | + p->aEdit[p->nEdit-2] += nDel; | |
| 176 | + p->aEdit[p->nEdit-1] += nIns; | |
| 177 | + return; | |
| 178 | + } | |
| 179 | + if( nCopy==0 ){ | |
| 180 | + p->aEdit[p->nEdit-2] += nDel; | |
| 181 | + p->aEdit[p->nEdit-1] += nIns; | |
| 182 | + return; | |
| 183 | + } | |
| 184 | + } | |
| 185 | + if( nCopy==0 && nDel==0 ){ | |
| 186 | + p->aEdit[p->nEdit-1] += nIns; | |
| 187 | + return; | |
| 188 | + } | |
| 189 | + } | |
| 190 | + if( p->nEdit+3>p->nEditAlloc ){ | |
| 191 | + expandEdit(p, p->nEdit*2 + 15); | |
| 192 | + if( p->aEdit==0 ) return; | |
| 193 | + } | |
| 194 | + p->aEdit[p->nEdit++] = nCopy; | |
| 195 | + p->aEdit[p->nEdit++] = nDel; | |
| 196 | + p->aEdit[p->nEdit++] = nIns; | |
| 197 | +} | |
| 198 | + | |
| 199 | + | |
| 200 | +/* | |
| 201 | +** Given a diff context in which the aEdit[] array has been filled | |
| 202 | +** in, compute a context diff into pOut. | |
| 203 | +*/ | |
| 204 | +static void contextDiff(DContext *p, Blob *pOut, int nContext){ | |
| 205 | + DLine *A; /* Left side of the diff */ | |
| 206 | + DLine *B; /* Right side of the diff */ | |
| 207 | + int a = 0; /* Index of next line in A[] */ | |
| 208 | + int b = 0; /* Index of next line in B[] */ | |
| 209 | + int *R; /* Array of COPY/DELETE/INSERT triples */ | |
| 210 | + int r; /* Index into R[] */ | |
| 211 | + int nr; /* Number of COPY/DELETE/INSERT triples to process */ | |
| 212 | + int mxr; /* Maximum value for r */ | |
| 213 | + int na, nb; /* Number of lines shown from A and B */ | |
| 214 | + int i, j; /* Loop counters */ | |
| 215 | + int m; /* Number of lines to output */ | |
| 216 | + int skip; /* Number of lines to skip */ | |
| 217 | + | |
| 218 | + A = p->aFrom; | |
| 219 | + B = p->aTo; | |
| 220 | + R = p->aEdit; | |
| 221 | + mxr = p->nEdit; | |
| 222 | + if( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } | |
| 223 | + for(r=0; r<mxr; r += 3*nr){ | |
| 224 | + /* Figure out how many triples to show in a single block */ | |
| 225 | + for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){} | |
| 226 | + DEBUG( printf("r=%d nr=%d\n", r, nr); ) | |
| 227 | + | |
| 228 | + /* For the current block comprising nr triples, figure out | |
| 229 | + ** how many lines of A and B are to be displayed | |
| 230 | + */ | |
| 231 | + if( R[r]>nContext ){ | |
| 232 | + na = nb = nContext; | |
| 233 | + skip = R[r] - nContext; | |
| 234 | + }else{ | |
| 235 | + na = nb = R[r]; | |
| 236 | + skip = 0; | |
| 237 | + } | |
| 238 | + for(i=0; i<nr; i++){ | |
| 239 | + na += R[r+i*3+1]; | |
| 240 | + nb += R[r+i*3+2]; | |
| 241 | + } | |
| 242 | + if( R[r+nr*3]>nContext ){ | |
| 243 | + na += nContext; | |
| 244 | + nb += nContext; | |
| 245 | + }else{ | |
| 246 | + na += R[r+nr*3]; | |
| 247 | + nb += R[r+nr*3]; | |
| 248 | + } | |
| 249 | + for(i=1; i<nr; i++){ | |
| 250 | + na += R[r+i*3]; | |
| 251 | + nb += R[r+i*3]; | |
| 252 | + } | |
| 253 | + blob_appendf(pOut,"@@ -%d,%d +%d,%d @@\n", a+skip+1, na, b+skip+1, nb); | |
| 254 | + | |
| 255 | + /* Show the initial common area */ | |
| 256 | + a += skip; | |
| 257 | + b += skip; | |
| 258 | + m = R[r] - skip; | |
| 259 | + for(j=0; j<m; j++){ | |
| 260 | + appendDiffLine(pOut, " ", &A[a+j]); | |
| 261 | + } | |
| 262 | + a += m; | |
| 263 | + b += m; | |
| 264 | + | |
| 265 | + /* Show the differences */ | |
| 266 | + for(i=0; i<nr; i++){ | |
| 267 | + m = R[r+i*3+1]; | |
| 268 | + for(j=0; j<m; j++){ | |
| 269 | + appendDiffLine(pOut, "-", &A[a+j]); | |
| 270 | + } | |
| 271 | + a += m; | |
| 272 | + m = R[r+i*3+2]; | |
| 273 | + for(j=0; j<m; j++){ | |
| 274 | + appendDiffLine(pOut, "+", &B[b+j]); | |
| 275 | + } | |
| 276 | + b += m; | |
| 277 | + if( i<nr-1 ){ | |
| 278 | + m = R[r+i*3+3]; | |
| 279 | + for(j=0; j<m; j++){ | |
| 280 | + appendDiffLine(pOut, " ", &B[b+j]); | |
| 281 | + } | |
| 282 | + b += m; | |
| 283 | + a += m; | |
| 284 | + } | |
| 285 | + } | |
| 286 | + | |
| 287 | + /* Show the final common area */ | |
| 288 | + assert( nr==i ); | |
| 289 | + m = R[r+nr*3]; | |
| 290 | + if( m>nContext ) m = nContext; | |
| 291 | + for(j=0; j<m; j++){ | |
| 292 | + appendDiffLine(pOut, " ", &B[b+j]); | |
| 293 | + } | |
| 294 | + } | |
| 295 | +} | |
| 296 | + | |
| 297 | +/* | |
| 298 | +** Compare two blocks of text on lines iS1 through iE1-1 of the aFrom[] | |
| 299 | +** file and lines iS2 through iE2-1 of the aTo[] file. Locate a sequence | |
| 300 | +** of lines in these two blocks that are exactly the same. Return | |
| 301 | +** the bounds of the matching sequence. | |
| 302 | +*/ | |
| 303 | +static void longestCommonSequence( | |
| 304 | + DContext *p, | |
| 305 | + int iS1, int iE1, | |
| 306 | + int iS2, int iE2, | |
| 307 | + int *piSX, int *piEX, | |
| 308 | + int *piSY, int *piEY | |
| 309 | +){ | |
| 310 | + int bestScore = -1000000000; | |
| 311 | + int i, j; | |
| 312 | + int iSX, iSY, iEX, iEY; | |
| 313 | + int score, skew, dist, mid; | |
| 314 | + | |
| 315 | + *piSX = iS1; | |
| 316 | + *piEX = iS1; | |
| 317 | + *piSY = iS2; | |
| 318 | + *piEY = iS2; | |
| 319 | + mid = (iE1 + iS1)/2; | |
| 320 | + for(i=iS1; i<iE1; i++){ | |
| 321 | + int limit = 0; | |
| 322 | + j = p->aTo[p->aFrom[i].h % p->nTo].iHash; | |
| 323 | + while( j>0 | |
| 324 | + && (j-1<iS2 || j>=iE2 || !same_dline(&p->aFrom[i], &p->aTo[j-1])) | |
| 325 | + ){ | |
| 326 | + if( limit++ > 10 ){ | |
| 327 | + j = 0; | |
| 328 | + break; | |
| 329 | + } | |
| 330 | + j = p->aTo[j-1].iNext; | |
| 331 | + } | |
| 332 | + if( j==0 ) continue; | |
| 333 | + iSX = i; | |
| 334 | + iSY = j-1; | |
| 335 | + while( iSX>iS1 && iSY>iS2 && same_dline(&p->aFrom[iSX-1],&p->aTo[iSY-1]) ){ | |
| 336 | + iSX--; | |
| 337 | + iSY--; | |
| 338 | + } | |
| 339 | + iEX = i+1; | |
| 340 | + iEY = j; | |
| 341 | + while( iEX<iE1 && iEY<iE2 && same_dline(&p->aFrom[iEX],&p->aTo[iEY]) ){ | |
| 342 | + iEX++; | |
| 343 | + iEY++; | |
| 344 | + } | |
| 345 | + skew = (iSX-iS1) - (iSY-iS2); | |
| 346 | + if( skew<0 ) skew = -skew; | |
| 347 | + dist = (iSX+iEX)/2 - mid; | |
| 348 | + if( dist<0 ) dist = -dist; | |
| 349 | + score = (iEX - iSX) - 0.05*skew - 0.05*dist; | |
| 350 | + if( score>bestScore ){ | |
| 351 | + bestScore = score; | |
| 352 | + *piSX = iSX; | |
| 353 | + *piSY = iSY; | |
| 354 | + *piEX = iEX; | |
| 355 | + *piEY = iEY; | |
| 356 | + } | |
| 357 | + } | |
| 358 | + /* printf("LCS(%d..%d/%d..%d) = %d..%d/%d..%d\n", | |
| 359 | + iS1, iE1, iS2, iE2, *piSX, *piEX, *piSY, *piEY); */ | |
| 360 | +} | |
| 361 | + | |
| 362 | +/* | |
| 363 | +** Do a single step in the difference. Compute a sequence of | |
| 364 | +** copy/delete/insert steps that will convert lines iS1 through iE1-1 of | |
| 365 | +** the input into lines iS2 through iE2-1 of the output and write | |
| 366 | +** that sequence into the difference context. | |
| 367 | +*/ | |
| 368 | +static void diff_step(DContext *p, int iS1, int iE1, int iS2, int iE2){ | |
| 369 | + int iSX, iEX, iSY, iEY; | |
| 370 | + | |
| 371 | + if( iE1<=iS1 ){ | |
| 372 | + if( iE2>iS2 ){ | |
| 373 | + appendTriple(p, 0, 0, iE2-iS2); | |
| 374 | + } | |
| 375 | + return; | |
| 376 | + } | |
| 377 | + if( iE2<=iS2 ){ | |
| 378 | + appendTriple(p, 0, iE1-iS1, 0); | |
| 379 | + return; | |
| 380 | + } | |
| 381 | + | |
| 382 | + /* Find the longest matching segment between the two sequences */ | |
| 383 | + longestCommonSequence(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY); | |
| 384 | + | |
| 385 | + if( iEX>iSX ){ | |
| 386 | + /* Recursively diff either side of the matching segment */ | |
| 387 | + diff_step(p, iS1, iSX, iS2, iSY); | |
| 388 | + if( iEX>iSX ){ | |
| 389 | + appendTriple(p, iEX - iSX, 0, 0); | |
| 390 | + } | |
| 391 | + diff_step(p, iEX, iE1, iEY, iE2); | |
| 392 | + }else{ | |
| 393 | + appendTriple(p, 0, iE1-iS1, iE2-iS2); | |
| 394 | + } | |
| 395 | +} | |
| 396 | + | |
| 397 | +/* | |
| 398 | +** Generate a report of the differences between files pA and pB. | |
| 399 | +** If pOut is not NULL then a unified diff is appended there. It | |
| 400 | +** is assumed that pOut has already been initialized. If pOut is | |
| 401 | +** NULL, then a pointer to an array of integers is returned. | |
| 402 | +** The integers come in triples. For each triple, | |
| 403 | +** the elements are the number of lines copied, the number of | |
| 404 | +** lines deleted, and the number of lines inserted. The vector | |
| 405 | +** is terminated by a triple of all zeros. | |
| 406 | +** | |
| 407 | +** This diff utility does not work on binary files. If a binary | |
| 408 | +** file is encountered, 0 is returned and pOut is written with | |
| 409 | +** text "cannot compute difference between binary files". | |
| 410 | +*/ | |
| 411 | +int *text_diff( | |
| 412 | + Blob *pA_Blob, /* FROM file */ | |
| 413 | + Blob *pB_Blob, /* TO file */ | |
| 414 | + Blob *pOut, /* Write unified diff here if not NULL */ | |
| 415 | + int nContext /* Amount of context to unified diff */ | |
| 416 | +){ | |
| 417 | + DContext c; | |
| 418 | + int mnE, iS, iE1, iE2; | |
| 419 | + | |
| 420 | + memset(&c, 0, sizeof(c)); | |
| 421 | + c.aFrom = break_into_lines(blob_str(pA_Blob), &c.nFrom); | |
| 422 | + c.aTo = break_into_lines(blob_str(pB_Blob), &c.nTo); | |
| 423 | + if( c.aFrom==0 || c.aTo==0 ){ | |
| 424 | + free(c.aFrom); | |
| 425 | + free(c.aTo); | |
| 426 | + if( pOut ){ | |
| 427 | + blob_appendf(pOut, "cannot compute difference between binary files\n"); | |
| 428 | + } | |
| 429 | + return 0; | |
| 430 | + } | |
| 431 | + | |
| 432 | + /* Carve off the common header and footer */ | |
| 433 | + iE1 = c.nFrom; | |
| 434 | + iE2 = c.nTo; | |
| 435 | + while( iE1>0 && iE2>0 && same_dline(&c.aFrom[iE1-1], &c.aTo[iE2-1]) ){ | |
| 436 | + iE1--; | |
| 437 | + iE2--; | |
| 438 | + } | |
| 439 | + mnE = iE1<iE2 ? iE1 : iE2; | |
| 440 | + for(iS=0; iS<mnE && same_dline(&c.aFrom[iS],&c.aTo[iS]); iS++){} | |
| 441 | + | |
| 442 | + /* do the difference */ | |
| 443 | + if( iS>0 ){ | |
| 444 | + appendTriple(&c, iS, 0, 0); | |
| 445 | + } | |
| 446 | + diff_step(&c, iS, iE1, iS, iE2); | |
| 447 | + if( iE1<c.nFrom ){ | |
| 448 | + appendTriple(&c, c.nFrom - iE1, 0, 0); | |
| 449 | + } | |
| 450 | + | |
| 451 | + expandEdit(&c, c.nEdit+3); | |
| 452 | + if( c.aEdit ){ | |
| 453 | + c.aEdit[c.nEdit++] = 0; | |
| 454 | + c.aEdit[c.nEdit++] = 0; | |
| 455 | + c.aEdit[c.nEdit++] = 0; | |
| 456 | + } | |
| 457 | + | |
| 458 | + if( pOut ){ | |
| 459 | + /* Compute a context diff if requested */ | |
| 460 | + contextDiff(&c, pOut, nContext); | |
| 461 | + free(c.aFrom); | |
| 462 | + free(c.aTo); | |
| 463 | + free(c.aEdit); | |
| 464 | + return 0; | |
| 465 | + }else{ | |
| 466 | + /* If a context diff is not requested, then return the | |
| 467 | + ** array of COPY/DELETE/INSERT triples after terminating the | |
| 468 | + ** array with a triple of all zeros. | |
| 469 | + */ | |
| 470 | + free(c.aFrom); | |
| 471 | + free(c.aTo); | |
| 472 | + return c.aEdit; | |
| 473 | + } | |
| 474 | +} | |
| 475 | + | |
| 476 | +#if 0 /********** Disabled and replaced by code above ************/ | |
| 123 | 477 | |
| 124 | 478 | /* |
| 125 | 479 | ** Generate a report of the differences between files pA and pB. |
| 126 | 480 | ** If pOut is not NULL then a unified diff is appended there. It |
| 127 | 481 | ** is assumed that pOut has already been initialized. If pOut is |
| @@ -472,10 +826,11 @@ | ||
| 472 | 826 | free(B); |
| 473 | 827 | |
| 474 | 828 | /* Return the result */ |
| 475 | 829 | return R; |
| 476 | 830 | } |
| 831 | +#endif /***************** End of the Wagner/Myers algorithm ************/ | |
| 477 | 832 | |
| 478 | 833 | /* |
| 479 | 834 | ** COMMAND: test-rawdiff |
| 480 | 835 | */ |
| 481 | 836 | void test_rawdiff_cmd(void){ |
| 482 | 837 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -42,29 +42,53 @@ | |
| 42 | ** line. If any line is longer than 1048575 characters, |
| 43 | ** the file is considered binary. |
| 44 | */ |
| 45 | typedef struct DLine DLine; |
| 46 | struct DLine { |
| 47 | const char *z; /* The text of the line */ |
| 48 | unsigned int h; /* Hash of the line */ |
| 49 | }; |
| 50 | |
| 51 | #define LENGTH_MASK 0x000fffff |
| 52 | |
| 53 | /* |
| 54 | ** Return an array of DLine objects containing a pointer to the |
| 55 | ** start of each line and a hash of that line. The lower |
| 56 | ** bits of the hash store the length of each line. |
| 57 | ** |
| 58 | ** Trailing whitespace is removed from each line. |
| 59 | ** |
| 60 | ** Return 0 if the file is binary or contains a line that is |
| 61 | ** longer than 1048575 bytes. |
| 62 | */ |
| 63 | static DLine *break_into_lines(char *z, int *pnLine){ |
| 64 | int nLine, i, j, k, x; |
| 65 | unsigned int h; |
| 66 | DLine *a; |
| 67 | |
| 68 | /* Count the number of lines. Allocate space to hold |
| 69 | ** the returned array. |
| 70 | */ |
| @@ -73,31 +97,35 @@ | |
| 73 | if( c==0 ){ |
| 74 | return 0; |
| 75 | } |
| 76 | if( c=='\n' && z[i+1]!=0 ){ |
| 77 | nLine++; |
| 78 | if( j>1048575 ){ |
| 79 | return 0; |
| 80 | } |
| 81 | j = 0; |
| 82 | } |
| 83 | } |
| 84 | if( j>1048575 ){ |
| 85 | return 0; |
| 86 | } |
| 87 | a = malloc( nLine*sizeof(a[0]) ); |
| 88 | if( a==0 ) fossil_panic("out of memory"); |
| 89 | |
| 90 | /* Fill in the array */ |
| 91 | for(i=0; i<nLine; i++){ |
| 92 | a[i].z = z; |
| 93 | for(j=0; z[j] && z[j]!='\n'; j++){} |
| 94 | for(k=j; k>0 && isspace(z[k-1]); k--){} |
| 95 | for(h=0, x=0; x<k; x++){ |
| 96 | h = h ^ (h<<2) ^ z[x]; |
| 97 | } |
| 98 | a[i].h = (h<<20) | k;; |
| 99 | z += j+1; |
| 100 | } |
| 101 | |
| 102 | /* Return results */ |
| 103 | *pnLine = nLine; |
| @@ -118,10 +146,336 @@ | |
| 118 | blob_append(pOut, zPrefix, 1); |
| 119 | blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK); |
| 120 | blob_append(pOut, "\n", 1); |
| 121 | } |
| 122 | |
| 123 | |
| 124 | /* |
| 125 | ** Generate a report of the differences between files pA and pB. |
| 126 | ** If pOut is not NULL then a unified diff is appended there. It |
| 127 | ** is assumed that pOut has already been initialized. If pOut is |
| @@ -472,10 +826,11 @@ | |
| 472 | free(B); |
| 473 | |
| 474 | /* Return the result */ |
| 475 | return R; |
| 476 | } |
| 477 | |
| 478 | /* |
| 479 | ** COMMAND: test-rawdiff |
| 480 | */ |
| 481 | void test_rawdiff_cmd(void){ |
| 482 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -42,29 +42,53 @@ | |
| 42 | ** line. If any line is longer than 1048575 characters, |
| 43 | ** the file is considered binary. |
| 44 | */ |
| 45 | typedef struct DLine DLine; |
| 46 | struct DLine { |
| 47 | const char *z; /* The text of the line */ |
| 48 | unsigned int h; /* Hash of the line */ |
| 49 | unsigned int iNext; /* Index+1 of next line with same the same hash */ |
| 50 | |
| 51 | /* an array of DLine elements services two purposes. The fields |
| 52 | ** above are one per line of input text. But each entry is also |
| 53 | ** a bucket in a hash table. */ |
| 54 | unsigned int iHash; /* First entry+1 in the hash array */ |
| 55 | }; |
| 56 | |
| 57 | /* |
| 58 | ** Maximum length of a line in a text file. (8192) |
| 59 | */ |
| 60 | #define LENGTH_MASK_SZ 13 |
| 61 | #define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1) |
| 62 | |
| 63 | /* |
| 64 | ** A context for running a diff. |
| 65 | */ |
| 66 | typedef struct DContext DContext; |
| 67 | struct DContext { |
| 68 | int *aEdit; /* Array of copy/delete/insert triples */ |
| 69 | int nEdit; /* Number of integers (3x num of triples) in aEdit[] */ |
| 70 | int nEditAlloc; /* Space allocated for aEdit[] */ |
| 71 | DLine *aFrom; /* File on left side of the diff */ |
| 72 | int nFrom; /* Number of lines in aFrom[] */ |
| 73 | DLine *aTo; /* File on right side of the diff */ |
| 74 | int nTo; /* Number of lines in aTo[] */ |
| 75 | }; |
| 76 | |
| 77 | /* |
| 78 | ** Return an array of DLine objects containing a pointer to the |
| 79 | ** start of each line and a hash of that line. The lower |
| 80 | ** bits of the hash store the length of each line. |
| 81 | ** |
| 82 | ** Trailing whitespace is removed from each line. |
| 83 | ** |
| 84 | ** Return 0 if the file is binary or contains a line that is |
| 85 | ** too long. |
| 86 | */ |
| 87 | static DLine *break_into_lines(char *z, int *pnLine){ |
| 88 | int nLine, i, j, k, x; |
| 89 | unsigned int h, h2; |
| 90 | DLine *a; |
| 91 | |
| 92 | /* Count the number of lines. Allocate space to hold |
| 93 | ** the returned array. |
| 94 | */ |
| @@ -73,31 +97,35 @@ | |
| 97 | if( c==0 ){ |
| 98 | return 0; |
| 99 | } |
| 100 | if( c=='\n' && z[i+1]!=0 ){ |
| 101 | nLine++; |
| 102 | if( j>LENGTH_MASK ){ |
| 103 | return 0; |
| 104 | } |
| 105 | j = 0; |
| 106 | } |
| 107 | } |
| 108 | if( j>LENGTH_MASK ){ |
| 109 | return 0; |
| 110 | } |
| 111 | a = malloc( nLine*sizeof(a[0]) ); |
| 112 | if( a==0 ) fossil_panic("out of memory"); |
| 113 | memset(a, 0, nLine*sizeof(a[0]) ); |
| 114 | |
| 115 | /* Fill in the array */ |
| 116 | for(i=0; i<nLine; i++){ |
| 117 | a[i].z = z; |
| 118 | for(j=0; z[j] && z[j]!='\n'; j++){} |
| 119 | for(k=j; k>0 && isspace(z[k-1]); k--){} |
| 120 | for(h=0, x=0; x<k; x++){ |
| 121 | h = h ^ (h<<2) ^ z[x]; |
| 122 | } |
| 123 | a[i].h = h = (h<<LENGTH_MASK_SZ) | k;; |
| 124 | h2 = h % nLine; |
| 125 | a[i].iNext = a[h2].iHash; |
| 126 | a[h2].iHash = i+1; |
| 127 | z += j+1; |
| 128 | } |
| 129 | |
| 130 | /* Return results */ |
| 131 | *pnLine = nLine; |
| @@ -118,10 +146,336 @@ | |
| 146 | blob_append(pOut, zPrefix, 1); |
| 147 | blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK); |
| 148 | blob_append(pOut, "\n", 1); |
| 149 | } |
| 150 | |
| 151 | /* |
| 152 | ** Expand the size of aEdit[] array to hold nEdit elements. |
| 153 | */ |
| 154 | static void expandEdit(DContext *p, int nEdit){ |
| 155 | int *a; |
| 156 | a = realloc(p->aEdit, nEdit*sizeof(int)); |
| 157 | if( a==0 ){ |
| 158 | free( p->aEdit ); |
| 159 | p->nEdit = 0; |
| 160 | nEdit = 0; |
| 161 | } |
| 162 | p->aEdit = a; |
| 163 | p->nEditAlloc = nEdit; |
| 164 | } |
| 165 | |
| 166 | /* |
| 167 | ** Append a new COPY/DELETE/INSERT triple. |
| 168 | */ |
| 169 | static void appendTriple(DContext *p, int nCopy, int nDel, int nIns){ |
| 170 | /* printf("APPEND %d/%d/%d\n", nCopy, nDel, nIns); */ |
| 171 | if( p->nEdit>=3 ){ |
| 172 | if( p->aEdit[p->nEdit-1]==0 ){ |
| 173 | if( p->aEdit[p->nEdit-2]==0 ){ |
| 174 | p->aEdit[p->nEdit-3] += nCopy; |
| 175 | p->aEdit[p->nEdit-2] += nDel; |
| 176 | p->aEdit[p->nEdit-1] += nIns; |
| 177 | return; |
| 178 | } |
| 179 | if( nCopy==0 ){ |
| 180 | p->aEdit[p->nEdit-2] += nDel; |
| 181 | p->aEdit[p->nEdit-1] += nIns; |
| 182 | return; |
| 183 | } |
| 184 | } |
| 185 | if( nCopy==0 && nDel==0 ){ |
| 186 | p->aEdit[p->nEdit-1] += nIns; |
| 187 | return; |
| 188 | } |
| 189 | } |
| 190 | if( p->nEdit+3>p->nEditAlloc ){ |
| 191 | expandEdit(p, p->nEdit*2 + 15); |
| 192 | if( p->aEdit==0 ) return; |
| 193 | } |
| 194 | p->aEdit[p->nEdit++] = nCopy; |
| 195 | p->aEdit[p->nEdit++] = nDel; |
| 196 | p->aEdit[p->nEdit++] = nIns; |
| 197 | } |
| 198 | |
| 199 | |
| 200 | /* |
| 201 | ** Given a diff context in which the aEdit[] array has been filled |
| 202 | ** in, compute a context diff into pOut. |
| 203 | */ |
| 204 | static void contextDiff(DContext *p, Blob *pOut, int nContext){ |
| 205 | DLine *A; /* Left side of the diff */ |
| 206 | DLine *B; /* Right side of the diff */ |
| 207 | int a = 0; /* Index of next line in A[] */ |
| 208 | int b = 0; /* Index of next line in B[] */ |
| 209 | int *R; /* Array of COPY/DELETE/INSERT triples */ |
| 210 | int r; /* Index into R[] */ |
| 211 | int nr; /* Number of COPY/DELETE/INSERT triples to process */ |
| 212 | int mxr; /* Maximum value for r */ |
| 213 | int na, nb; /* Number of lines shown from A and B */ |
| 214 | int i, j; /* Loop counters */ |
| 215 | int m; /* Number of lines to output */ |
| 216 | int skip; /* Number of lines to skip */ |
| 217 | |
| 218 | A = p->aFrom; |
| 219 | B = p->aTo; |
| 220 | R = p->aEdit; |
| 221 | mxr = p->nEdit; |
| 222 | if( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } |
| 223 | for(r=0; r<mxr; r += 3*nr){ |
| 224 | /* Figure out how many triples to show in a single block */ |
| 225 | for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){} |
| 226 | DEBUG( printf("r=%d nr=%d\n", r, nr); ) |
| 227 | |
| 228 | /* For the current block comprising nr triples, figure out |
| 229 | ** how many lines of A and B are to be displayed |
| 230 | */ |
| 231 | if( R[r]>nContext ){ |
| 232 | na = nb = nContext; |
| 233 | skip = R[r] - nContext; |
| 234 | }else{ |
| 235 | na = nb = R[r]; |
| 236 | skip = 0; |
| 237 | } |
| 238 | for(i=0; i<nr; i++){ |
| 239 | na += R[r+i*3+1]; |
| 240 | nb += R[r+i*3+2]; |
| 241 | } |
| 242 | if( R[r+nr*3]>nContext ){ |
| 243 | na += nContext; |
| 244 | nb += nContext; |
| 245 | }else{ |
| 246 | na += R[r+nr*3]; |
| 247 | nb += R[r+nr*3]; |
| 248 | } |
| 249 | for(i=1; i<nr; i++){ |
| 250 | na += R[r+i*3]; |
| 251 | nb += R[r+i*3]; |
| 252 | } |
| 253 | blob_appendf(pOut,"@@ -%d,%d +%d,%d @@\n", a+skip+1, na, b+skip+1, nb); |
| 254 | |
| 255 | /* Show the initial common area */ |
| 256 | a += skip; |
| 257 | b += skip; |
| 258 | m = R[r] - skip; |
| 259 | for(j=0; j<m; j++){ |
| 260 | appendDiffLine(pOut, " ", &A[a+j]); |
| 261 | } |
| 262 | a += m; |
| 263 | b += m; |
| 264 | |
| 265 | /* Show the differences */ |
| 266 | for(i=0; i<nr; i++){ |
| 267 | m = R[r+i*3+1]; |
| 268 | for(j=0; j<m; j++){ |
| 269 | appendDiffLine(pOut, "-", &A[a+j]); |
| 270 | } |
| 271 | a += m; |
| 272 | m = R[r+i*3+2]; |
| 273 | for(j=0; j<m; j++){ |
| 274 | appendDiffLine(pOut, "+", &B[b+j]); |
| 275 | } |
| 276 | b += m; |
| 277 | if( i<nr-1 ){ |
| 278 | m = R[r+i*3+3]; |
| 279 | for(j=0; j<m; j++){ |
| 280 | appendDiffLine(pOut, " ", &B[b+j]); |
| 281 | } |
| 282 | b += m; |
| 283 | a += m; |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /* Show the final common area */ |
| 288 | assert( nr==i ); |
| 289 | m = R[r+nr*3]; |
| 290 | if( m>nContext ) m = nContext; |
| 291 | for(j=0; j<m; j++){ |
| 292 | appendDiffLine(pOut, " ", &B[b+j]); |
| 293 | } |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | /* |
| 298 | ** Compare two blocks of text on lines iS1 through iE1-1 of the aFrom[] |
| 299 | ** file and lines iS2 through iE2-1 of the aTo[] file. Locate a sequence |
| 300 | ** of lines in these two blocks that are exactly the same. Return |
| 301 | ** the bounds of the matching sequence. |
| 302 | */ |
| 303 | static void longestCommonSequence( |
| 304 | DContext *p, |
| 305 | int iS1, int iE1, |
| 306 | int iS2, int iE2, |
| 307 | int *piSX, int *piEX, |
| 308 | int *piSY, int *piEY |
| 309 | ){ |
| 310 | int bestScore = -1000000000; |
| 311 | int i, j; |
| 312 | int iSX, iSY, iEX, iEY; |
| 313 | int score, skew, dist, mid; |
| 314 | |
| 315 | *piSX = iS1; |
| 316 | *piEX = iS1; |
| 317 | *piSY = iS2; |
| 318 | *piEY = iS2; |
| 319 | mid = (iE1 + iS1)/2; |
| 320 | for(i=iS1; i<iE1; i++){ |
| 321 | int limit = 0; |
| 322 | j = p->aTo[p->aFrom[i].h % p->nTo].iHash; |
| 323 | while( j>0 |
| 324 | && (j-1<iS2 || j>=iE2 || !same_dline(&p->aFrom[i], &p->aTo[j-1])) |
| 325 | ){ |
| 326 | if( limit++ > 10 ){ |
| 327 | j = 0; |
| 328 | break; |
| 329 | } |
| 330 | j = p->aTo[j-1].iNext; |
| 331 | } |
| 332 | if( j==0 ) continue; |
| 333 | iSX = i; |
| 334 | iSY = j-1; |
| 335 | while( iSX>iS1 && iSY>iS2 && same_dline(&p->aFrom[iSX-1],&p->aTo[iSY-1]) ){ |
| 336 | iSX--; |
| 337 | iSY--; |
| 338 | } |
| 339 | iEX = i+1; |
| 340 | iEY = j; |
| 341 | while( iEX<iE1 && iEY<iE2 && same_dline(&p->aFrom[iEX],&p->aTo[iEY]) ){ |
| 342 | iEX++; |
| 343 | iEY++; |
| 344 | } |
| 345 | skew = (iSX-iS1) - (iSY-iS2); |
| 346 | if( skew<0 ) skew = -skew; |
| 347 | dist = (iSX+iEX)/2 - mid; |
| 348 | if( dist<0 ) dist = -dist; |
| 349 | score = (iEX - iSX) - 0.05*skew - 0.05*dist; |
| 350 | if( score>bestScore ){ |
| 351 | bestScore = score; |
| 352 | *piSX = iSX; |
| 353 | *piSY = iSY; |
| 354 | *piEX = iEX; |
| 355 | *piEY = iEY; |
| 356 | } |
| 357 | } |
| 358 | /* printf("LCS(%d..%d/%d..%d) = %d..%d/%d..%d\n", |
| 359 | iS1, iE1, iS2, iE2, *piSX, *piEX, *piSY, *piEY); */ |
| 360 | } |
| 361 | |
| 362 | /* |
| 363 | ** Do a single step in the difference. Compute a sequence of |
| 364 | ** copy/delete/insert steps that will convert lines iS1 through iE1-1 of |
| 365 | ** the input into lines iS2 through iE2-1 of the output and write |
| 366 | ** that sequence into the difference context. |
| 367 | */ |
| 368 | static void diff_step(DContext *p, int iS1, int iE1, int iS2, int iE2){ |
| 369 | int iSX, iEX, iSY, iEY; |
| 370 | |
| 371 | if( iE1<=iS1 ){ |
| 372 | if( iE2>iS2 ){ |
| 373 | appendTriple(p, 0, 0, iE2-iS2); |
| 374 | } |
| 375 | return; |
| 376 | } |
| 377 | if( iE2<=iS2 ){ |
| 378 | appendTriple(p, 0, iE1-iS1, 0); |
| 379 | return; |
| 380 | } |
| 381 | |
| 382 | /* Find the longest matching segment between the two sequences */ |
| 383 | longestCommonSequence(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY); |
| 384 | |
| 385 | if( iEX>iSX ){ |
| 386 | /* Recursively diff either side of the matching segment */ |
| 387 | diff_step(p, iS1, iSX, iS2, iSY); |
| 388 | if( iEX>iSX ){ |
| 389 | appendTriple(p, iEX - iSX, 0, 0); |
| 390 | } |
| 391 | diff_step(p, iEX, iE1, iEY, iE2); |
| 392 | }else{ |
| 393 | appendTriple(p, 0, iE1-iS1, iE2-iS2); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | /* |
| 398 | ** Generate a report of the differences between files pA and pB. |
| 399 | ** If pOut is not NULL then a unified diff is appended there. It |
| 400 | ** is assumed that pOut has already been initialized. If pOut is |
| 401 | ** NULL, then a pointer to an array of integers is returned. |
| 402 | ** The integers come in triples. For each triple, |
| 403 | ** the elements are the number of lines copied, the number of |
| 404 | ** lines deleted, and the number of lines inserted. The vector |
| 405 | ** is terminated by a triple of all zeros. |
| 406 | ** |
| 407 | ** This diff utility does not work on binary files. If a binary |
| 408 | ** file is encountered, 0 is returned and pOut is written with |
| 409 | ** text "cannot compute difference between binary files". |
| 410 | */ |
| 411 | int *text_diff( |
| 412 | Blob *pA_Blob, /* FROM file */ |
| 413 | Blob *pB_Blob, /* TO file */ |
| 414 | Blob *pOut, /* Write unified diff here if not NULL */ |
| 415 | int nContext /* Amount of context to unified diff */ |
| 416 | ){ |
| 417 | DContext c; |
| 418 | int mnE, iS, iE1, iE2; |
| 419 | |
| 420 | memset(&c, 0, sizeof(c)); |
| 421 | c.aFrom = break_into_lines(blob_str(pA_Blob), &c.nFrom); |
| 422 | c.aTo = break_into_lines(blob_str(pB_Blob), &c.nTo); |
| 423 | if( c.aFrom==0 || c.aTo==0 ){ |
| 424 | free(c.aFrom); |
| 425 | free(c.aTo); |
| 426 | if( pOut ){ |
| 427 | blob_appendf(pOut, "cannot compute difference between binary files\n"); |
| 428 | } |
| 429 | return 0; |
| 430 | } |
| 431 | |
| 432 | /* Carve off the common header and footer */ |
| 433 | iE1 = c.nFrom; |
| 434 | iE2 = c.nTo; |
| 435 | while( iE1>0 && iE2>0 && same_dline(&c.aFrom[iE1-1], &c.aTo[iE2-1]) ){ |
| 436 | iE1--; |
| 437 | iE2--; |
| 438 | } |
| 439 | mnE = iE1<iE2 ? iE1 : iE2; |
| 440 | for(iS=0; iS<mnE && same_dline(&c.aFrom[iS],&c.aTo[iS]); iS++){} |
| 441 | |
| 442 | /* do the difference */ |
| 443 | if( iS>0 ){ |
| 444 | appendTriple(&c, iS, 0, 0); |
| 445 | } |
| 446 | diff_step(&c, iS, iE1, iS, iE2); |
| 447 | if( iE1<c.nFrom ){ |
| 448 | appendTriple(&c, c.nFrom - iE1, 0, 0); |
| 449 | } |
| 450 | |
| 451 | expandEdit(&c, c.nEdit+3); |
| 452 | if( c.aEdit ){ |
| 453 | c.aEdit[c.nEdit++] = 0; |
| 454 | c.aEdit[c.nEdit++] = 0; |
| 455 | c.aEdit[c.nEdit++] = 0; |
| 456 | } |
| 457 | |
| 458 | if( pOut ){ |
| 459 | /* Compute a context diff if requested */ |
| 460 | contextDiff(&c, pOut, nContext); |
| 461 | free(c.aFrom); |
| 462 | free(c.aTo); |
| 463 | free(c.aEdit); |
| 464 | return 0; |
| 465 | }else{ |
| 466 | /* If a context diff is not requested, then return the |
| 467 | ** array of COPY/DELETE/INSERT triples after terminating the |
| 468 | ** array with a triple of all zeros. |
| 469 | */ |
| 470 | free(c.aFrom); |
| 471 | free(c.aTo); |
| 472 | return c.aEdit; |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | #if 0 /********** Disabled and replaced by code above ************/ |
| 477 | |
| 478 | /* |
| 479 | ** Generate a report of the differences between files pA and pB. |
| 480 | ** If pOut is not NULL then a unified diff is appended there. It |
| 481 | ** is assumed that pOut has already been initialized. If pOut is |
| @@ -472,10 +826,11 @@ | |
| 826 | free(B); |
| 827 | |
| 828 | /* Return the result */ |
| 829 | return R; |
| 830 | } |
| 831 | #endif /***************** End of the Wagner/Myers algorithm ************/ |
| 832 | |
| 833 | /* |
| 834 | ** COMMAND: test-rawdiff |
| 835 | */ |
| 836 | void test_rawdiff_cmd(void){ |
| 837 |
+171
-35
| --- src/tagview.c | ||
| +++ src/tagview.c | ||
| @@ -1,7 +1,8 @@ | ||
| 1 | 1 | /* |
| 2 | 2 | ** Copyright (c) 2007 D. Richard Hipp |
| 3 | +** Copyright (c) 2008 Stephan Beal | |
| 3 | 4 | ** |
| 4 | 5 | ** This program is free software; you can redistribute it and/or |
| 5 | 6 | ** modify it under the terms of the GNU General Public |
| 6 | 7 | ** License as published by the Free Software Foundation; either |
| 7 | 8 | ** version 2 of the License, or (at your option) any later version. |
| @@ -40,60 +41,195 @@ | ||
| 40 | 41 | const char *zLink, |
| 41 | 42 | const char *zDesc |
| 42 | 43 | ){ |
| 43 | 44 | @ <tr><td valign="top" align="right"> |
| 44 | 45 | if( zLink && zLink[0] ){ |
| 45 | - @ <a href="%s(zLink)">%h(zTitle)</a> | |
| 46 | + @ <a href="%s(g.zBaseURL)/%s(zLink)">%h(zTitle)</a> | |
| 46 | 47 | }else{ |
| 47 | 48 | @ %h(zTitle) |
| 48 | 49 | } |
| 49 | 50 | @ </td><td valign="top">%h(zDesc)</td></tr> |
| 50 | 51 | } |
| 51 | 52 | |
| 52 | -/* | |
| 53 | -** WEBPAGE: /tagview | |
| 54 | -*/ | |
| 55 | -void tagview_page(void){ | |
| 53 | +static void tagview_page_list_tags( char const * like ) | |
| 54 | +{ | |
| 56 | 55 | Stmt st; |
| 57 | - | |
| 58 | - login_check_credentials(); | |
| 59 | - if( !g.okSetup ){ | |
| 60 | - login_needed(); | |
| 56 | + char * likeclause = 0; | |
| 57 | + const int limit = 10; | |
| 58 | + char * limitstr = 0; | |
| 59 | + if( like && strlen(like) ) | |
| 60 | + { | |
| 61 | + likeclause = mprintf( "AND t.tagname LIKE '%%%%%q%%%%'", like ); | |
| 62 | + @ <h2>Tags matching [%s(likeclause)]:</h2> | |
| 61 | 63 | } |
| 62 | - style_header("Tags List"); | |
| 64 | + else | |
| 65 | + { | |
| 66 | + limitstr = mprintf( "LIMIT %d", limit ); | |
| 67 | + @ <h2>%d(limit) most recent tags:</h2> | |
| 68 | + } | |
| 63 | 69 | @ <table cellpadding='4px' border='1'><tbody> |
| 64 | - @ <tr><th>Tag name</th><th>Timestamp</th><th>Version</th></tr> | |
| 65 | - db_prepare( &st, | |
| 66 | - "select t.tagname, DATETIME(tx.mtime), b.uuid " | |
| 67 | - "FROM tag t, tagxref tx, blob b " | |
| 68 | - "WHERE t.tagid=tx.tagid and tx.rid=b.rid " | |
| 69 | - "AND tx.tagtype != 0 " | |
| 70 | - "ORDER BY tx.mtime DESC" | |
| 71 | - ); | |
| 72 | - while( SQLITE_ROW == db_step(&st) ) | |
| 73 | - { | |
| 74 | - char const * tagname = db_column_text( &st, 0 ); | |
| 75 | - char const * tagtime = db_column_text( &st, 1 ); | |
| 76 | - char const * uuid = db_column_text( &st, 2 ); | |
| 77 | - const int offset = 10; | |
| 78 | - char shortname[offset+1]; | |
| 79 | - shortname[offset] = '\0'; | |
| 80 | - memcpy( shortname, uuid, offset ); | |
| 81 | - @ <tr> | |
| 82 | - @ <td><tt>%s(tagname)</tt></td> | |
| 83 | - @ <td align='center'><tt>%s(tagtime)</tt></td> | |
| 84 | - @ <td><tt> | |
| 85 | - @ <a href='/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a></tt> | |
| 86 | - @ </td></tr> | |
| 70 | + @ <tr> | |
| 71 | + @ <th>Tag ID</th> | |
| 72 | + @ <th>Tag name</th> | |
| 73 | + @ <th>Timestamp</th> | |
| 74 | + @ <th>Version</th> | |
| 75 | + @ </tr> | |
| 76 | + char * sql = mprintf( | |
| 77 | + "SELECT t.tagid, t.tagname, DATETIME(tx.mtime), b.uuid " | |
| 78 | + "FROM tag t, tagxref tx, blob b " | |
| 79 | + "WHERE (t.tagid=tx.tagid) and (tx.srcid=b.rid) " | |
| 80 | + "AND (tx.tagtype != 0) %s " | |
| 81 | + "ORDER BY tx.mtime DESC %s", | |
| 82 | + likeclause ? likeclause : " ", | |
| 83 | + limitstr ? limitstr : " " | |
| 84 | + ); | |
| 85 | + db_prepare( &st, sql ); | |
| 86 | + if( likeclause ) free( likeclause ); | |
| 87 | + free( sql ); | |
| 88 | + while( SQLITE_ROW == db_step(&st) ){ | |
| 89 | + int tagid = db_column_int( &st, 0 ); | |
| 90 | + char const * tagname = db_column_text( &st, 1 ); | |
| 91 | + char const * tagtime = db_column_text( &st, 2 ); | |
| 92 | + char const * uuid = db_column_text( &st, 3 ); | |
| 93 | + const int offset = 10; | |
| 94 | + char shortname[offset+1]; | |
| 95 | + shortname[offset] = '\0'; | |
| 96 | + memcpy( shortname, uuid, offset ); | |
| 97 | + @ <tr> | |
| 98 | + @ <td><tt> | |
| 99 | + @ <a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a> | |
| 100 | + @ </tt></td> | |
| 101 | + @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> | |
| 102 | + @ <td align='center'><tt>%s(tagtime)</tt></td> | |
| 103 | + @ <td><tt> | |
| 104 | + @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> | |
| 105 | + @ </tt></td></tr> | |
| 87 | 106 | } |
| 88 | 107 | db_finalize( &st ); |
| 89 | 108 | @ </tbody></table> |
| 90 | 109 | @ <hr/>TODOs include: |
| 91 | 110 | @ <ul> |
| 92 | 111 | @ <li>Page through long tags lists.</li> |
| 93 | - @ <li>Format the timestamp field.</li> | |
| 112 | + @ <li>Refactor the internal report routines to be reusable.</li> | |
| 94 | 113 | @ <li>Allow different sorting.</li> |
| 114 | + @ <li>Selectively filter out wiki/ticket/baseline</li> | |
| 95 | 115 | @ <li>?</li> |
| 96 | 116 | @ </ul> |
| 97 | - style_footer(); | |
| 117 | + | |
| 118 | +} | |
| 119 | + | |
| 120 | +static void tagview_page_search_miniform(void){ | |
| 121 | + char const * like = P("like"); | |
| 122 | + @ <div style='font-size:smaller'> | |
| 123 | + @ <form action='/tagview' method='post'> | |
| 124 | + @ Search for tags: | |
| 125 | + @ <input type='text' name='like' value='%s((like?like:""))' size='10'/> | |
| 126 | + @ <input type='submit'/> | |
| 127 | + @ </form> | |
| 128 | + @ </div> | |
| 129 | +} | |
| 130 | + | |
| 131 | + | |
| 132 | +static void tagview_page_default(void){ | |
| 133 | + tagview_page_list_tags( 0 ); | |
| 134 | +} | |
| 135 | + | |
| 136 | +static void tagview_page_tag_by_id( int tagid ) | |
| 137 | +{ | |
| 138 | + Stmt st; | |
| 139 | + char * sql = mprintf( | |
| 140 | + "SELECT DISTINCT (t.tagname), DATETIME(tx.mtime), b.uuid " | |
| 141 | + "FROM tag t, tagxref tx, blob b " | |
| 142 | + "WHERE (t.tagid=%d) AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " | |
| 143 | + "ORDER BY tx.mtime DESC", | |
| 144 | + tagid); | |
| 145 | + db_prepare( &st, sql ); | |
| 146 | + free( sql ); | |
| 147 | + @ <h2>Tag ID %d(tagid):</h2> | |
| 148 | + @ <table cellpadding='4px' border='1'><tbody> | |
| 149 | + @ <tr><th>Tag name</th><th>Timestamp</th><th>Version</th></tr> | |
| 150 | + while( SQLITE_ROW == db_step(&st) ) | |
| 151 | + { | |
| 152 | + char const * tagname = db_column_text( &st, 0 ); | |
| 153 | + char const * tagtime = db_column_text( &st, 1 ); | |
| 154 | + char const * uuid = db_column_text( &st, 2 ); | |
| 155 | + const int offset = 10; | |
| 156 | + char shortname[offset+1]; | |
| 157 | + shortname[offset] = '\0'; | |
| 158 | + memcpy( shortname, uuid, offset ); | |
| 159 | + @ <tr> | |
| 160 | + @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> | |
| 161 | + @ <td align='center'><tt>%s(tagtime)</tt></td> | |
| 162 | + @ <td><tt> | |
| 163 | + @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> | |
| 164 | + @ </tt></td></tr> | |
| 165 | + } | |
| 166 | + @ </tbody></table> | |
| 167 | + db_finalize( &st ); | |
| 168 | +} | |
| 169 | + | |
| 170 | +static void tagview_page_tag_by_name( char const * tagname ) | |
| 171 | +{ | |
| 172 | + Stmt st; | |
| 173 | + char * sql = mprintf( | |
| 174 | + "SELECT DISTINCT t.tagid, DATETIME(tx.mtime), b.uuid " | |
| 175 | + "FROM tag t, tagxref tx, blob b " | |
| 176 | + "WHERE (t.tagname='%q') AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " | |
| 177 | + "ORDER BY tx.mtime DESC", | |
| 178 | + tagname); | |
| 179 | + db_prepare( &st, sql ); | |
| 180 | + free( sql ); | |
| 181 | + @ <h2>Tag '%s(tagname)':</h2> | |
| 182 | + @ <table cellpadding='4px' border='1'><tbody> | |
| 183 | + @ <tr><th>Tag ID</th><th>Timestamp</th><th>Version</th></tr> | |
| 184 | + while( SQLITE_ROW == db_step(&st) ) | |
| 185 | + { | |
| 186 | + int tagid = db_column_int( &st, 0 ); | |
| 187 | + char const * tagtime = db_column_text( &st, 1 ); | |
| 188 | + char const * uuid = db_column_text( &st, 2 ); | |
| 189 | + const int offset = 10; | |
| 190 | + char shortname[offset+1]; | |
| 191 | + shortname[offset] = '\0'; | |
| 192 | + memcpy( shortname, uuid, offset ); | |
| 193 | + @ <tr> | |
| 194 | + @ <td><tt><a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a></tt></td> | |
| 195 | + @ <td align='center'><tt>%s(tagtime)</tt></td> | |
| 196 | + @ <td><tt> | |
| 197 | + @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> | |
| 198 | + @ </tt></td></tr> | |
| 199 | + } | |
| 200 | + @ </tbody></table> | |
| 201 | + db_finalize( &st ); | |
| 98 | 202 | } |
| 99 | 203 | |
| 204 | + | |
| 205 | +/* | |
| 206 | +** WEBPAGE: /tagview | |
| 207 | +*/ | |
| 208 | +void tagview_page(void){ | |
| 209 | + | |
| 210 | + login_check_credentials(); | |
| 211 | + if( !g.okSetup ){ | |
| 212 | + login_needed(); | |
| 213 | + } | |
| 214 | + style_header("Tags"); | |
| 215 | + tagview_page_search_miniform(); | |
| 216 | + @ <hr/> | |
| 217 | + char const * check = 0; | |
| 218 | + if( 0 != (check = P("tagid")) ) | |
| 219 | + { | |
| 220 | + tagview_page_tag_by_id( atoi(check) ); | |
| 221 | + } | |
| 222 | + else if( 0 != (check = P("like")) ) | |
| 223 | + { | |
| 224 | + tagview_page_list_tags( check ); | |
| 225 | + } | |
| 226 | + else if( 0 != (check = P("name")) ) | |
| 227 | + { | |
| 228 | + tagview_page_tag_by_name( check ); | |
| 229 | + } | |
| 230 | + else | |
| 231 | + { | |
| 232 | + tagview_page_default(); | |
| 233 | + } | |
| 234 | + style_footer(); | |
| 235 | +} | |
| 100 | 236 |
| --- src/tagview.c | |
| +++ src/tagview.c | |
| @@ -1,7 +1,8 @@ | |
| 1 | /* |
| 2 | ** Copyright (c) 2007 D. Richard Hipp |
| 3 | ** |
| 4 | ** This program is free software; you can redistribute it and/or |
| 5 | ** modify it under the terms of the GNU General Public |
| 6 | ** License as published by the Free Software Foundation; either |
| 7 | ** version 2 of the License, or (at your option) any later version. |
| @@ -40,60 +41,195 @@ | |
| 40 | const char *zLink, |
| 41 | const char *zDesc |
| 42 | ){ |
| 43 | @ <tr><td valign="top" align="right"> |
| 44 | if( zLink && zLink[0] ){ |
| 45 | @ <a href="%s(zLink)">%h(zTitle)</a> |
| 46 | }else{ |
| 47 | @ %h(zTitle) |
| 48 | } |
| 49 | @ </td><td valign="top">%h(zDesc)</td></tr> |
| 50 | } |
| 51 | |
| 52 | /* |
| 53 | ** WEBPAGE: /tagview |
| 54 | */ |
| 55 | void tagview_page(void){ |
| 56 | Stmt st; |
| 57 | |
| 58 | login_check_credentials(); |
| 59 | if( !g.okSetup ){ |
| 60 | login_needed(); |
| 61 | } |
| 62 | style_header("Tags List"); |
| 63 | @ <table cellpadding='4px' border='1'><tbody> |
| 64 | @ <tr><th>Tag name</th><th>Timestamp</th><th>Version</th></tr> |
| 65 | db_prepare( &st, |
| 66 | "select t.tagname, DATETIME(tx.mtime), b.uuid " |
| 67 | "FROM tag t, tagxref tx, blob b " |
| 68 | "WHERE t.tagid=tx.tagid and tx.rid=b.rid " |
| 69 | "AND tx.tagtype != 0 " |
| 70 | "ORDER BY tx.mtime DESC" |
| 71 | ); |
| 72 | while( SQLITE_ROW == db_step(&st) ) |
| 73 | { |
| 74 | char const * tagname = db_column_text( &st, 0 ); |
| 75 | char const * tagtime = db_column_text( &st, 1 ); |
| 76 | char const * uuid = db_column_text( &st, 2 ); |
| 77 | const int offset = 10; |
| 78 | char shortname[offset+1]; |
| 79 | shortname[offset] = '\0'; |
| 80 | memcpy( shortname, uuid, offset ); |
| 81 | @ <tr> |
| 82 | @ <td><tt>%s(tagname)</tt></td> |
| 83 | @ <td align='center'><tt>%s(tagtime)</tt></td> |
| 84 | @ <td><tt> |
| 85 | @ <a href='/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a></tt> |
| 86 | @ </td></tr> |
| 87 | } |
| 88 | db_finalize( &st ); |
| 89 | @ </tbody></table> |
| 90 | @ <hr/>TODOs include: |
| 91 | @ <ul> |
| 92 | @ <li>Page through long tags lists.</li> |
| 93 | @ <li>Format the timestamp field.</li> |
| 94 | @ <li>Allow different sorting.</li> |
| 95 | @ <li>?</li> |
| 96 | @ </ul> |
| 97 | style_footer(); |
| 98 | } |
| 99 | |
| 100 |
| --- src/tagview.c | |
| +++ src/tagview.c | |
| @@ -1,7 +1,8 @@ | |
| 1 | /* |
| 2 | ** Copyright (c) 2007 D. Richard Hipp |
| 3 | ** Copyright (c) 2008 Stephan Beal |
| 4 | ** |
| 5 | ** This program is free software; you can redistribute it and/or |
| 6 | ** modify it under the terms of the GNU General Public |
| 7 | ** License as published by the Free Software Foundation; either |
| 8 | ** version 2 of the License, or (at your option) any later version. |
| @@ -40,60 +41,195 @@ | |
| 41 | const char *zLink, |
| 42 | const char *zDesc |
| 43 | ){ |
| 44 | @ <tr><td valign="top" align="right"> |
| 45 | if( zLink && zLink[0] ){ |
| 46 | @ <a href="%s(g.zBaseURL)/%s(zLink)">%h(zTitle)</a> |
| 47 | }else{ |
| 48 | @ %h(zTitle) |
| 49 | } |
| 50 | @ </td><td valign="top">%h(zDesc)</td></tr> |
| 51 | } |
| 52 | |
| 53 | static void tagview_page_list_tags( char const * like ) |
| 54 | { |
| 55 | Stmt st; |
| 56 | char * likeclause = 0; |
| 57 | const int limit = 10; |
| 58 | char * limitstr = 0; |
| 59 | if( like && strlen(like) ) |
| 60 | { |
| 61 | likeclause = mprintf( "AND t.tagname LIKE '%%%%%q%%%%'", like ); |
| 62 | @ <h2>Tags matching [%s(likeclause)]:</h2> |
| 63 | } |
| 64 | else |
| 65 | { |
| 66 | limitstr = mprintf( "LIMIT %d", limit ); |
| 67 | @ <h2>%d(limit) most recent tags:</h2> |
| 68 | } |
| 69 | @ <table cellpadding='4px' border='1'><tbody> |
| 70 | @ <tr> |
| 71 | @ <th>Tag ID</th> |
| 72 | @ <th>Tag name</th> |
| 73 | @ <th>Timestamp</th> |
| 74 | @ <th>Version</th> |
| 75 | @ </tr> |
| 76 | char * sql = mprintf( |
| 77 | "SELECT t.tagid, t.tagname, DATETIME(tx.mtime), b.uuid " |
| 78 | "FROM tag t, tagxref tx, blob b " |
| 79 | "WHERE (t.tagid=tx.tagid) and (tx.srcid=b.rid) " |
| 80 | "AND (tx.tagtype != 0) %s " |
| 81 | "ORDER BY tx.mtime DESC %s", |
| 82 | likeclause ? likeclause : " ", |
| 83 | limitstr ? limitstr : " " |
| 84 | ); |
| 85 | db_prepare( &st, sql ); |
| 86 | if( likeclause ) free( likeclause ); |
| 87 | free( sql ); |
| 88 | while( SQLITE_ROW == db_step(&st) ){ |
| 89 | int tagid = db_column_int( &st, 0 ); |
| 90 | char const * tagname = db_column_text( &st, 1 ); |
| 91 | char const * tagtime = db_column_text( &st, 2 ); |
| 92 | char const * uuid = db_column_text( &st, 3 ); |
| 93 | const int offset = 10; |
| 94 | char shortname[offset+1]; |
| 95 | shortname[offset] = '\0'; |
| 96 | memcpy( shortname, uuid, offset ); |
| 97 | @ <tr> |
| 98 | @ <td><tt> |
| 99 | @ <a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a> |
| 100 | @ </tt></td> |
| 101 | @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> |
| 102 | @ <td align='center'><tt>%s(tagtime)</tt></td> |
| 103 | @ <td><tt> |
| 104 | @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> |
| 105 | @ </tt></td></tr> |
| 106 | } |
| 107 | db_finalize( &st ); |
| 108 | @ </tbody></table> |
| 109 | @ <hr/>TODOs include: |
| 110 | @ <ul> |
| 111 | @ <li>Page through long tags lists.</li> |
| 112 | @ <li>Refactor the internal report routines to be reusable.</li> |
| 113 | @ <li>Allow different sorting.</li> |
| 114 | @ <li>Selectively filter out wiki/ticket/baseline</li> |
| 115 | @ <li>?</li> |
| 116 | @ </ul> |
| 117 | |
| 118 | } |
| 119 | |
| 120 | static void tagview_page_search_miniform(void){ |
| 121 | char const * like = P("like"); |
| 122 | @ <div style='font-size:smaller'> |
| 123 | @ <form action='/tagview' method='post'> |
| 124 | @ Search for tags: |
| 125 | @ <input type='text' name='like' value='%s((like?like:""))' size='10'/> |
| 126 | @ <input type='submit'/> |
| 127 | @ </form> |
| 128 | @ </div> |
| 129 | } |
| 130 | |
| 131 | |
| 132 | static void tagview_page_default(void){ |
| 133 | tagview_page_list_tags( 0 ); |
| 134 | } |
| 135 | |
| 136 | static void tagview_page_tag_by_id( int tagid ) |
| 137 | { |
| 138 | Stmt st; |
| 139 | char * sql = mprintf( |
| 140 | "SELECT DISTINCT (t.tagname), DATETIME(tx.mtime), b.uuid " |
| 141 | "FROM tag t, tagxref tx, blob b " |
| 142 | "WHERE (t.tagid=%d) AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 143 | "ORDER BY tx.mtime DESC", |
| 144 | tagid); |
| 145 | db_prepare( &st, sql ); |
| 146 | free( sql ); |
| 147 | @ <h2>Tag ID %d(tagid):</h2> |
| 148 | @ <table cellpadding='4px' border='1'><tbody> |
| 149 | @ <tr><th>Tag name</th><th>Timestamp</th><th>Version</th></tr> |
| 150 | while( SQLITE_ROW == db_step(&st) ) |
| 151 | { |
| 152 | char const * tagname = db_column_text( &st, 0 ); |
| 153 | char const * tagtime = db_column_text( &st, 1 ); |
| 154 | char const * uuid = db_column_text( &st, 2 ); |
| 155 | const int offset = 10; |
| 156 | char shortname[offset+1]; |
| 157 | shortname[offset] = '\0'; |
| 158 | memcpy( shortname, uuid, offset ); |
| 159 | @ <tr> |
| 160 | @ <td><tt><a href='%s(g.zBaseURL)/tagview/%q(tagname)'>%s(tagname)</a></tt></td> |
| 161 | @ <td align='center'><tt>%s(tagtime)</tt></td> |
| 162 | @ <td><tt> |
| 163 | @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> |
| 164 | @ </tt></td></tr> |
| 165 | } |
| 166 | @ </tbody></table> |
| 167 | db_finalize( &st ); |
| 168 | } |
| 169 | |
| 170 | static void tagview_page_tag_by_name( char const * tagname ) |
| 171 | { |
| 172 | Stmt st; |
| 173 | char * sql = mprintf( |
| 174 | "SELECT DISTINCT t.tagid, DATETIME(tx.mtime), b.uuid " |
| 175 | "FROM tag t, tagxref tx, blob b " |
| 176 | "WHERE (t.tagname='%q') AND (t.tagid=tx.tagid) AND (tx.srcid=b.rid) " |
| 177 | "ORDER BY tx.mtime DESC", |
| 178 | tagname); |
| 179 | db_prepare( &st, sql ); |
| 180 | free( sql ); |
| 181 | @ <h2>Tag '%s(tagname)':</h2> |
| 182 | @ <table cellpadding='4px' border='1'><tbody> |
| 183 | @ <tr><th>Tag ID</th><th>Timestamp</th><th>Version</th></tr> |
| 184 | while( SQLITE_ROW == db_step(&st) ) |
| 185 | { |
| 186 | int tagid = db_column_int( &st, 0 ); |
| 187 | char const * tagtime = db_column_text( &st, 1 ); |
| 188 | char const * uuid = db_column_text( &st, 2 ); |
| 189 | const int offset = 10; |
| 190 | char shortname[offset+1]; |
| 191 | shortname[offset] = '\0'; |
| 192 | memcpy( shortname, uuid, offset ); |
| 193 | @ <tr> |
| 194 | @ <td><tt><a href='%s(g.zBaseURL)/tagview?tagid=%d(tagid)'>%d(tagid)</a></tt></td> |
| 195 | @ <td align='center'><tt>%s(tagtime)</tt></td> |
| 196 | @ <td><tt> |
| 197 | @ <a href='%s(g.zBaseURL)/vinfo/%s(uuid)'><strong>%s(shortname)</strong>%s(uuid+offset)</a> |
| 198 | @ </tt></td></tr> |
| 199 | } |
| 200 | @ </tbody></table> |
| 201 | db_finalize( &st ); |
| 202 | } |
| 203 | |
| 204 | |
| 205 | /* |
| 206 | ** WEBPAGE: /tagview |
| 207 | */ |
| 208 | void tagview_page(void){ |
| 209 | |
| 210 | login_check_credentials(); |
| 211 | if( !g.okSetup ){ |
| 212 | login_needed(); |
| 213 | } |
| 214 | style_header("Tags"); |
| 215 | tagview_page_search_miniform(); |
| 216 | @ <hr/> |
| 217 | char const * check = 0; |
| 218 | if( 0 != (check = P("tagid")) ) |
| 219 | { |
| 220 | tagview_page_tag_by_id( atoi(check) ); |
| 221 | } |
| 222 | else if( 0 != (check = P("like")) ) |
| 223 | { |
| 224 | tagview_page_list_tags( check ); |
| 225 | } |
| 226 | else if( 0 != (check = P("name")) ) |
| 227 | { |
| 228 | tagview_page_tag_by_name( check ); |
| 229 | } |
| 230 | else |
| 231 | { |
| 232 | tagview_page_default(); |
| 233 | } |
| 234 | style_footer(); |
| 235 | } |
| 236 |