Fossil SCM

Merged importer to mainline.

aku 2008-02-03 01:36 trunk merge
Commit 0523983440b0cd26366d924a3674eab30e93e1f1
2 files changed +363 -8 +171 -35
+363 -8
--- src/diff.c
+++ src/diff.c
@@ -42,29 +42,53 @@
4242
** line. If any line is longer than 1048575 characters,
4343
** the file is considered binary.
4444
*/
4545
typedef struct DLine DLine;
4646
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 */
4955
};
5056
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
+};
5276
5377
/*
5478
** Return an array of DLine objects containing a pointer to the
5579
** start of each line and a hash of that line. The lower
5680
** bits of the hash store the length of each line.
5781
**
5882
** Trailing whitespace is removed from each line.
5983
**
6084
** Return 0 if the file is binary or contains a line that is
61
-** longer than 1048575 bytes.
85
+** too long.
6286
*/
6387
static DLine *break_into_lines(char *z, int *pnLine){
6488
int nLine, i, j, k, x;
65
- unsigned int h;
89
+ unsigned int h, h2;
6690
DLine *a;
6791
6892
/* Count the number of lines. Allocate space to hold
6993
** the returned array.
7094
*/
@@ -73,31 +97,35 @@
7397
if( c==0 ){
7498
return 0;
7599
}
76100
if( c=='\n' && z[i+1]!=0 ){
77101
nLine++;
78
- if( j>1048575 ){
102
+ if( j>LENGTH_MASK ){
79103
return 0;
80104
}
81105
j = 0;
82106
}
83107
}
84
- if( j>1048575 ){
108
+ if( j>LENGTH_MASK ){
85109
return 0;
86110
}
87111
a = malloc( nLine*sizeof(a[0]) );
88112
if( a==0 ) fossil_panic("out of memory");
113
+ memset(a, 0, nLine*sizeof(a[0]) );
89114
90115
/* Fill in the array */
91116
for(i=0; i<nLine; i++){
92117
a[i].z = z;
93118
for(j=0; z[j] && z[j]!='\n'; j++){}
94119
for(k=j; k>0 && isspace(z[k-1]); k--){}
95120
for(h=0, x=0; x<k; x++){
96121
h = h ^ (h<<2) ^ z[x];
97122
}
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;
99127
z += j+1;
100128
}
101129
102130
/* Return results */
103131
*pnLine = nLine;
@@ -118,10 +146,336 @@
118146
blob_append(pOut, zPrefix, 1);
119147
blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK);
120148
blob_append(pOut, "\n", 1);
121149
}
122150
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 ************/
123477
124478
/*
125479
** Generate a report of the differences between files pA and pB.
126480
** If pOut is not NULL then a unified diff is appended there. It
127481
** is assumed that pOut has already been initialized. If pOut is
@@ -472,10 +826,11 @@
472826
free(B);
473827
474828
/* Return the result */
475829
return R;
476830
}
831
+#endif /***************** End of the Wagner/Myers algorithm ************/
477832
478833
/*
479834
** COMMAND: test-rawdiff
480835
*/
481836
void test_rawdiff_cmd(void){
482837
--- 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 @@
11
/*
22
** Copyright (c) 2007 D. Richard Hipp
3
+** Copyright (c) 2008 Stephan Beal
34
**
45
** This program is free software; you can redistribute it and/or
56
** modify it under the terms of the GNU General Public
67
** License as published by the Free Software Foundation; either
78
** version 2 of the License, or (at your option) any later version.
@@ -40,60 +41,195 @@
4041
const char *zLink,
4142
const char *zDesc
4243
){
4344
@ <tr><td valign="top" align="right">
4445
if( zLink && zLink[0] ){
45
- @ <a href="%s(zLink)">%h(zTitle)</a>
46
+ @ <a href="%s(g.zBaseURL)/%s(zLink)">%h(zTitle)</a>
4647
}else{
4748
@ %h(zTitle)
4849
}
4950
@ </td><td valign="top">%h(zDesc)</td></tr>
5051
}
5152
52
-/*
53
-** WEBPAGE: /tagview
54
-*/
55
-void tagview_page(void){
53
+static void tagview_page_list_tags( char const * like )
54
+{
5655
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>
6163
}
62
- style_header("Tags List");
64
+ else
65
+ {
66
+ limitstr = mprintf( "LIMIT %d", limit );
67
+ @ <h2>%d(limit) most recent tags:</h2>
68
+ }
6369
@ <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>
87106
}
88107
db_finalize( &st );
89108
@ </tbody></table>
90109
@ <hr/>TODOs include:
91110
@ <ul>
92111
@ <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>
94113
@ <li>Allow different sorting.</li>
114
+ @ <li>Selectively filter out wiki/ticket/baseline</li>
95115
@ <li>?</li>
96116
@ </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 );
98202
}
99203
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
+}
100236
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button