Fossil SCM
Improvements to annotate. Faster. More correct. Added gradient background colors to the web view. Default to showing line numbers. Do not show the check-in user. Fix for ticket [58ce2f221ae2e64ed].
Commit
89bf6f13f9c407c448b6a20b79fe9f0a4192f4e6
Parent
434be79b43e2b2a…
1 file changed
+135
-97
+135
-97
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -1653,23 +1653,26 @@ | ||
| 1653 | 1653 | int iS1, int iE1, /* Range of lines in p->aFrom[] */ |
| 1654 | 1654 | int iS2, int iE2, /* Range of lines in p->aTo[] */ |
| 1655 | 1655 | int *piSX, int *piEX, /* Write p->aFrom[] common segment here */ |
| 1656 | 1656 | int *piSY, int *piEY /* Write p->aTo[] common segment here */ |
| 1657 | 1657 | ){ |
| 1658 | - double bestScore = -1e30; /* Best score seen so far */ | |
| 1659 | 1658 | int i, j, k; /* Loop counters */ |
| 1660 | 1659 | int n; /* Loop limit */ |
| 1661 | 1660 | DLine *pA, *pB; /* Pointers to lines */ |
| 1662 | 1661 | int iSX, iSY, iEX, iEY; /* Current match */ |
| 1663 | - double score; /* Current score */ | |
| 1664 | - int skew; /* How lopsided is the match */ | |
| 1665 | - int dist; /* Distance of match from center */ | |
| 1662 | + int skew = 0; /* How lopsided is the match */ | |
| 1663 | + int dist = 0; /* Distance of match from center */ | |
| 1666 | 1664 | int mid; /* Center of the span */ |
| 1667 | 1665 | int iSXb, iSYb, iEXb, iEYb; /* Best match so far */ |
| 1668 | 1666 | int iSXp, iSYp, iEXp, iEYp; /* Previous match */ |
| 1669 | - | |
| 1667 | + sqlite3_int64 bestScore; /* Best score so far */ | |
| 1668 | + sqlite3_int64 score; /* Score for current candidate LCS */ | |
| 1669 | + int span; /* combined width of the input sequences */ | |
| 1670 | 1670 | |
| 1671 | + span = (iE1 - iS1) + (iE2 - iS2); | |
| 1672 | + bestScore = -10000; | |
| 1673 | + score = 0; | |
| 1671 | 1674 | iSXb = iSXp = iS1; |
| 1672 | 1675 | iEXb = iEXp = iS1; |
| 1673 | 1676 | iSYb = iSYp = iS2; |
| 1674 | 1677 | iEYb = iEYp = iS2; |
| 1675 | 1678 | mid = (iE1 + iS1)/2; |
| @@ -1707,11 +1710,11 @@ | ||
| 1707 | 1710 | iEY += k; |
| 1708 | 1711 | skew = (iSX-iS1) - (iSY-iS2); |
| 1709 | 1712 | if( skew<0 ) skew = -skew; |
| 1710 | 1713 | dist = (iSX+iEX)/2 - mid; |
| 1711 | 1714 | if( dist<0 ) dist = -dist; |
| 1712 | - score = (iEX - iSX) - 0.05*skew - 0.05*dist; | |
| 1715 | + score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist); | |
| 1713 | 1716 | if( score>bestScore ){ |
| 1714 | 1717 | bestScore = score; |
| 1715 | 1718 | iSXb = iSX; |
| 1716 | 1719 | iSYb = iSY; |
| 1717 | 1720 | iEXb = iEX; |
| @@ -1731,12 +1734,10 @@ | ||
| 1731 | 1734 | *piSX = iSXb; |
| 1732 | 1735 | *piSY = iSYb; |
| 1733 | 1736 | *piEX = iEXb; |
| 1734 | 1737 | *piEY = iEYb; |
| 1735 | 1738 | } |
| 1736 | - /* printf("LCS(%d..%d/%d..%d) = %d..%d/%d..%d\n", | |
| 1737 | - iS1, iE1, iS2, iE2, *piSX, *piEX, *piSY, *piEY); */ | |
| 1738 | 1739 | } |
| 1739 | 1740 | |
| 1740 | 1741 | /* |
| 1741 | 1742 | ** Expand the size of aEdit[] array to hold at least nEdit elements. |
| 1742 | 1743 | */ |
| @@ -2191,16 +2192,24 @@ | ||
| 2191 | 2192 | DContext c; /* The diff-engine context */ |
| 2192 | 2193 | struct AnnLine { /* Lines of the original files... */ |
| 2193 | 2194 | const char *z; /* The text of the line */ |
| 2194 | 2195 | short int n; /* Number of bytes (omitting trailing space and \n) */ |
| 2195 | 2196 | short int iLevel; /* Level at which tag was set */ |
| 2196 | - const char *zSrc; /* Tag showing origin of this line */ | |
| 2197 | + int iVers; /* aVers[] entry responsible for this line */ | |
| 2197 | 2198 | } *aOrig; |
| 2198 | 2199 | int nOrig; /* Number of elements in aOrig[] */ |
| 2199 | 2200 | int nNoSrc; /* Number of entries where aOrig[].zSrc==NULL */ |
| 2200 | 2201 | int iLevel; /* Current level */ |
| 2201 | 2202 | int nVers; /* Number of versions analyzed */ |
| 2203 | + struct AnnVers { | |
| 2204 | + const char *zFUuid; /* File being analyzed */ | |
| 2205 | + const char *zMUuid; /* Check-in containing the file */ | |
| 2206 | + const char *zUser; /* User who did the check-in */ | |
| 2207 | + const char *zDate; /* Date of the check-in */ | |
| 2208 | + const char *zBgColor; /* Suggested background color */ | |
| 2209 | + unsigned cnt; /* Number of lines contributed by this check-in */ | |
| 2210 | + } *aVers; /* For each check-in analyzed */ | |
| 2202 | 2211 | char **azVers; /* Names of versions analyzed */ |
| 2203 | 2212 | }; |
| 2204 | 2213 | |
| 2205 | 2214 | /* |
| 2206 | 2215 | ** Initialize the annotation process by specifying the file that is |
| @@ -2217,11 +2226,11 @@ | ||
| 2217 | 2226 | } |
| 2218 | 2227 | p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo ); |
| 2219 | 2228 | for(i=0; i<p->c.nTo; i++){ |
| 2220 | 2229 | p->aOrig[i].z = p->c.aTo[i].z; |
| 2221 | 2230 | p->aOrig[i].n = p->c.aTo[i].h & LENGTH_MASK; |
| 2222 | - p->aOrig[i].zSrc = 0; | |
| 2231 | + p->aOrig[i].iVers = -1; | |
| 2223 | 2232 | } |
| 2224 | 2233 | p->nOrig = p->c.nTo; |
| 2225 | 2234 | return 0; |
| 2226 | 2235 | } |
| 2227 | 2236 | |
| @@ -2230,15 +2239,13 @@ | ||
| 2230 | 2239 | ** being annotated. Do another step of the annotation. Return true |
| 2231 | 2240 | ** if additional annotation is required. zPName is the tag to insert |
| 2232 | 2241 | ** on each line of the file being annotated that was contributed by |
| 2233 | 2242 | ** pParent. Memory to hold zPName is leaked. |
| 2234 | 2243 | */ |
| 2235 | -static int annotation_step(Annotator *p, Blob *pParent, char *zPName){ | |
| 2244 | +static int annotation_step(Annotator *p, Blob *pParent, int iVers){ | |
| 2236 | 2245 | int i, j; |
| 2237 | 2246 | int lnTo; |
| 2238 | - int iPrevLevel; | |
| 2239 | - int iThisLevel; | |
| 2240 | 2247 | |
| 2241 | 2248 | /* Prepare the parent file to be diffed */ |
| 2242 | 2249 | p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent), |
| 2243 | 2250 | &p->c.nFrom, 1); |
| 2244 | 2251 | if( p->c.aFrom==0 ){ |
| @@ -2248,24 +2255,22 @@ | ||
| 2248 | 2255 | /* Compute the differences going from pParent to the file being |
| 2249 | 2256 | ** annotated. */ |
| 2250 | 2257 | diff_all(&p->c); |
| 2251 | 2258 | |
| 2252 | 2259 | /* Where new lines are inserted on this difference, record the |
| 2253 | - ** zPName as the source of the new line. | |
| 2260 | + ** iVers as the source of the new line. | |
| 2254 | 2261 | */ |
| 2255 | - iPrevLevel = p->iLevel; | |
| 2256 | 2262 | p->iLevel++; |
| 2257 | - iThisLevel = p->iLevel; | |
| 2258 | 2263 | for(i=lnTo=0; i<p->c.nEdit; i+=3){ |
| 2259 | - struct AnnLine *x = &p->aOrig[lnTo]; | |
| 2260 | - for(j=0; j<p->c.aEdit[i]; j++, lnTo++, x++){ | |
| 2261 | - if( x->zSrc==0 || x->iLevel==iPrevLevel ){ | |
| 2262 | - x->zSrc = zPName; | |
| 2263 | - x->iLevel = iThisLevel; | |
| 2264 | + int nCopy = p->c.aEdit[i]; | |
| 2265 | + int nIns = p->c.aEdit[i+2]; | |
| 2266 | + lnTo += nCopy; | |
| 2267 | + for(j=0; j<nIns; j++, lnTo++){ | |
| 2268 | + if( p->aOrig[lnTo].iVers<0 ){ | |
| 2269 | + p->aOrig[lnTo].iVers = iVers; | |
| 2264 | 2270 | } |
| 2265 | 2271 | } |
| 2266 | - lnTo += p->c.aEdit[i+2]; | |
| 2267 | 2272 | } |
| 2268 | 2273 | |
| 2269 | 2274 | /* Clear out the diff results */ |
| 2270 | 2275 | fossil_free(p->c.aEdit); |
| 2271 | 2276 | p->c.aEdit = 0; |
| @@ -2278,39 +2283,10 @@ | ||
| 2278 | 2283 | /* Return no errors */ |
| 2279 | 2284 | return 0; |
| 2280 | 2285 | } |
| 2281 | 2286 | |
| 2282 | 2287 | |
| 2283 | -/* | |
| 2284 | -** COMMAND: test-annotate-step | |
| 2285 | -*/ | |
| 2286 | -void test_annotate_step_cmd(void){ | |
| 2287 | - Blob orig, b; | |
| 2288 | - Annotator x; | |
| 2289 | - int i; | |
| 2290 | - | |
| 2291 | - if( g.argc<4 ) usage("RID1 RID2 ..."); | |
| 2292 | - db_must_be_within_tree(); | |
| 2293 | - blob_zero(&b); | |
| 2294 | - content_get(name_to_rid(g.argv[2]), &orig); | |
| 2295 | - if( annotation_start(&x, &orig) ){ | |
| 2296 | - fossil_fatal("binary file"); | |
| 2297 | - } | |
| 2298 | - for(i=3; i<g.argc; i++){ | |
| 2299 | - blob_zero(&b); | |
| 2300 | - content_get(name_to_rid(g.argv[i]), &b); | |
| 2301 | - if( annotation_step(&x, &b, g.argv[i-1]) ){ | |
| 2302 | - fossil_fatal("binary file"); | |
| 2303 | - } | |
| 2304 | - } | |
| 2305 | - for(i=0; i<x.nOrig; i++){ | |
| 2306 | - const char *zSrc = x.aOrig[i].zSrc; | |
| 2307 | - if( zSrc==0 ) zSrc = g.argv[g.argc-1]; | |
| 2308 | - fossil_print("%10s: %.*s\n", zSrc, x.aOrig[i].n, x.aOrig[i].z); | |
| 2309 | - } | |
| 2310 | -} | |
| 2311 | - | |
| 2312 | 2288 | /* Annotation flags */ |
| 2313 | 2289 | #define ANN_FILE_VERS 0x01 /* Show file vers rather than commit vers */ |
| 2314 | 2290 | #define ANN_FILE_ANCEST 0x02 /* Prefer check-ins in the ANCESTOR table */ |
| 2315 | 2291 | |
| 2316 | 2292 | /* |
| @@ -2320,11 +2296,10 @@ | ||
| 2320 | 2296 | */ |
| 2321 | 2297 | static void annotate_file( |
| 2322 | 2298 | Annotator *p, /* The annotator */ |
| 2323 | 2299 | int fnid, /* The name of the file to be annotated */ |
| 2324 | 2300 | int mid, /* Use the version of the file in this check-in */ |
| 2325 | - int webLabel, /* Use web-style annotations if true */ | |
| 2326 | 2301 | int iLimit, /* Limit the number of levels if greater than zero */ |
| 2327 | 2302 | int annFlags /* Flags to alter the annotation */ |
| 2328 | 2303 | ){ |
| 2329 | 2304 | Blob toAnnotate; /* Text of the final (mid) version of the file */ |
| 2330 | 2305 | Blob step; /* Text of previous revision */ |
| @@ -2350,57 +2325,69 @@ | ||
| 2350 | 2325 | "DELETE FROM vseen;" |
| 2351 | 2326 | ); |
| 2352 | 2327 | |
| 2353 | 2328 | db_prepare(&ins, "INSERT OR IGNORE INTO vseen(rid) VALUES(:rid)"); |
| 2354 | 2329 | db_prepare(&q, |
| 2355 | - "SELECT (SELECT uuid FROM blob WHERE rid=mlink.%s)," | |
| 2330 | + "SELECT (SELECT uuid FROM blob WHERE rid=mlink.fid)," | |
| 2331 | + " (SELECT uuid FROM blob WHERE rid=mlink.mid)," | |
| 2356 | 2332 | " date(event.mtime)," |
| 2357 | 2333 | " coalesce(event.euser,event.user)," |
| 2358 | 2334 | " mlink.pid" |
| 2359 | 2335 | " FROM mlink, event" |
| 2360 | 2336 | " WHERE mlink.fid=:rid" |
| 2361 | 2337 | " AND event.objid=mlink.mid" |
| 2362 | 2338 | " AND mlink.pid NOT IN vseen" |
| 2363 | 2339 | " ORDER BY %s event.mtime", |
| 2364 | - (annFlags & ANN_FILE_VERS)!=0 ? "fid" : "mid", | |
| 2365 | 2340 | (annFlags & ANN_FILE_ANCEST)!=0 ? |
| 2366 | 2341 | "(mlink.mid IN (SELECT rid FROM ancestor)) DESC,":"" |
| 2367 | 2342 | ); |
| 2368 | 2343 | |
| 2369 | 2344 | db_bind_int(&q, ":rid", rid); |
| 2370 | 2345 | if( iLimit==0 ) iLimit = 1000000000; |
| 2371 | 2346 | while( rid && iLimit>cnt && db_step(&q)==SQLITE_ROW ){ |
| 2372 | - const char *zUuid = db_column_text(&q, 0); | |
| 2373 | - const char *zDate = db_column_text(&q, 1); | |
| 2374 | - const char *zUser = db_column_text(&q, 2); | |
| 2375 | - int prevId = db_column_int(&q, 3); | |
| 2376 | - if( webLabel ){ | |
| 2377 | - zLabel = mprintf( | |
| 2378 | - "<a href='%R/info/%s' target='infowindow'>%.10s</a> %s %13.13s", | |
| 2379 | - zUuid, zUuid, zDate, zUser | |
| 2380 | - ); | |
| 2381 | - }else{ | |
| 2382 | - zLabel = mprintf("%.10s %s %13.13s", zUuid, zDate, zUser); | |
| 2347 | + int prevId = db_column_int(&q, 4); | |
| 2348 | + p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0])); | |
| 2349 | + p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0)); | |
| 2350 | + p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1)); | |
| 2351 | + p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2)); | |
| 2352 | + p->aVers[p->nVers].zUser = fossil_strdup(db_column_text(&q, 3)); | |
| 2353 | + if( p->nVers ){ | |
| 2354 | + content_get(rid, &step); | |
| 2355 | + annotation_step(p, &step, p->nVers-1); | |
| 2356 | + blob_reset(&step); | |
| 2383 | 2357 | } |
| 2384 | 2358 | p->nVers++; |
| 2385 | - p->azVers = fossil_realloc(p->azVers, p->nVers*sizeof(p->azVers[0]) ); | |
| 2386 | - p->azVers[p->nVers-1] = zLabel; | |
| 2387 | - content_get(rid, &step); | |
| 2388 | - annotation_step(p, &step, cnt==iLimit-1 ? "" : zLabel); | |
| 2389 | 2359 | db_bind_int(&ins, ":rid", rid); |
| 2390 | 2360 | db_step(&ins); |
| 2391 | 2361 | db_reset(&ins); |
| 2392 | - blob_reset(&step); | |
| 2393 | 2362 | db_reset(&q); |
| 2394 | 2363 | rid = prevId; |
| 2395 | 2364 | db_bind_int(&q, ":rid", prevId); |
| 2396 | 2365 | cnt++; |
| 2397 | 2366 | } |
| 2398 | 2367 | db_finalize(&q); |
| 2399 | 2368 | db_finalize(&ins); |
| 2400 | 2369 | db_end_transaction(0); |
| 2401 | 2370 | } |
| 2371 | + | |
| 2372 | +/* | |
| 2373 | +** Return a color from a gradient. | |
| 2374 | +*/ | |
| 2375 | +unsigned gradient_color(unsigned c1, unsigned c2, int n, int i){ | |
| 2376 | + unsigned c; /* Result color */ | |
| 2377 | + unsigned x1, x2; | |
| 2378 | + x1 = (c1>>16)&0xff; | |
| 2379 | + x2 = (c2>>16)&0xff; | |
| 2380 | + c = (x1*(n-i) + x2*i)/n<<16 & 0xff0000; | |
| 2381 | + x1 = (c1>>8)&0xff; | |
| 2382 | + x2 = (c2>>8)&0xff; | |
| 2383 | + c |= (x1*(n-i) + x2*i)/n<<8 & 0xff00; | |
| 2384 | + x1 = c1&0xff; | |
| 2385 | + x2 = c2&0xff; | |
| 2386 | + c |= (x1*(n-i) + x2*i)/n & 0xff; | |
| 2387 | + return c; | |
| 2388 | +} | |
| 2402 | 2389 | |
| 2403 | 2390 | /* |
| 2404 | 2391 | ** WEBPAGE: annotate |
| 2405 | 2392 | ** |
| 2406 | 2393 | ** Query parameters: |
| @@ -2422,10 +2409,12 @@ | ||
| 2422 | 2409 | int showLog = 0; /* True to display the log */ |
| 2423 | 2410 | char zLn[10]; /* Line number buffer */ |
| 2424 | 2411 | char zFormat[10]; /* Format string for line numbers */ |
| 2425 | 2412 | Annotator ann; |
| 2426 | 2413 | HQuery url; |
| 2414 | + struct AnnVers *p; | |
| 2415 | + unsigned clr1, clr2, clr; | |
| 2427 | 2416 | |
| 2428 | 2417 | showLn = atoi(PD("ln","1")); |
| 2429 | 2418 | showLog = atoi(PD("log","1")); |
| 2430 | 2419 | login_check_credentials(); |
| 2431 | 2420 | if( !g.perm.Read ){ login_needed(); return; } |
| @@ -2471,43 +2460,84 @@ | ||
| 2471 | 2460 | } |
| 2472 | 2461 | if( iLimit!=20 ){ |
| 2473 | 2462 | style_submenu_element("20 Ancestors", "20 Ancestors", |
| 2474 | 2463 | url_render(&url, "limit", "20", 0, 0)); |
| 2475 | 2464 | } |
| 2476 | - annotate_file(&ann, fnid, mid, g.perm.Hyperlink, iLimit, annFlags); | |
| 2465 | + annotate_file(&ann, fnid, mid, iLimit, annFlags); | |
| 2466 | + if( db_get_boolean("white-foreground", 0) ){ | |
| 2467 | + clr1 = 0xa04040; | |
| 2468 | + clr2 = 0x4059a0; | |
| 2469 | + }else{ | |
| 2470 | + clr1 = 0xffb5b5; /* Recent changes: red (hot) */ | |
| 2471 | + clr2 = 0xb5e0ff; /* Older changes: blue (cold) */ | |
| 2472 | + } | |
| 2473 | + for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ | |
| 2474 | + clr = gradient_color(clr1, clr2, ann.nVers-1, i); | |
| 2475 | + ann.aVers[i].zBgColor = mprintf("#%06x", clr); | |
| 2476 | + ann.aVers[i].zUser = mprintf("%h", ann.aVers[i].zUser); | |
| 2477 | + } | |
| 2478 | + | |
| 2477 | 2479 | if( showLog ){ |
| 2478 | - int i; | |
| 2479 | 2480 | @ <h2>Versions analyzed:</h2> |
| 2480 | 2481 | @ <ol> |
| 2481 | - for(i=0; i<ann.nVers; i++){ | |
| 2482 | - @ <li><tt>%s(ann.azVers[i])</tt></li> | |
| 2482 | + for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ | |
| 2483 | + @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate) | |
| 2484 | + @ check-in %z(href("%R/info/%S",p->zMUuid))%.10s(p->zMUuid)</a> | |
| 2485 | + @ artifact %z(href("%R/artifact/%S",p->zFUuid))%.10s(p->zFUuid)</a> | |
| 2486 | + @ </span> | |
| 2487 | +#if 0 | |
| 2488 | + if( i>0 ){ | |
| 2489 | + char *zLink = xhref("target='infowindow'", | |
| 2490 | + "%R/fdiff?v1=%S&v2=%S&sbs=1", | |
| 2491 | + p->zFUuid,ann.aVers[0].zFUuid); | |
| 2492 | + @ %z(zLink)[diff-to-top]</a> | |
| 2493 | + if( i>1 ){ | |
| 2494 | + zLink = xhref("target='infowindow'", | |
| 2495 | + "%R/fdiff?v1=%S&v2=%S&sbs=1", | |
| 2496 | + p->zFUuid,p[-1].zFUuid); | |
| 2497 | + @ %z(zLink)[diff-to-previous]</a> | |
| 2498 | + } | |
| 2499 | + } | |
| 2500 | +#endif | |
| 2483 | 2501 | } |
| 2484 | 2502 | @ </ol> |
| 2485 | 2503 | @ <hr> |
| 2486 | 2504 | } |
| 2487 | 2505 | if( iLimit<0 ){ |
| 2488 | 2506 | @ <h2>Annotation:</h2> |
| 2507 | + iLimit = ann.nVers+10; | |
| 2489 | 2508 | }else{ |
| 2490 | 2509 | @ <h2>Annotation of %d(iLimit) most recent ancestors:</h2> |
| 2491 | 2510 | } |
| 2492 | 2511 | if( showLn ){ |
| 2493 | 2512 | sqlite3_snprintf(sizeof(zLn), zLn, "%d", ann.nOrig+1); |
| 2494 | - sqlite3_snprintf(sizeof(zFormat), zFormat, "%%%dd: ", strlen(zLn)); | |
| 2513 | + sqlite3_snprintf(sizeof(zFormat), zFormat, "%%%dd:", strlen(zLn)); | |
| 2495 | 2514 | }else{ |
| 2496 | 2515 | zLn[0] = 0; |
| 2497 | 2516 | } |
| 2498 | 2517 | @ <pre> |
| 2499 | 2518 | for(i=0; i<ann.nOrig; i++){ |
| 2500 | - const char *zSrc = ann.aOrig[i].zSrc; | |
| 2501 | - const char *zSep = ":"; | |
| 2502 | - if( zSrc[0]==0 ){ | |
| 2503 | - zSrc = " "; | |
| 2504 | - zSep = " "; | |
| 2505 | - } | |
| 2506 | - ((char*)ann.aOrig[i].z)[ann.aOrig[i].n] = 0; | |
| 2507 | - if( showLn ) sqlite3_snprintf(sizeof(zLn), zLn, zFormat, i+1); | |
| 2508 | - @ %s(zSrc)%s(zSep)%s(zLn) %h(ann.aOrig[i].z) | |
| 2519 | + struct AnnVers *p; | |
| 2520 | + int iVers = ann.aOrig[i].iVers; | |
| 2521 | + char *z = (char*)ann.aOrig[i].z; | |
| 2522 | + int n = ann.aOrig[i].n; | |
| 2523 | + char zPrefix[300]; | |
| 2524 | + z[n] = 0; | |
| 2525 | + if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; | |
| 2526 | + if( iVers>=0 ){ | |
| 2527 | + struct AnnVers *p = ann.aVers+iVers; | |
| 2528 | + char *zLink = xhref("target='infowindow'", "%R/info/%S", p->zMUuid); | |
| 2529 | + sqlite3_snprintf(sizeof(zPrefix), zPrefix, | |
| 2530 | + "<span style='background-color:%s'>" | |
| 2531 | + "%s%.10s</a> %s</span> %4d:", | |
| 2532 | + p->zBgColor, zLink, p->zMUuid, p->zDate, i+1); | |
| 2533 | + fossil_free(zLink); | |
| 2534 | + }else{ | |
| 2535 | + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1); | |
| 2536 | + } | |
| 2537 | + @ %s(zPrefix) %h(z) | |
| 2538 | + | |
| 2509 | 2539 | } |
| 2510 | 2540 | @ </pre> |
| 2511 | 2541 | style_footer(); |
| 2512 | 2542 | } |
| 2513 | 2543 | |
| @@ -2571,28 +2601,36 @@ | ||
| 2571 | 2601 | " ORDER BY ancestor.generation ASC LIMIT 1", |
| 2572 | 2602 | fid, fnid); |
| 2573 | 2603 | if( mid==0 ){ |
| 2574 | 2604 | fossil_panic("unable to find manifest"); |
| 2575 | 2605 | } |
| 2576 | - if( fileVers ) annFlags |= ANN_FILE_VERS; | |
| 2577 | 2606 | annFlags |= ANN_FILE_ANCEST; |
| 2578 | - annotate_file(&ann, fnid, mid, 0, iLimit, annFlags); | |
| 2607 | + annotate_file(&ann, fnid, mid, iLimit, annFlags); | |
| 2579 | 2608 | if( showLog ){ |
| 2580 | - for(i=0; i<ann.nVers; i++){ | |
| 2581 | - printf("version %3d: %s\n", i+1, ann.azVers[i]); | |
| 2609 | + struct AnnVers *p; | |
| 2610 | + for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ | |
| 2611 | + fossil_print("version %3d: %s %.10s file %.10s\n", | |
| 2612 | + i+1, p->zDate, p->zMUuid, p->zFUuid); | |
| 2582 | 2613 | } |
| 2583 | - printf("---------------------------------------------------\n"); | |
| 2614 | + fossil_print("---------------------------------------------------\n"); | |
| 2584 | 2615 | } |
| 2585 | 2616 | for(i=0; i<ann.nOrig; i++){ |
| 2586 | - const char *zSrc = ann.aOrig[i].zSrc; | |
| 2587 | - const char *zSep = ":"; | |
| 2588 | - if( zSrc[0]==0 ){ | |
| 2589 | - zSrc = ""; | |
| 2590 | - zSep = " "; | |
| 2591 | - } | |
| 2592 | - fossil_print("%36s%s%.*s\n", | |
| 2593 | - zSrc, zSep, ann.aOrig[i].n, ann.aOrig[i].z); | |
| 2617 | + struct AnnVers *p; | |
| 2618 | + int iVers = ann.aOrig[i].iVers; | |
| 2619 | + char *z = (char*)ann.aOrig[i].z; | |
| 2620 | + int n = ann.aOrig[i].n; | |
| 2621 | + char zPrefix[200]; | |
| 2622 | + z[n] = 0; | |
| 2623 | + if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; | |
| 2624 | + if( iVers>=0 ){ | |
| 2625 | + struct AnnVers *p = ann.aVers+iVers; | |
| 2626 | + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%.10s %s", | |
| 2627 | + fileVers ? p->zFUuid : p->zMUuid, p->zDate); | |
| 2628 | + }else{ | |
| 2629 | + zPrefix[0] = 0; | |
| 2630 | + } | |
| 2631 | + fossil_print("%21s %4d: %.*s\n", zPrefix, i+1, n, z); | |
| 2594 | 2632 | } |
| 2595 | 2633 | } |
| 2596 | 2634 | |
| 2597 | 2635 | /* |
| 2598 | 2636 | ** COMMAND: test-looks-like-utf |
| 2599 | 2637 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -1653,23 +1653,26 @@ | |
| 1653 | int iS1, int iE1, /* Range of lines in p->aFrom[] */ |
| 1654 | int iS2, int iE2, /* Range of lines in p->aTo[] */ |
| 1655 | int *piSX, int *piEX, /* Write p->aFrom[] common segment here */ |
| 1656 | int *piSY, int *piEY /* Write p->aTo[] common segment here */ |
| 1657 | ){ |
| 1658 | double bestScore = -1e30; /* Best score seen so far */ |
| 1659 | int i, j, k; /* Loop counters */ |
| 1660 | int n; /* Loop limit */ |
| 1661 | DLine *pA, *pB; /* Pointers to lines */ |
| 1662 | int iSX, iSY, iEX, iEY; /* Current match */ |
| 1663 | double score; /* Current score */ |
| 1664 | int skew; /* How lopsided is the match */ |
| 1665 | int dist; /* Distance of match from center */ |
| 1666 | int mid; /* Center of the span */ |
| 1667 | int iSXb, iSYb, iEXb, iEYb; /* Best match so far */ |
| 1668 | int iSXp, iSYp, iEXp, iEYp; /* Previous match */ |
| 1669 | |
| 1670 | |
| 1671 | iSXb = iSXp = iS1; |
| 1672 | iEXb = iEXp = iS1; |
| 1673 | iSYb = iSYp = iS2; |
| 1674 | iEYb = iEYp = iS2; |
| 1675 | mid = (iE1 + iS1)/2; |
| @@ -1707,11 +1710,11 @@ | |
| 1707 | iEY += k; |
| 1708 | skew = (iSX-iS1) - (iSY-iS2); |
| 1709 | if( skew<0 ) skew = -skew; |
| 1710 | dist = (iSX+iEX)/2 - mid; |
| 1711 | if( dist<0 ) dist = -dist; |
| 1712 | score = (iEX - iSX) - 0.05*skew - 0.05*dist; |
| 1713 | if( score>bestScore ){ |
| 1714 | bestScore = score; |
| 1715 | iSXb = iSX; |
| 1716 | iSYb = iSY; |
| 1717 | iEXb = iEX; |
| @@ -1731,12 +1734,10 @@ | |
| 1731 | *piSX = iSXb; |
| 1732 | *piSY = iSYb; |
| 1733 | *piEX = iEXb; |
| 1734 | *piEY = iEYb; |
| 1735 | } |
| 1736 | /* printf("LCS(%d..%d/%d..%d) = %d..%d/%d..%d\n", |
| 1737 | iS1, iE1, iS2, iE2, *piSX, *piEX, *piSY, *piEY); */ |
| 1738 | } |
| 1739 | |
| 1740 | /* |
| 1741 | ** Expand the size of aEdit[] array to hold at least nEdit elements. |
| 1742 | */ |
| @@ -2191,16 +2192,24 @@ | |
| 2191 | DContext c; /* The diff-engine context */ |
| 2192 | struct AnnLine { /* Lines of the original files... */ |
| 2193 | const char *z; /* The text of the line */ |
| 2194 | short int n; /* Number of bytes (omitting trailing space and \n) */ |
| 2195 | short int iLevel; /* Level at which tag was set */ |
| 2196 | const char *zSrc; /* Tag showing origin of this line */ |
| 2197 | } *aOrig; |
| 2198 | int nOrig; /* Number of elements in aOrig[] */ |
| 2199 | int nNoSrc; /* Number of entries where aOrig[].zSrc==NULL */ |
| 2200 | int iLevel; /* Current level */ |
| 2201 | int nVers; /* Number of versions analyzed */ |
| 2202 | char **azVers; /* Names of versions analyzed */ |
| 2203 | }; |
| 2204 | |
| 2205 | /* |
| 2206 | ** Initialize the annotation process by specifying the file that is |
| @@ -2217,11 +2226,11 @@ | |
| 2217 | } |
| 2218 | p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo ); |
| 2219 | for(i=0; i<p->c.nTo; i++){ |
| 2220 | p->aOrig[i].z = p->c.aTo[i].z; |
| 2221 | p->aOrig[i].n = p->c.aTo[i].h & LENGTH_MASK; |
| 2222 | p->aOrig[i].zSrc = 0; |
| 2223 | } |
| 2224 | p->nOrig = p->c.nTo; |
| 2225 | return 0; |
| 2226 | } |
| 2227 | |
| @@ -2230,15 +2239,13 @@ | |
| 2230 | ** being annotated. Do another step of the annotation. Return true |
| 2231 | ** if additional annotation is required. zPName is the tag to insert |
| 2232 | ** on each line of the file being annotated that was contributed by |
| 2233 | ** pParent. Memory to hold zPName is leaked. |
| 2234 | */ |
| 2235 | static int annotation_step(Annotator *p, Blob *pParent, char *zPName){ |
| 2236 | int i, j; |
| 2237 | int lnTo; |
| 2238 | int iPrevLevel; |
| 2239 | int iThisLevel; |
| 2240 | |
| 2241 | /* Prepare the parent file to be diffed */ |
| 2242 | p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent), |
| 2243 | &p->c.nFrom, 1); |
| 2244 | if( p->c.aFrom==0 ){ |
| @@ -2248,24 +2255,22 @@ | |
| 2248 | /* Compute the differences going from pParent to the file being |
| 2249 | ** annotated. */ |
| 2250 | diff_all(&p->c); |
| 2251 | |
| 2252 | /* Where new lines are inserted on this difference, record the |
| 2253 | ** zPName as the source of the new line. |
| 2254 | */ |
| 2255 | iPrevLevel = p->iLevel; |
| 2256 | p->iLevel++; |
| 2257 | iThisLevel = p->iLevel; |
| 2258 | for(i=lnTo=0; i<p->c.nEdit; i+=3){ |
| 2259 | struct AnnLine *x = &p->aOrig[lnTo]; |
| 2260 | for(j=0; j<p->c.aEdit[i]; j++, lnTo++, x++){ |
| 2261 | if( x->zSrc==0 || x->iLevel==iPrevLevel ){ |
| 2262 | x->zSrc = zPName; |
| 2263 | x->iLevel = iThisLevel; |
| 2264 | } |
| 2265 | } |
| 2266 | lnTo += p->c.aEdit[i+2]; |
| 2267 | } |
| 2268 | |
| 2269 | /* Clear out the diff results */ |
| 2270 | fossil_free(p->c.aEdit); |
| 2271 | p->c.aEdit = 0; |
| @@ -2278,39 +2283,10 @@ | |
| 2278 | /* Return no errors */ |
| 2279 | return 0; |
| 2280 | } |
| 2281 | |
| 2282 | |
| 2283 | /* |
| 2284 | ** COMMAND: test-annotate-step |
| 2285 | */ |
| 2286 | void test_annotate_step_cmd(void){ |
| 2287 | Blob orig, b; |
| 2288 | Annotator x; |
| 2289 | int i; |
| 2290 | |
| 2291 | if( g.argc<4 ) usage("RID1 RID2 ..."); |
| 2292 | db_must_be_within_tree(); |
| 2293 | blob_zero(&b); |
| 2294 | content_get(name_to_rid(g.argv[2]), &orig); |
| 2295 | if( annotation_start(&x, &orig) ){ |
| 2296 | fossil_fatal("binary file"); |
| 2297 | } |
| 2298 | for(i=3; i<g.argc; i++){ |
| 2299 | blob_zero(&b); |
| 2300 | content_get(name_to_rid(g.argv[i]), &b); |
| 2301 | if( annotation_step(&x, &b, g.argv[i-1]) ){ |
| 2302 | fossil_fatal("binary file"); |
| 2303 | } |
| 2304 | } |
| 2305 | for(i=0; i<x.nOrig; i++){ |
| 2306 | const char *zSrc = x.aOrig[i].zSrc; |
| 2307 | if( zSrc==0 ) zSrc = g.argv[g.argc-1]; |
| 2308 | fossil_print("%10s: %.*s\n", zSrc, x.aOrig[i].n, x.aOrig[i].z); |
| 2309 | } |
| 2310 | } |
| 2311 | |
| 2312 | /* Annotation flags */ |
| 2313 | #define ANN_FILE_VERS 0x01 /* Show file vers rather than commit vers */ |
| 2314 | #define ANN_FILE_ANCEST 0x02 /* Prefer check-ins in the ANCESTOR table */ |
| 2315 | |
| 2316 | /* |
| @@ -2320,11 +2296,10 @@ | |
| 2320 | */ |
| 2321 | static void annotate_file( |
| 2322 | Annotator *p, /* The annotator */ |
| 2323 | int fnid, /* The name of the file to be annotated */ |
| 2324 | int mid, /* Use the version of the file in this check-in */ |
| 2325 | int webLabel, /* Use web-style annotations if true */ |
| 2326 | int iLimit, /* Limit the number of levels if greater than zero */ |
| 2327 | int annFlags /* Flags to alter the annotation */ |
| 2328 | ){ |
| 2329 | Blob toAnnotate; /* Text of the final (mid) version of the file */ |
| 2330 | Blob step; /* Text of previous revision */ |
| @@ -2350,57 +2325,69 @@ | |
| 2350 | "DELETE FROM vseen;" |
| 2351 | ); |
| 2352 | |
| 2353 | db_prepare(&ins, "INSERT OR IGNORE INTO vseen(rid) VALUES(:rid)"); |
| 2354 | db_prepare(&q, |
| 2355 | "SELECT (SELECT uuid FROM blob WHERE rid=mlink.%s)," |
| 2356 | " date(event.mtime)," |
| 2357 | " coalesce(event.euser,event.user)," |
| 2358 | " mlink.pid" |
| 2359 | " FROM mlink, event" |
| 2360 | " WHERE mlink.fid=:rid" |
| 2361 | " AND event.objid=mlink.mid" |
| 2362 | " AND mlink.pid NOT IN vseen" |
| 2363 | " ORDER BY %s event.mtime", |
| 2364 | (annFlags & ANN_FILE_VERS)!=0 ? "fid" : "mid", |
| 2365 | (annFlags & ANN_FILE_ANCEST)!=0 ? |
| 2366 | "(mlink.mid IN (SELECT rid FROM ancestor)) DESC,":"" |
| 2367 | ); |
| 2368 | |
| 2369 | db_bind_int(&q, ":rid", rid); |
| 2370 | if( iLimit==0 ) iLimit = 1000000000; |
| 2371 | while( rid && iLimit>cnt && db_step(&q)==SQLITE_ROW ){ |
| 2372 | const char *zUuid = db_column_text(&q, 0); |
| 2373 | const char *zDate = db_column_text(&q, 1); |
| 2374 | const char *zUser = db_column_text(&q, 2); |
| 2375 | int prevId = db_column_int(&q, 3); |
| 2376 | if( webLabel ){ |
| 2377 | zLabel = mprintf( |
| 2378 | "<a href='%R/info/%s' target='infowindow'>%.10s</a> %s %13.13s", |
| 2379 | zUuid, zUuid, zDate, zUser |
| 2380 | ); |
| 2381 | }else{ |
| 2382 | zLabel = mprintf("%.10s %s %13.13s", zUuid, zDate, zUser); |
| 2383 | } |
| 2384 | p->nVers++; |
| 2385 | p->azVers = fossil_realloc(p->azVers, p->nVers*sizeof(p->azVers[0]) ); |
| 2386 | p->azVers[p->nVers-1] = zLabel; |
| 2387 | content_get(rid, &step); |
| 2388 | annotation_step(p, &step, cnt==iLimit-1 ? "" : zLabel); |
| 2389 | db_bind_int(&ins, ":rid", rid); |
| 2390 | db_step(&ins); |
| 2391 | db_reset(&ins); |
| 2392 | blob_reset(&step); |
| 2393 | db_reset(&q); |
| 2394 | rid = prevId; |
| 2395 | db_bind_int(&q, ":rid", prevId); |
| 2396 | cnt++; |
| 2397 | } |
| 2398 | db_finalize(&q); |
| 2399 | db_finalize(&ins); |
| 2400 | db_end_transaction(0); |
| 2401 | } |
| 2402 | |
| 2403 | /* |
| 2404 | ** WEBPAGE: annotate |
| 2405 | ** |
| 2406 | ** Query parameters: |
| @@ -2422,10 +2409,12 @@ | |
| 2422 | int showLog = 0; /* True to display the log */ |
| 2423 | char zLn[10]; /* Line number buffer */ |
| 2424 | char zFormat[10]; /* Format string for line numbers */ |
| 2425 | Annotator ann; |
| 2426 | HQuery url; |
| 2427 | |
| 2428 | showLn = atoi(PD("ln","1")); |
| 2429 | showLog = atoi(PD("log","1")); |
| 2430 | login_check_credentials(); |
| 2431 | if( !g.perm.Read ){ login_needed(); return; } |
| @@ -2471,43 +2460,84 @@ | |
| 2471 | } |
| 2472 | if( iLimit!=20 ){ |
| 2473 | style_submenu_element("20 Ancestors", "20 Ancestors", |
| 2474 | url_render(&url, "limit", "20", 0, 0)); |
| 2475 | } |
| 2476 | annotate_file(&ann, fnid, mid, g.perm.Hyperlink, iLimit, annFlags); |
| 2477 | if( showLog ){ |
| 2478 | int i; |
| 2479 | @ <h2>Versions analyzed:</h2> |
| 2480 | @ <ol> |
| 2481 | for(i=0; i<ann.nVers; i++){ |
| 2482 | @ <li><tt>%s(ann.azVers[i])</tt></li> |
| 2483 | } |
| 2484 | @ </ol> |
| 2485 | @ <hr> |
| 2486 | } |
| 2487 | if( iLimit<0 ){ |
| 2488 | @ <h2>Annotation:</h2> |
| 2489 | }else{ |
| 2490 | @ <h2>Annotation of %d(iLimit) most recent ancestors:</h2> |
| 2491 | } |
| 2492 | if( showLn ){ |
| 2493 | sqlite3_snprintf(sizeof(zLn), zLn, "%d", ann.nOrig+1); |
| 2494 | sqlite3_snprintf(sizeof(zFormat), zFormat, "%%%dd: ", strlen(zLn)); |
| 2495 | }else{ |
| 2496 | zLn[0] = 0; |
| 2497 | } |
| 2498 | @ <pre> |
| 2499 | for(i=0; i<ann.nOrig; i++){ |
| 2500 | const char *zSrc = ann.aOrig[i].zSrc; |
| 2501 | const char *zSep = ":"; |
| 2502 | if( zSrc[0]==0 ){ |
| 2503 | zSrc = " "; |
| 2504 | zSep = " "; |
| 2505 | } |
| 2506 | ((char*)ann.aOrig[i].z)[ann.aOrig[i].n] = 0; |
| 2507 | if( showLn ) sqlite3_snprintf(sizeof(zLn), zLn, zFormat, i+1); |
| 2508 | @ %s(zSrc)%s(zSep)%s(zLn) %h(ann.aOrig[i].z) |
| 2509 | } |
| 2510 | @ </pre> |
| 2511 | style_footer(); |
| 2512 | } |
| 2513 | |
| @@ -2571,28 +2601,36 @@ | |
| 2571 | " ORDER BY ancestor.generation ASC LIMIT 1", |
| 2572 | fid, fnid); |
| 2573 | if( mid==0 ){ |
| 2574 | fossil_panic("unable to find manifest"); |
| 2575 | } |
| 2576 | if( fileVers ) annFlags |= ANN_FILE_VERS; |
| 2577 | annFlags |= ANN_FILE_ANCEST; |
| 2578 | annotate_file(&ann, fnid, mid, 0, iLimit, annFlags); |
| 2579 | if( showLog ){ |
| 2580 | for(i=0; i<ann.nVers; i++){ |
| 2581 | printf("version %3d: %s\n", i+1, ann.azVers[i]); |
| 2582 | } |
| 2583 | printf("---------------------------------------------------\n"); |
| 2584 | } |
| 2585 | for(i=0; i<ann.nOrig; i++){ |
| 2586 | const char *zSrc = ann.aOrig[i].zSrc; |
| 2587 | const char *zSep = ":"; |
| 2588 | if( zSrc[0]==0 ){ |
| 2589 | zSrc = ""; |
| 2590 | zSep = " "; |
| 2591 | } |
| 2592 | fossil_print("%36s%s%.*s\n", |
| 2593 | zSrc, zSep, ann.aOrig[i].n, ann.aOrig[i].z); |
| 2594 | } |
| 2595 | } |
| 2596 | |
| 2597 | /* |
| 2598 | ** COMMAND: test-looks-like-utf |
| 2599 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -1653,23 +1653,26 @@ | |
| 1653 | int iS1, int iE1, /* Range of lines in p->aFrom[] */ |
| 1654 | int iS2, int iE2, /* Range of lines in p->aTo[] */ |
| 1655 | int *piSX, int *piEX, /* Write p->aFrom[] common segment here */ |
| 1656 | int *piSY, int *piEY /* Write p->aTo[] common segment here */ |
| 1657 | ){ |
| 1658 | int i, j, k; /* Loop counters */ |
| 1659 | int n; /* Loop limit */ |
| 1660 | DLine *pA, *pB; /* Pointers to lines */ |
| 1661 | int iSX, iSY, iEX, iEY; /* Current match */ |
| 1662 | int skew = 0; /* How lopsided is the match */ |
| 1663 | int dist = 0; /* Distance of match from center */ |
| 1664 | int mid; /* Center of the span */ |
| 1665 | int iSXb, iSYb, iEXb, iEYb; /* Best match so far */ |
| 1666 | int iSXp, iSYp, iEXp, iEYp; /* Previous match */ |
| 1667 | sqlite3_int64 bestScore; /* Best score so far */ |
| 1668 | sqlite3_int64 score; /* Score for current candidate LCS */ |
| 1669 | int span; /* combined width of the input sequences */ |
| 1670 | |
| 1671 | span = (iE1 - iS1) + (iE2 - iS2); |
| 1672 | bestScore = -10000; |
| 1673 | score = 0; |
| 1674 | iSXb = iSXp = iS1; |
| 1675 | iEXb = iEXp = iS1; |
| 1676 | iSYb = iSYp = iS2; |
| 1677 | iEYb = iEYp = iS2; |
| 1678 | mid = (iE1 + iS1)/2; |
| @@ -1707,11 +1710,11 @@ | |
| 1710 | iEY += k; |
| 1711 | skew = (iSX-iS1) - (iSY-iS2); |
| 1712 | if( skew<0 ) skew = -skew; |
| 1713 | dist = (iSX+iEX)/2 - mid; |
| 1714 | if( dist<0 ) dist = -dist; |
| 1715 | score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist); |
| 1716 | if( score>bestScore ){ |
| 1717 | bestScore = score; |
| 1718 | iSXb = iSX; |
| 1719 | iSYb = iSY; |
| 1720 | iEXb = iEX; |
| @@ -1731,12 +1734,10 @@ | |
| 1734 | *piSX = iSXb; |
| 1735 | *piSY = iSYb; |
| 1736 | *piEX = iEXb; |
| 1737 | *piEY = iEYb; |
| 1738 | } |
| 1739 | } |
| 1740 | |
| 1741 | /* |
| 1742 | ** Expand the size of aEdit[] array to hold at least nEdit elements. |
| 1743 | */ |
| @@ -2191,16 +2192,24 @@ | |
| 2192 | DContext c; /* The diff-engine context */ |
| 2193 | struct AnnLine { /* Lines of the original files... */ |
| 2194 | const char *z; /* The text of the line */ |
| 2195 | short int n; /* Number of bytes (omitting trailing space and \n) */ |
| 2196 | short int iLevel; /* Level at which tag was set */ |
| 2197 | int iVers; /* aVers[] entry responsible for this line */ |
| 2198 | } *aOrig; |
| 2199 | int nOrig; /* Number of elements in aOrig[] */ |
| 2200 | int nNoSrc; /* Number of entries where aOrig[].zSrc==NULL */ |
| 2201 | int iLevel; /* Current level */ |
| 2202 | int nVers; /* Number of versions analyzed */ |
| 2203 | struct AnnVers { |
| 2204 | const char *zFUuid; /* File being analyzed */ |
| 2205 | const char *zMUuid; /* Check-in containing the file */ |
| 2206 | const char *zUser; /* User who did the check-in */ |
| 2207 | const char *zDate; /* Date of the check-in */ |
| 2208 | const char *zBgColor; /* Suggested background color */ |
| 2209 | unsigned cnt; /* Number of lines contributed by this check-in */ |
| 2210 | } *aVers; /* For each check-in analyzed */ |
| 2211 | char **azVers; /* Names of versions analyzed */ |
| 2212 | }; |
| 2213 | |
| 2214 | /* |
| 2215 | ** Initialize the annotation process by specifying the file that is |
| @@ -2217,11 +2226,11 @@ | |
| 2226 | } |
| 2227 | p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo ); |
| 2228 | for(i=0; i<p->c.nTo; i++){ |
| 2229 | p->aOrig[i].z = p->c.aTo[i].z; |
| 2230 | p->aOrig[i].n = p->c.aTo[i].h & LENGTH_MASK; |
| 2231 | p->aOrig[i].iVers = -1; |
| 2232 | } |
| 2233 | p->nOrig = p->c.nTo; |
| 2234 | return 0; |
| 2235 | } |
| 2236 | |
| @@ -2230,15 +2239,13 @@ | |
| 2239 | ** being annotated. Do another step of the annotation. Return true |
| 2240 | ** if additional annotation is required. zPName is the tag to insert |
| 2241 | ** on each line of the file being annotated that was contributed by |
| 2242 | ** pParent. Memory to hold zPName is leaked. |
| 2243 | */ |
| 2244 | static int annotation_step(Annotator *p, Blob *pParent, int iVers){ |
| 2245 | int i, j; |
| 2246 | int lnTo; |
| 2247 | |
| 2248 | /* Prepare the parent file to be diffed */ |
| 2249 | p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent), |
| 2250 | &p->c.nFrom, 1); |
| 2251 | if( p->c.aFrom==0 ){ |
| @@ -2248,24 +2255,22 @@ | |
| 2255 | /* Compute the differences going from pParent to the file being |
| 2256 | ** annotated. */ |
| 2257 | diff_all(&p->c); |
| 2258 | |
| 2259 | /* Where new lines are inserted on this difference, record the |
| 2260 | ** iVers as the source of the new line. |
| 2261 | */ |
| 2262 | p->iLevel++; |
| 2263 | for(i=lnTo=0; i<p->c.nEdit; i+=3){ |
| 2264 | int nCopy = p->c.aEdit[i]; |
| 2265 | int nIns = p->c.aEdit[i+2]; |
| 2266 | lnTo += nCopy; |
| 2267 | for(j=0; j<nIns; j++, lnTo++){ |
| 2268 | if( p->aOrig[lnTo].iVers<0 ){ |
| 2269 | p->aOrig[lnTo].iVers = iVers; |
| 2270 | } |
| 2271 | } |
| 2272 | } |
| 2273 | |
| 2274 | /* Clear out the diff results */ |
| 2275 | fossil_free(p->c.aEdit); |
| 2276 | p->c.aEdit = 0; |
| @@ -2278,39 +2283,10 @@ | |
| 2283 | /* Return no errors */ |
| 2284 | return 0; |
| 2285 | } |
| 2286 | |
| 2287 | |
| 2288 | /* Annotation flags */ |
| 2289 | #define ANN_FILE_VERS 0x01 /* Show file vers rather than commit vers */ |
| 2290 | #define ANN_FILE_ANCEST 0x02 /* Prefer check-ins in the ANCESTOR table */ |
| 2291 | |
| 2292 | /* |
| @@ -2320,11 +2296,10 @@ | |
| 2296 | */ |
| 2297 | static void annotate_file( |
| 2298 | Annotator *p, /* The annotator */ |
| 2299 | int fnid, /* The name of the file to be annotated */ |
| 2300 | int mid, /* Use the version of the file in this check-in */ |
| 2301 | int iLimit, /* Limit the number of levels if greater than zero */ |
| 2302 | int annFlags /* Flags to alter the annotation */ |
| 2303 | ){ |
| 2304 | Blob toAnnotate; /* Text of the final (mid) version of the file */ |
| 2305 | Blob step; /* Text of previous revision */ |
| @@ -2350,57 +2325,69 @@ | |
| 2325 | "DELETE FROM vseen;" |
| 2326 | ); |
| 2327 | |
| 2328 | db_prepare(&ins, "INSERT OR IGNORE INTO vseen(rid) VALUES(:rid)"); |
| 2329 | db_prepare(&q, |
| 2330 | "SELECT (SELECT uuid FROM blob WHERE rid=mlink.fid)," |
| 2331 | " (SELECT uuid FROM blob WHERE rid=mlink.mid)," |
| 2332 | " date(event.mtime)," |
| 2333 | " coalesce(event.euser,event.user)," |
| 2334 | " mlink.pid" |
| 2335 | " FROM mlink, event" |
| 2336 | " WHERE mlink.fid=:rid" |
| 2337 | " AND event.objid=mlink.mid" |
| 2338 | " AND mlink.pid NOT IN vseen" |
| 2339 | " ORDER BY %s event.mtime", |
| 2340 | (annFlags & ANN_FILE_ANCEST)!=0 ? |
| 2341 | "(mlink.mid IN (SELECT rid FROM ancestor)) DESC,":"" |
| 2342 | ); |
| 2343 | |
| 2344 | db_bind_int(&q, ":rid", rid); |
| 2345 | if( iLimit==0 ) iLimit = 1000000000; |
| 2346 | while( rid && iLimit>cnt && db_step(&q)==SQLITE_ROW ){ |
| 2347 | int prevId = db_column_int(&q, 4); |
| 2348 | p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0])); |
| 2349 | p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0)); |
| 2350 | p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1)); |
| 2351 | p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2)); |
| 2352 | p->aVers[p->nVers].zUser = fossil_strdup(db_column_text(&q, 3)); |
| 2353 | if( p->nVers ){ |
| 2354 | content_get(rid, &step); |
| 2355 | annotation_step(p, &step, p->nVers-1); |
| 2356 | blob_reset(&step); |
| 2357 | } |
| 2358 | p->nVers++; |
| 2359 | db_bind_int(&ins, ":rid", rid); |
| 2360 | db_step(&ins); |
| 2361 | db_reset(&ins); |
| 2362 | db_reset(&q); |
| 2363 | rid = prevId; |
| 2364 | db_bind_int(&q, ":rid", prevId); |
| 2365 | cnt++; |
| 2366 | } |
| 2367 | db_finalize(&q); |
| 2368 | db_finalize(&ins); |
| 2369 | db_end_transaction(0); |
| 2370 | } |
| 2371 | |
| 2372 | /* |
| 2373 | ** Return a color from a gradient. |
| 2374 | */ |
| 2375 | unsigned gradient_color(unsigned c1, unsigned c2, int n, int i){ |
| 2376 | unsigned c; /* Result color */ |
| 2377 | unsigned x1, x2; |
| 2378 | x1 = (c1>>16)&0xff; |
| 2379 | x2 = (c2>>16)&0xff; |
| 2380 | c = (x1*(n-i) + x2*i)/n<<16 & 0xff0000; |
| 2381 | x1 = (c1>>8)&0xff; |
| 2382 | x2 = (c2>>8)&0xff; |
| 2383 | c |= (x1*(n-i) + x2*i)/n<<8 & 0xff00; |
| 2384 | x1 = c1&0xff; |
| 2385 | x2 = c2&0xff; |
| 2386 | c |= (x1*(n-i) + x2*i)/n & 0xff; |
| 2387 | return c; |
| 2388 | } |
| 2389 | |
| 2390 | /* |
| 2391 | ** WEBPAGE: annotate |
| 2392 | ** |
| 2393 | ** Query parameters: |
| @@ -2422,10 +2409,12 @@ | |
| 2409 | int showLog = 0; /* True to display the log */ |
| 2410 | char zLn[10]; /* Line number buffer */ |
| 2411 | char zFormat[10]; /* Format string for line numbers */ |
| 2412 | Annotator ann; |
| 2413 | HQuery url; |
| 2414 | struct AnnVers *p; |
| 2415 | unsigned clr1, clr2, clr; |
| 2416 | |
| 2417 | showLn = atoi(PD("ln","1")); |
| 2418 | showLog = atoi(PD("log","1")); |
| 2419 | login_check_credentials(); |
| 2420 | if( !g.perm.Read ){ login_needed(); return; } |
| @@ -2471,43 +2460,84 @@ | |
| 2460 | } |
| 2461 | if( iLimit!=20 ){ |
| 2462 | style_submenu_element("20 Ancestors", "20 Ancestors", |
| 2463 | url_render(&url, "limit", "20", 0, 0)); |
| 2464 | } |
| 2465 | annotate_file(&ann, fnid, mid, iLimit, annFlags); |
| 2466 | if( db_get_boolean("white-foreground", 0) ){ |
| 2467 | clr1 = 0xa04040; |
| 2468 | clr2 = 0x4059a0; |
| 2469 | }else{ |
| 2470 | clr1 = 0xffb5b5; /* Recent changes: red (hot) */ |
| 2471 | clr2 = 0xb5e0ff; /* Older changes: blue (cold) */ |
| 2472 | } |
| 2473 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2474 | clr = gradient_color(clr1, clr2, ann.nVers-1, i); |
| 2475 | ann.aVers[i].zBgColor = mprintf("#%06x", clr); |
| 2476 | ann.aVers[i].zUser = mprintf("%h", ann.aVers[i].zUser); |
| 2477 | } |
| 2478 | |
| 2479 | if( showLog ){ |
| 2480 | @ <h2>Versions analyzed:</h2> |
| 2481 | @ <ol> |
| 2482 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2483 | @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate) |
| 2484 | @ check-in %z(href("%R/info/%S",p->zMUuid))%.10s(p->zMUuid)</a> |
| 2485 | @ artifact %z(href("%R/artifact/%S",p->zFUuid))%.10s(p->zFUuid)</a> |
| 2486 | @ </span> |
| 2487 | #if 0 |
| 2488 | if( i>0 ){ |
| 2489 | char *zLink = xhref("target='infowindow'", |
| 2490 | "%R/fdiff?v1=%S&v2=%S&sbs=1", |
| 2491 | p->zFUuid,ann.aVers[0].zFUuid); |
| 2492 | @ %z(zLink)[diff-to-top]</a> |
| 2493 | if( i>1 ){ |
| 2494 | zLink = xhref("target='infowindow'", |
| 2495 | "%R/fdiff?v1=%S&v2=%S&sbs=1", |
| 2496 | p->zFUuid,p[-1].zFUuid); |
| 2497 | @ %z(zLink)[diff-to-previous]</a> |
| 2498 | } |
| 2499 | } |
| 2500 | #endif |
| 2501 | } |
| 2502 | @ </ol> |
| 2503 | @ <hr> |
| 2504 | } |
| 2505 | if( iLimit<0 ){ |
| 2506 | @ <h2>Annotation:</h2> |
| 2507 | iLimit = ann.nVers+10; |
| 2508 | }else{ |
| 2509 | @ <h2>Annotation of %d(iLimit) most recent ancestors:</h2> |
| 2510 | } |
| 2511 | if( showLn ){ |
| 2512 | sqlite3_snprintf(sizeof(zLn), zLn, "%d", ann.nOrig+1); |
| 2513 | sqlite3_snprintf(sizeof(zFormat), zFormat, "%%%dd:", strlen(zLn)); |
| 2514 | }else{ |
| 2515 | zLn[0] = 0; |
| 2516 | } |
| 2517 | @ <pre> |
| 2518 | for(i=0; i<ann.nOrig; i++){ |
| 2519 | struct AnnVers *p; |
| 2520 | int iVers = ann.aOrig[i].iVers; |
| 2521 | char *z = (char*)ann.aOrig[i].z; |
| 2522 | int n = ann.aOrig[i].n; |
| 2523 | char zPrefix[300]; |
| 2524 | z[n] = 0; |
| 2525 | if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; |
| 2526 | if( iVers>=0 ){ |
| 2527 | struct AnnVers *p = ann.aVers+iVers; |
| 2528 | char *zLink = xhref("target='infowindow'", "%R/info/%S", p->zMUuid); |
| 2529 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, |
| 2530 | "<span style='background-color:%s'>" |
| 2531 | "%s%.10s</a> %s</span> %4d:", |
| 2532 | p->zBgColor, zLink, p->zMUuid, p->zDate, i+1); |
| 2533 | fossil_free(zLink); |
| 2534 | }else{ |
| 2535 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1); |
| 2536 | } |
| 2537 | @ %s(zPrefix) %h(z) |
| 2538 | |
| 2539 | } |
| 2540 | @ </pre> |
| 2541 | style_footer(); |
| 2542 | } |
| 2543 | |
| @@ -2571,28 +2601,36 @@ | |
| 2601 | " ORDER BY ancestor.generation ASC LIMIT 1", |
| 2602 | fid, fnid); |
| 2603 | if( mid==0 ){ |
| 2604 | fossil_panic("unable to find manifest"); |
| 2605 | } |
| 2606 | annFlags |= ANN_FILE_ANCEST; |
| 2607 | annotate_file(&ann, fnid, mid, iLimit, annFlags); |
| 2608 | if( showLog ){ |
| 2609 | struct AnnVers *p; |
| 2610 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2611 | fossil_print("version %3d: %s %.10s file %.10s\n", |
| 2612 | i+1, p->zDate, p->zMUuid, p->zFUuid); |
| 2613 | } |
| 2614 | fossil_print("---------------------------------------------------\n"); |
| 2615 | } |
| 2616 | for(i=0; i<ann.nOrig; i++){ |
| 2617 | struct AnnVers *p; |
| 2618 | int iVers = ann.aOrig[i].iVers; |
| 2619 | char *z = (char*)ann.aOrig[i].z; |
| 2620 | int n = ann.aOrig[i].n; |
| 2621 | char zPrefix[200]; |
| 2622 | z[n] = 0; |
| 2623 | if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; |
| 2624 | if( iVers>=0 ){ |
| 2625 | struct AnnVers *p = ann.aVers+iVers; |
| 2626 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%.10s %s", |
| 2627 | fileVers ? p->zFUuid : p->zMUuid, p->zDate); |
| 2628 | }else{ |
| 2629 | zPrefix[0] = 0; |
| 2630 | } |
| 2631 | fossil_print("%21s %4d: %.*s\n", zPrefix, i+1, n, z); |
| 2632 | } |
| 2633 | } |
| 2634 | |
| 2635 | /* |
| 2636 | ** COMMAND: test-looks-like-utf |
| 2637 |