Fossil SCM
Improved limit handling for annotation. The limit can now be expressed in compute-time seconds rather than versions analyzed, and defaults to "1.0s", which is enough to compute a complete annotation on most reasonable files.
Commit
517b9a5652649c95dbeae4c5f9f8621d6918d54c2f74c288c560ee8c3e38f86b
Parent
037e06b37015873…
2 files changed
+97
-61
+7
+97
-61
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -41,11 +41,11 @@ | ||
| 41 | 41 | #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ |
| 42 | 42 | #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ |
| 43 | 43 | #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */ |
| 44 | 44 | #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ |
| 45 | 45 | #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ |
| 46 | -#define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better, but slower side-by-side */ | |
| 46 | +#define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */ | |
| 47 | 47 | |
| 48 | 48 | /* |
| 49 | 49 | ** These error messages are shared in multiple locations. They are defined |
| 50 | 50 | ** here for consistency. |
| 51 | 51 | */ |
| @@ -2083,11 +2083,11 @@ | ||
| 2083 | 2083 | short int n; /* Number of bytes (omitting trailing \n) */ |
| 2084 | 2084 | short int iVers; /* Level at which tag was set */ |
| 2085 | 2085 | } *aOrig; |
| 2086 | 2086 | int nOrig; /* Number of elements in aOrig[] */ |
| 2087 | 2087 | int nVers; /* Number of versions analyzed */ |
| 2088 | - int bLimit; /* True if the iLimit was reached */ | |
| 2088 | + int bMoreToDo; /* True if the limit was reached */ | |
| 2089 | 2089 | struct AnnVers { |
| 2090 | 2090 | const char *zFUuid; /* File being analyzed */ |
| 2091 | 2091 | const char *zMUuid; /* Check-in containing the file */ |
| 2092 | 2092 | const char *zDate; /* Date of the check-in */ |
| 2093 | 2093 | const char *zBgColor; /* Suggested background color */ |
| @@ -2176,32 +2176,64 @@ | ||
| 2176 | 2176 | |
| 2177 | 2177 | /* Return no errors */ |
| 2178 | 2178 | return 0; |
| 2179 | 2179 | } |
| 2180 | 2180 | |
| 2181 | +/* Return the current time as milliseconds since the Julian epoch */ | |
| 2182 | +static sqlite3_int64 current_time_in_milliseconds(void){ | |
| 2183 | + static sqlite3_vfs *clockVfs = 0; | |
| 2184 | + sqlite3_int64 t; | |
| 2185 | + if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); | |
| 2186 | + if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ | |
| 2187 | + clockVfs->xCurrentTimeInt64(clockVfs, &t); | |
| 2188 | + }else{ | |
| 2189 | + double r; | |
| 2190 | + clockVfs->xCurrentTime(clockVfs, &r); | |
| 2191 | + t = (sqlite3_int64)(r*86400000.0); | |
| 2192 | + } | |
| 2193 | + return t; | |
| 2194 | +} | |
| 2181 | 2195 | |
| 2182 | 2196 | /* |
| 2183 | 2197 | ** Compute a complete annotation on a file. The file is identified by its |
| 2184 | 2198 | ** filename and check-in name (NULL for current check-in). |
| 2185 | 2199 | */ |
| 2186 | 2200 | static void annotate_file( |
| 2187 | - Annotator *p, /* The annotator */ | |
| 2188 | - const char *zFilename,/* The name of the file to be annotated */ | |
| 2189 | - const char *zRevision,/* Use the version of the file in this check-in */ | |
| 2190 | - int iLimit, /* Limit the number of levels if greater than zero */ | |
| 2191 | - u64 annFlags /* Flags to alter the annotation */ | |
| 2201 | + Annotator *p, /* The annotator */ | |
| 2202 | + const char *zFilename, /* The name of the file to be annotated */ | |
| 2203 | + const char *zRevision, /* Use the version of the file in this check-in */ | |
| 2204 | + const char *zLimit, /* Limit the number of versions analyzed */ | |
| 2205 | + u64 annFlags /* Flags to alter the annotation */ | |
| 2192 | 2206 | ){ |
| 2193 | - Blob toAnnotate; /* Text of the final (mid) version of the file */ | |
| 2194 | - Blob step; /* Text of previous revision */ | |
| 2195 | - Blob treename; /* FILENAME translated to canonical form */ | |
| 2196 | - int cid; /* Selected check-in ID */ | |
| 2197 | - int rid; /* Artifact ID of the file being annotated */ | |
| 2198 | - int fnid; /* Filename ID */ | |
| 2199 | - Stmt q; /* Query returning all ancestor versions */ | |
| 2200 | - int cnt = 0; /* Number of versions analyzed */ | |
| 2201 | - | |
| 2202 | - if( iLimit<=0 ) iLimit = 1000000000; /* A negative limit means no limit */ | |
| 2207 | + Blob toAnnotate; /* Text of the final (mid) version of the file */ | |
| 2208 | + Blob step; /* Text of previous revision */ | |
| 2209 | + Blob treename; /* FILENAME translated to canonical form */ | |
| 2210 | + int cid; /* Selected check-in ID */ | |
| 2211 | + int rid; /* Artifact ID of the file being annotated */ | |
| 2212 | + int fnid; /* Filename ID */ | |
| 2213 | + Stmt q; /* Query returning all ancestor versions */ | |
| 2214 | + int cnt = 0; /* Number of versions analyzed */ | |
| 2215 | + int iLimit; /* Maximum number of versions to analyze */ | |
| 2216 | + sqlite3_int64 mxTime; /* Halt at this time if not already complete */ | |
| 2217 | + | |
| 2218 | + if( zLimit ){ | |
| 2219 | + if( strcmp(zLimit,"none")==0 ){ | |
| 2220 | + iLimit = 0; | |
| 2221 | + mxTime = 0; | |
| 2222 | + }else if( sqlite3_strglob("*[0-9]s", zLimit)==0 ){ | |
| 2223 | + iLimit = 0; | |
| 2224 | + mxTime = current_time_in_milliseconds() + 1000.0*atof(zLimit); | |
| 2225 | + }else{ | |
| 2226 | + iLimit = atoi(zLimit); | |
| 2227 | + if( iLimit<=0 ) iLimit = 30; | |
| 2228 | + mxTime = 0; | |
| 2229 | + } | |
| 2230 | + }else{ | |
| 2231 | + /* Default limit is as much as we can do in 1.000 seconds */ | |
| 2232 | + iLimit = 0; | |
| 2233 | + mxTime = current_time_in_milliseconds()+1000; | |
| 2234 | + } | |
| 2203 | 2235 | db_begin_transaction(); |
| 2204 | 2236 | |
| 2205 | 2237 | /* Get the artificate ID for the check-in begin analyzed */ |
| 2206 | 2238 | if( zRevision ){ |
| 2207 | 2239 | cid = name_to_typed_rid(zRevision, "ci"); |
| @@ -2233,19 +2265,27 @@ | ||
| 2233 | 2265 | " AND mlink.mid!=mlink.pid" |
| 2234 | 2266 | " ORDER BY ancestor.generation;", |
| 2235 | 2267 | fnid |
| 2236 | 2268 | ); |
| 2237 | 2269 | |
| 2238 | - if( iLimit==0 ) iLimit = 1000000000; | |
| 2239 | - while( iLimit>cnt && db_step(&q)==SQLITE_ROW ){ | |
| 2270 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 2271 | + if( cnt>=3 ){ /* Process at least 3 rows before imposing limits */ | |
| 2272 | + if( (iLimit>0 && cnt>=iLimit) | |
| 2273 | + || (cnt>0 && mxTime>0 && current_time_in_milliseconds()>mxTime) | |
| 2274 | + ){ | |
| 2275 | + p->bMoreToDo = 1; | |
| 2276 | + break; | |
| 2277 | + } | |
| 2278 | + } | |
| 2240 | 2279 | rid = db_column_int(&q, 4); |
| 2241 | 2280 | if( cnt==0 ){ |
| 2242 | 2281 | if( !content_get(rid, &toAnnotate) ){ |
| 2243 | 2282 | fossil_fatal("unable to retrieve content of artifact #%d", rid); |
| 2244 | 2283 | } |
| 2245 | 2284 | blob_to_utf8_no_bom(&toAnnotate, 0); |
| 2246 | 2285 | annotation_start(p, &toAnnotate, annFlags); |
| 2286 | + p->bMoreToDo = 0; | |
| 2247 | 2287 | } |
| 2248 | 2288 | p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0])); |
| 2249 | 2289 | p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0)); |
| 2250 | 2290 | p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1)); |
| 2251 | 2291 | p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2)); |
| @@ -2257,11 +2297,10 @@ | ||
| 2257 | 2297 | annotation_step(p, &step, p->nVers-1, annFlags); |
| 2258 | 2298 | blob_reset(&step); |
| 2259 | 2299 | } |
| 2260 | 2300 | cnt++; |
| 2261 | 2301 | } |
| 2262 | - p->bLimit = iLimit==cnt; | |
| 2263 | 2302 | db_finalize(&q); |
| 2264 | 2303 | db_end_transaction(0); |
| 2265 | 2304 | } |
| 2266 | 2305 | |
| 2267 | 2306 | /* |
| @@ -2300,25 +2339,29 @@ | ||
| 2300 | 2339 | ** Query parameters: |
| 2301 | 2340 | ** |
| 2302 | 2341 | ** checkin=ID The manifest ID at which to start the annotation |
| 2303 | 2342 | ** filename=FILENAME The filename. |
| 2304 | 2343 | ** filevers=BOOLEAN Show file versions rather than check-in versions |
| 2305 | -** limit=N Limit the search depth to N ancestors | |
| 2344 | +** limit=LIMIT Limit the amount of analysis: | |
| 2345 | +** "none" No limit | |
| 2346 | +** "Xs" As much as can be computed in X seconds | |
| 2347 | +** "N" N versions | |
| 2306 | 2348 | ** log=BOOLEAN Show a log of versions analyzed |
| 2307 | 2349 | ** w=BOOLEAN Ignore whitespace |
| 2308 | 2350 | ** |
| 2309 | 2351 | */ |
| 2310 | 2352 | void annotation_page(void){ |
| 2311 | 2353 | int i; |
| 2312 | - int iLimit; /* Depth limit */ | |
| 2354 | + const char *zLimit; /* Depth limit */ | |
| 2313 | 2355 | u64 annFlags = DIFF_STRIP_EOLCR; |
| 2314 | 2356 | int showLog; /* True to display the log */ |
| 2315 | 2357 | int fileVers; /* Show file version instead of check-in versions */ |
| 2316 | 2358 | int ignoreWs; /* Ignore whitespace */ |
| 2317 | 2359 | const char *zFilename; /* Name of file to annotate */ |
| 2318 | 2360 | const char *zRevision; /* Name of check-in from which to start annotation */ |
| 2319 | 2361 | const char *zCI; /* The check-in containing zFilename */ |
| 2362 | + int szHash; /* Number of characters in %S display */ | |
| 2320 | 2363 | char *zLink; |
| 2321 | 2364 | Annotator ann; |
| 2322 | 2365 | HQuery url; |
| 2323 | 2366 | struct AnnVers *p; |
| 2324 | 2367 | unsigned clr1, clr2, clr; |
| @@ -2329,18 +2372,18 @@ | ||
| 2329 | 2372 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2330 | 2373 | if( exclude_spiders() ) return; |
| 2331 | 2374 | load_control(); |
| 2332 | 2375 | zFilename = P("filename"); |
| 2333 | 2376 | zRevision = PD("checkin",0); |
| 2334 | - iLimit = atoi(PD("limit","20")); | |
| 2377 | + zLimit = P("limit"); | |
| 2335 | 2378 | showLog = PB("log"); |
| 2336 | 2379 | fileVers = PB("filevers"); |
| 2337 | 2380 | ignoreWs = PB("w"); |
| 2338 | 2381 | if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS; |
| 2339 | 2382 | |
| 2340 | 2383 | /* compute the annotation */ |
| 2341 | - annotate_file(&ann, zFilename, zRevision, iLimit, annFlags); | |
| 2384 | + annotate_file(&ann, zFilename, zRevision, zLimit, annFlags); | |
| 2342 | 2385 | zCI = ann.aVers[0].zMUuid; |
| 2343 | 2386 | |
| 2344 | 2387 | /* generate the web page */ |
| 2345 | 2388 | style_header("Annotation For %h", zFilename); |
| 2346 | 2389 | if( bBlame ){ |
| @@ -2348,30 +2391,22 @@ | ||
| 2348 | 2391 | }else{ |
| 2349 | 2392 | url_initialize(&url, "annotate"); |
| 2350 | 2393 | } |
| 2351 | 2394 | url_add_parameter(&url, "checkin", P("checkin")); |
| 2352 | 2395 | url_add_parameter(&url, "filename", zFilename); |
| 2353 | - if( iLimit!=20 ){ | |
| 2354 | - url_add_parameter(&url, "limit", sqlite3_mprintf("%d", iLimit)); | |
| 2396 | + if( zLimit ){ | |
| 2397 | + url_add_parameter(&url, "limit", zLimit); | |
| 2355 | 2398 | } |
| 2356 | 2399 | url_add_parameter(&url, "w", ignoreWs ? "1" : "0"); |
| 2357 | 2400 | url_add_parameter(&url, "log", showLog ? "1" : "0"); |
| 2358 | 2401 | url_add_parameter(&url, "filevers", fileVers ? "1" : "0"); |
| 2359 | 2402 | style_submenu_checkbox("w", "Ignore Whitespace", 0, 0); |
| 2360 | 2403 | style_submenu_checkbox("log", "Log", 0, "toggle_annotation_log()"); |
| 2361 | 2404 | style_submenu_checkbox("filevers", "Link to Files", 0, 0); |
| 2362 | - if( ann.bLimit ){ | |
| 2363 | - char *z1, *z2; | |
| 2405 | + if( ann.bMoreToDo ){ | |
| 2364 | 2406 | style_submenu_element("All Ancestors", "%s", |
| 2365 | - url_render(&url, "limit", "-1", 0, 0)); | |
| 2366 | - z1 = sqlite3_mprintf("%d Ancestors", iLimit+20); | |
| 2367 | - z2 = sqlite3_mprintf("%d", iLimit+20); | |
| 2368 | - style_submenu_element(z1, "%s", url_render(&url, "limit", z2, 0, 0)); | |
| 2369 | - } | |
| 2370 | - if( iLimit>20 ){ | |
| 2371 | - style_submenu_element("20 Ancestors", "%s", | |
| 2372 | - url_render(&url, "limit", "20", 0, 0)); | |
| 2407 | + url_render(&url, "limit", "none", 0, 0)); | |
| 2373 | 2408 | } |
| 2374 | 2409 | if( skin_detail_boolean("white-foreground") ){ |
| 2375 | 2410 | clr1 = 0xa04040; |
| 2376 | 2411 | clr2 = 0x4059a0; |
| 2377 | 2412 | }else{ |
| @@ -2381,11 +2416,11 @@ | ||
| 2381 | 2416 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2382 | 2417 | clr = gradient_color(clr1, clr2, ann.nVers-1, i); |
| 2383 | 2418 | ann.aVers[i].zBgColor = mprintf("#%06x", clr); |
| 2384 | 2419 | } |
| 2385 | 2420 | |
| 2386 | - @ <div id="annotation_log" style='display:%s(showLog?"block":"none")'> | |
| 2421 | + @ <div id="annotation_log" style='display:%s(showLog?"block":"none");'> | |
| 2387 | 2422 | zLink = href("%R/finfo?name=%t&ci=%!S",zFilename,zCI); |
| 2388 | 2423 | @ <h2>Versions of %z(zLink)%h(zFilename)</a> analyzed:</h2> |
| 2389 | 2424 | @ <ol> |
| 2390 | 2425 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2391 | 2426 | @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate) |
| @@ -2402,28 +2437,28 @@ | ||
| 2402 | 2437 | @ var x = document.forms["f01"].elements["log"].checked |
| 2403 | 2438 | @ w.style.display = x ? "block" : "none"; |
| 2404 | 2439 | @ } |
| 2405 | 2440 | @ </script> |
| 2406 | 2441 | |
| 2407 | - if( !ann.bLimit ){ | |
| 2442 | + if( !ann.bMoreToDo ){ | |
| 2408 | 2443 | @ <h2>Origin for each line in |
| 2409 | 2444 | @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> |
| 2410 | 2445 | @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> |
| 2411 | - iLimit = ann.nVers+10; | |
| 2412 | 2446 | }else{ |
| 2413 | - @ <h2>Lines added by the %d(iLimit) most recent ancestors of | |
| 2447 | + @ <h2>Lines added by the %d(ann.nVers) most recent ancestors of | |
| 2414 | 2448 | @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> |
| 2415 | 2449 | @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> |
| 2416 | 2450 | } |
| 2417 | 2451 | @ <pre> |
| 2452 | + szHash = length_of_S_display(); | |
| 2418 | 2453 | for(i=0; i<ann.nOrig; i++){ |
| 2419 | 2454 | int iVers = ann.aOrig[i].iVers; |
| 2420 | 2455 | char *z = (char*)ann.aOrig[i].z; |
| 2421 | 2456 | int n = ann.aOrig[i].n; |
| 2422 | 2457 | char zPrefix[300]; |
| 2423 | 2458 | z[n] = 0; |
| 2424 | - if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; | |
| 2459 | + if( iVers<0 && !ann.bMoreToDo ) iVers = ann.nVers-1; | |
| 2425 | 2460 | |
| 2426 | 2461 | if( bBlame ){ |
| 2427 | 2462 | if( iVers>=0 ){ |
| 2428 | 2463 | struct AnnVers *p = ann.aVers+iVers; |
| 2429 | 2464 | const char *zUuid = fileVers ? p->zFUuid : p->zMUuid; |
| @@ -2432,11 +2467,11 @@ | ||
| 2432 | 2467 | "<span style='background-color:%s'>" |
| 2433 | 2468 | "%s%.10s</a> %s</span> %13.13s:", |
| 2434 | 2469 | p->zBgColor, zLink, zUuid, p->zDate, p->zUser); |
| 2435 | 2470 | fossil_free(zLink); |
| 2436 | 2471 | }else{ |
| 2437 | - sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", ""); | |
| 2472 | + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%*s", szHash+28, ""); | |
| 2438 | 2473 | } |
| 2439 | 2474 | }else{ |
| 2440 | 2475 | if( iVers>=0 ){ |
| 2441 | 2476 | struct AnnVers *p = ann.aVers+iVers; |
| 2442 | 2477 | const char *zUuid = fileVers ? p->zFUuid : p->zMUuid; |
| @@ -2445,11 +2480,11 @@ | ||
| 2445 | 2480 | "<span style='background-color:%s'>" |
| 2446 | 2481 | "%s%.10s</a> %s</span> %4d:", |
| 2447 | 2482 | p->zBgColor, zLink, zUuid, p->zDate, i+1); |
| 2448 | 2483 | fossil_free(zLink); |
| 2449 | 2484 | }else{ |
| 2450 | - sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1); | |
| 2485 | + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%*s%4d:",szHash+14,"",i+1); | |
| 2451 | 2486 | } |
| 2452 | 2487 | } |
| 2453 | 2488 | @ %s(zPrefix) %h(z) |
| 2454 | 2489 | |
| 2455 | 2490 | } |
| @@ -2473,33 +2508,33 @@ | ||
| 2473 | 2508 | ** Options: |
| 2474 | 2509 | ** --filevers Show file version numbers rather than |
| 2475 | 2510 | ** check-in versions |
| 2476 | 2511 | ** -r|--revision VERSION The specific check-in containing the file |
| 2477 | 2512 | ** -l|--log List all versions analyzed |
| 2478 | -** -n|--limit N Only look backwards in time by N versions | |
| 2513 | +** -n|--limit LIMIT Limit the amount of analysis: | |
| 2514 | +** N Up to N versions | |
| 2515 | +** Xs As much as possible in X seconds | |
| 2516 | +** none No limit | |
| 2479 | 2517 | ** -w|--ignore-all-space Ignore white space when comparing lines |
| 2480 | 2518 | ** -Z|--ignore-trailing-space Ignore whitespace at line end |
| 2481 | 2519 | ** |
| 2482 | 2520 | ** See also: info, finfo, timeline |
| 2483 | 2521 | */ |
| 2484 | 2522 | void annotate_cmd(void){ |
| 2485 | 2523 | const char *zRevision; /* Revision name, or NULL for current check-in */ |
| 2486 | - Annotator ann; /* The annotation of the file */ | |
| 2487 | - int i; /* Loop counter */ | |
| 2488 | - const char *zLimit; /* The value to the -n|--limit option */ | |
| 2489 | - int iLimit; /* How far back in time to look */ | |
| 2490 | - int showLog; /* True to show the log */ | |
| 2491 | - int fileVers; /* Show file version instead of check-in versions */ | |
| 2492 | - u64 annFlags = 0; /* Flags to control annotation properties */ | |
| 2493 | - int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */ | |
| 2524 | + Annotator ann; /* The annotation of the file */ | |
| 2525 | + int i; /* Loop counter */ | |
| 2526 | + const char *zLimit; /* The value to the -n|--limit option */ | |
| 2527 | + int showLog; /* True to show the log */ | |
| 2528 | + int fileVers; /* Show file version instead of check-in versions */ | |
| 2529 | + u64 annFlags = 0; /* Flags to control annotation properties */ | |
| 2530 | + int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */ | |
| 2531 | + int szHash; /* Display size of a version hash */ | |
| 2494 | 2532 | |
| 2495 | 2533 | bBlame = g.argv[1][0]!='a'; |
| 2496 | 2534 | zRevision = find_option("r","revision",1); |
| 2497 | 2535 | zLimit = find_option("limit","n",1); |
| 2498 | - if( zLimit==0 || zLimit[0]==0 ) zLimit = "-1"; | |
| 2499 | - iLimit = atoi(zLimit); | |
| 2500 | - if( iLimit<=0 ) iLimit = 1000000000; | |
| 2501 | 2536 | showLog = find_option("log","l",0)!=0; |
| 2502 | 2537 | if( find_option("ignore-trailing-space","Z",0)!=0 ){ |
| 2503 | 2538 | annFlags = DIFF_IGNORE_EOLWS; |
| 2504 | 2539 | } |
| 2505 | 2540 | if( find_option("ignore-all-space","w",0)!=0 ){ |
| @@ -2514,39 +2549,40 @@ | ||
| 2514 | 2549 | if( g.argc<3 ) { |
| 2515 | 2550 | usage("FILENAME"); |
| 2516 | 2551 | } |
| 2517 | 2552 | |
| 2518 | 2553 | annFlags |= DIFF_STRIP_EOLCR; |
| 2519 | - annotate_file(&ann, g.argv[2], zRevision, iLimit, annFlags); | |
| 2554 | + annotate_file(&ann, g.argv[2], zRevision, zLimit, annFlags); | |
| 2520 | 2555 | if( showLog ){ |
| 2521 | 2556 | struct AnnVers *p; |
| 2522 | 2557 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2523 | 2558 | fossil_print("version %3d: %s %S file %S\n", |
| 2524 | 2559 | i+1, p->zDate, p->zMUuid, p->zFUuid); |
| 2525 | 2560 | } |
| 2526 | 2561 | fossil_print("---------------------------------------------------\n"); |
| 2527 | 2562 | } |
| 2563 | + szHash = length_of_S_display(); | |
| 2528 | 2564 | for(i=0; i<ann.nOrig; i++){ |
| 2529 | 2565 | int iVers = ann.aOrig[i].iVers; |
| 2530 | 2566 | char *z = (char*)ann.aOrig[i].z; |
| 2531 | 2567 | int n = ann.aOrig[i].n; |
| 2532 | 2568 | struct AnnVers *p; |
| 2533 | - if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; | |
| 2534 | - p = ann.aVers + iVers; | |
| 2569 | + if( iVers<0 && !ann.bMoreToDo ) iVers = ann.nVers-1; | |
| 2535 | 2570 | if( bBlame ){ |
| 2536 | 2571 | if( iVers>=0 ){ |
| 2572 | + p = ann.aVers + iVers; | |
| 2537 | 2573 | fossil_print("%S %s %13.13s: %.*s\n", |
| 2538 | 2574 | fileVers ? p->zFUuid : p->zMUuid, p->zDate, p->zUser, n, z); |
| 2539 | 2575 | }else{ |
| 2540 | - fossil_print("%35s %.*s\n", "", n, z); | |
| 2576 | + fossil_print("%*s %.*s\n", szHash+26, "", n, z); | |
| 2541 | 2577 | } |
| 2542 | 2578 | }else{ |
| 2543 | 2579 | if( iVers>=0 ){ |
| 2580 | + p = ann.aVers + iVers; | |
| 2544 | 2581 | fossil_print("%S %s %5d: %.*s\n", |
| 2545 | 2582 | fileVers ? p->zFUuid : p->zMUuid, p->zDate, i+1, n, z); |
| 2546 | 2583 | }else{ |
| 2547 | - fossil_print("%21s %5d: %.*s\n", | |
| 2548 | - "", i+1, n, z); | |
| 2584 | + fossil_print("%*s %5d: %.*s\n", szHash+11, "", i+1, n, z); | |
| 2549 | 2585 | } |
| 2550 | 2586 | } |
| 2551 | 2587 | } |
| 2552 | 2588 | } |
| 2553 | 2589 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -41,11 +41,11 @@ | |
| 41 | #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ |
| 42 | #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ |
| 43 | #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */ |
| 44 | #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ |
| 45 | #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ |
| 46 | #define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better, but slower side-by-side */ |
| 47 | |
| 48 | /* |
| 49 | ** These error messages are shared in multiple locations. They are defined |
| 50 | ** here for consistency. |
| 51 | */ |
| @@ -2083,11 +2083,11 @@ | |
| 2083 | short int n; /* Number of bytes (omitting trailing \n) */ |
| 2084 | short int iVers; /* Level at which tag was set */ |
| 2085 | } *aOrig; |
| 2086 | int nOrig; /* Number of elements in aOrig[] */ |
| 2087 | int nVers; /* Number of versions analyzed */ |
| 2088 | int bLimit; /* True if the iLimit was reached */ |
| 2089 | struct AnnVers { |
| 2090 | const char *zFUuid; /* File being analyzed */ |
| 2091 | const char *zMUuid; /* Check-in containing the file */ |
| 2092 | const char *zDate; /* Date of the check-in */ |
| 2093 | const char *zBgColor; /* Suggested background color */ |
| @@ -2176,32 +2176,64 @@ | |
| 2176 | |
| 2177 | /* Return no errors */ |
| 2178 | return 0; |
| 2179 | } |
| 2180 | |
| 2181 | |
| 2182 | /* |
| 2183 | ** Compute a complete annotation on a file. The file is identified by its |
| 2184 | ** filename and check-in name (NULL for current check-in). |
| 2185 | */ |
| 2186 | static void annotate_file( |
| 2187 | Annotator *p, /* The annotator */ |
| 2188 | const char *zFilename,/* The name of the file to be annotated */ |
| 2189 | const char *zRevision,/* Use the version of the file in this check-in */ |
| 2190 | int iLimit, /* Limit the number of levels if greater than zero */ |
| 2191 | u64 annFlags /* Flags to alter the annotation */ |
| 2192 | ){ |
| 2193 | Blob toAnnotate; /* Text of the final (mid) version of the file */ |
| 2194 | Blob step; /* Text of previous revision */ |
| 2195 | Blob treename; /* FILENAME translated to canonical form */ |
| 2196 | int cid; /* Selected check-in ID */ |
| 2197 | int rid; /* Artifact ID of the file being annotated */ |
| 2198 | int fnid; /* Filename ID */ |
| 2199 | Stmt q; /* Query returning all ancestor versions */ |
| 2200 | int cnt = 0; /* Number of versions analyzed */ |
| 2201 | |
| 2202 | if( iLimit<=0 ) iLimit = 1000000000; /* A negative limit means no limit */ |
| 2203 | db_begin_transaction(); |
| 2204 | |
| 2205 | /* Get the artificate ID for the check-in begin analyzed */ |
| 2206 | if( zRevision ){ |
| 2207 | cid = name_to_typed_rid(zRevision, "ci"); |
| @@ -2233,19 +2265,27 @@ | |
| 2233 | " AND mlink.mid!=mlink.pid" |
| 2234 | " ORDER BY ancestor.generation;", |
| 2235 | fnid |
| 2236 | ); |
| 2237 | |
| 2238 | if( iLimit==0 ) iLimit = 1000000000; |
| 2239 | while( iLimit>cnt && db_step(&q)==SQLITE_ROW ){ |
| 2240 | rid = db_column_int(&q, 4); |
| 2241 | if( cnt==0 ){ |
| 2242 | if( !content_get(rid, &toAnnotate) ){ |
| 2243 | fossil_fatal("unable to retrieve content of artifact #%d", rid); |
| 2244 | } |
| 2245 | blob_to_utf8_no_bom(&toAnnotate, 0); |
| 2246 | annotation_start(p, &toAnnotate, annFlags); |
| 2247 | } |
| 2248 | p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0])); |
| 2249 | p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0)); |
| 2250 | p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1)); |
| 2251 | p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2)); |
| @@ -2257,11 +2297,10 @@ | |
| 2257 | annotation_step(p, &step, p->nVers-1, annFlags); |
| 2258 | blob_reset(&step); |
| 2259 | } |
| 2260 | cnt++; |
| 2261 | } |
| 2262 | p->bLimit = iLimit==cnt; |
| 2263 | db_finalize(&q); |
| 2264 | db_end_transaction(0); |
| 2265 | } |
| 2266 | |
| 2267 | /* |
| @@ -2300,25 +2339,29 @@ | |
| 2300 | ** Query parameters: |
| 2301 | ** |
| 2302 | ** checkin=ID The manifest ID at which to start the annotation |
| 2303 | ** filename=FILENAME The filename. |
| 2304 | ** filevers=BOOLEAN Show file versions rather than check-in versions |
| 2305 | ** limit=N Limit the search depth to N ancestors |
| 2306 | ** log=BOOLEAN Show a log of versions analyzed |
| 2307 | ** w=BOOLEAN Ignore whitespace |
| 2308 | ** |
| 2309 | */ |
| 2310 | void annotation_page(void){ |
| 2311 | int i; |
| 2312 | int iLimit; /* Depth limit */ |
| 2313 | u64 annFlags = DIFF_STRIP_EOLCR; |
| 2314 | int showLog; /* True to display the log */ |
| 2315 | int fileVers; /* Show file version instead of check-in versions */ |
| 2316 | int ignoreWs; /* Ignore whitespace */ |
| 2317 | const char *zFilename; /* Name of file to annotate */ |
| 2318 | const char *zRevision; /* Name of check-in from which to start annotation */ |
| 2319 | const char *zCI; /* The check-in containing zFilename */ |
| 2320 | char *zLink; |
| 2321 | Annotator ann; |
| 2322 | HQuery url; |
| 2323 | struct AnnVers *p; |
| 2324 | unsigned clr1, clr2, clr; |
| @@ -2329,18 +2372,18 @@ | |
| 2329 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2330 | if( exclude_spiders() ) return; |
| 2331 | load_control(); |
| 2332 | zFilename = P("filename"); |
| 2333 | zRevision = PD("checkin",0); |
| 2334 | iLimit = atoi(PD("limit","20")); |
| 2335 | showLog = PB("log"); |
| 2336 | fileVers = PB("filevers"); |
| 2337 | ignoreWs = PB("w"); |
| 2338 | if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS; |
| 2339 | |
| 2340 | /* compute the annotation */ |
| 2341 | annotate_file(&ann, zFilename, zRevision, iLimit, annFlags); |
| 2342 | zCI = ann.aVers[0].zMUuid; |
| 2343 | |
| 2344 | /* generate the web page */ |
| 2345 | style_header("Annotation For %h", zFilename); |
| 2346 | if( bBlame ){ |
| @@ -2348,30 +2391,22 @@ | |
| 2348 | }else{ |
| 2349 | url_initialize(&url, "annotate"); |
| 2350 | } |
| 2351 | url_add_parameter(&url, "checkin", P("checkin")); |
| 2352 | url_add_parameter(&url, "filename", zFilename); |
| 2353 | if( iLimit!=20 ){ |
| 2354 | url_add_parameter(&url, "limit", sqlite3_mprintf("%d", iLimit)); |
| 2355 | } |
| 2356 | url_add_parameter(&url, "w", ignoreWs ? "1" : "0"); |
| 2357 | url_add_parameter(&url, "log", showLog ? "1" : "0"); |
| 2358 | url_add_parameter(&url, "filevers", fileVers ? "1" : "0"); |
| 2359 | style_submenu_checkbox("w", "Ignore Whitespace", 0, 0); |
| 2360 | style_submenu_checkbox("log", "Log", 0, "toggle_annotation_log()"); |
| 2361 | style_submenu_checkbox("filevers", "Link to Files", 0, 0); |
| 2362 | if( ann.bLimit ){ |
| 2363 | char *z1, *z2; |
| 2364 | style_submenu_element("All Ancestors", "%s", |
| 2365 | url_render(&url, "limit", "-1", 0, 0)); |
| 2366 | z1 = sqlite3_mprintf("%d Ancestors", iLimit+20); |
| 2367 | z2 = sqlite3_mprintf("%d", iLimit+20); |
| 2368 | style_submenu_element(z1, "%s", url_render(&url, "limit", z2, 0, 0)); |
| 2369 | } |
| 2370 | if( iLimit>20 ){ |
| 2371 | style_submenu_element("20 Ancestors", "%s", |
| 2372 | url_render(&url, "limit", "20", 0, 0)); |
| 2373 | } |
| 2374 | if( skin_detail_boolean("white-foreground") ){ |
| 2375 | clr1 = 0xa04040; |
| 2376 | clr2 = 0x4059a0; |
| 2377 | }else{ |
| @@ -2381,11 +2416,11 @@ | |
| 2381 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2382 | clr = gradient_color(clr1, clr2, ann.nVers-1, i); |
| 2383 | ann.aVers[i].zBgColor = mprintf("#%06x", clr); |
| 2384 | } |
| 2385 | |
| 2386 | @ <div id="annotation_log" style='display:%s(showLog?"block":"none")'> |
| 2387 | zLink = href("%R/finfo?name=%t&ci=%!S",zFilename,zCI); |
| 2388 | @ <h2>Versions of %z(zLink)%h(zFilename)</a> analyzed:</h2> |
| 2389 | @ <ol> |
| 2390 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2391 | @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate) |
| @@ -2402,28 +2437,28 @@ | |
| 2402 | @ var x = document.forms["f01"].elements["log"].checked |
| 2403 | @ w.style.display = x ? "block" : "none"; |
| 2404 | @ } |
| 2405 | @ </script> |
| 2406 | |
| 2407 | if( !ann.bLimit ){ |
| 2408 | @ <h2>Origin for each line in |
| 2409 | @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> |
| 2410 | @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> |
| 2411 | iLimit = ann.nVers+10; |
| 2412 | }else{ |
| 2413 | @ <h2>Lines added by the %d(iLimit) most recent ancestors of |
| 2414 | @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> |
| 2415 | @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> |
| 2416 | } |
| 2417 | @ <pre> |
| 2418 | for(i=0; i<ann.nOrig; i++){ |
| 2419 | int iVers = ann.aOrig[i].iVers; |
| 2420 | char *z = (char*)ann.aOrig[i].z; |
| 2421 | int n = ann.aOrig[i].n; |
| 2422 | char zPrefix[300]; |
| 2423 | z[n] = 0; |
| 2424 | if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; |
| 2425 | |
| 2426 | if( bBlame ){ |
| 2427 | if( iVers>=0 ){ |
| 2428 | struct AnnVers *p = ann.aVers+iVers; |
| 2429 | const char *zUuid = fileVers ? p->zFUuid : p->zMUuid; |
| @@ -2432,11 +2467,11 @@ | |
| 2432 | "<span style='background-color:%s'>" |
| 2433 | "%s%.10s</a> %s</span> %13.13s:", |
| 2434 | p->zBgColor, zLink, zUuid, p->zDate, p->zUser); |
| 2435 | fossil_free(zLink); |
| 2436 | }else{ |
| 2437 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", ""); |
| 2438 | } |
| 2439 | }else{ |
| 2440 | if( iVers>=0 ){ |
| 2441 | struct AnnVers *p = ann.aVers+iVers; |
| 2442 | const char *zUuid = fileVers ? p->zFUuid : p->zMUuid; |
| @@ -2445,11 +2480,11 @@ | |
| 2445 | "<span style='background-color:%s'>" |
| 2446 | "%s%.10s</a> %s</span> %4d:", |
| 2447 | p->zBgColor, zLink, zUuid, p->zDate, i+1); |
| 2448 | fossil_free(zLink); |
| 2449 | }else{ |
| 2450 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1); |
| 2451 | } |
| 2452 | } |
| 2453 | @ %s(zPrefix) %h(z) |
| 2454 | |
| 2455 | } |
| @@ -2473,33 +2508,33 @@ | |
| 2473 | ** Options: |
| 2474 | ** --filevers Show file version numbers rather than |
| 2475 | ** check-in versions |
| 2476 | ** -r|--revision VERSION The specific check-in containing the file |
| 2477 | ** -l|--log List all versions analyzed |
| 2478 | ** -n|--limit N Only look backwards in time by N versions |
| 2479 | ** -w|--ignore-all-space Ignore white space when comparing lines |
| 2480 | ** -Z|--ignore-trailing-space Ignore whitespace at line end |
| 2481 | ** |
| 2482 | ** See also: info, finfo, timeline |
| 2483 | */ |
| 2484 | void annotate_cmd(void){ |
| 2485 | const char *zRevision; /* Revision name, or NULL for current check-in */ |
| 2486 | Annotator ann; /* The annotation of the file */ |
| 2487 | int i; /* Loop counter */ |
| 2488 | const char *zLimit; /* The value to the -n|--limit option */ |
| 2489 | int iLimit; /* How far back in time to look */ |
| 2490 | int showLog; /* True to show the log */ |
| 2491 | int fileVers; /* Show file version instead of check-in versions */ |
| 2492 | u64 annFlags = 0; /* Flags to control annotation properties */ |
| 2493 | int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */ |
| 2494 | |
| 2495 | bBlame = g.argv[1][0]!='a'; |
| 2496 | zRevision = find_option("r","revision",1); |
| 2497 | zLimit = find_option("limit","n",1); |
| 2498 | if( zLimit==0 || zLimit[0]==0 ) zLimit = "-1"; |
| 2499 | iLimit = atoi(zLimit); |
| 2500 | if( iLimit<=0 ) iLimit = 1000000000; |
| 2501 | showLog = find_option("log","l",0)!=0; |
| 2502 | if( find_option("ignore-trailing-space","Z",0)!=0 ){ |
| 2503 | annFlags = DIFF_IGNORE_EOLWS; |
| 2504 | } |
| 2505 | if( find_option("ignore-all-space","w",0)!=0 ){ |
| @@ -2514,39 +2549,40 @@ | |
| 2514 | if( g.argc<3 ) { |
| 2515 | usage("FILENAME"); |
| 2516 | } |
| 2517 | |
| 2518 | annFlags |= DIFF_STRIP_EOLCR; |
| 2519 | annotate_file(&ann, g.argv[2], zRevision, iLimit, annFlags); |
| 2520 | if( showLog ){ |
| 2521 | struct AnnVers *p; |
| 2522 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2523 | fossil_print("version %3d: %s %S file %S\n", |
| 2524 | i+1, p->zDate, p->zMUuid, p->zFUuid); |
| 2525 | } |
| 2526 | fossil_print("---------------------------------------------------\n"); |
| 2527 | } |
| 2528 | for(i=0; i<ann.nOrig; i++){ |
| 2529 | int iVers = ann.aOrig[i].iVers; |
| 2530 | char *z = (char*)ann.aOrig[i].z; |
| 2531 | int n = ann.aOrig[i].n; |
| 2532 | struct AnnVers *p; |
| 2533 | if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; |
| 2534 | p = ann.aVers + iVers; |
| 2535 | if( bBlame ){ |
| 2536 | if( iVers>=0 ){ |
| 2537 | fossil_print("%S %s %13.13s: %.*s\n", |
| 2538 | fileVers ? p->zFUuid : p->zMUuid, p->zDate, p->zUser, n, z); |
| 2539 | }else{ |
| 2540 | fossil_print("%35s %.*s\n", "", n, z); |
| 2541 | } |
| 2542 | }else{ |
| 2543 | if( iVers>=0 ){ |
| 2544 | fossil_print("%S %s %5d: %.*s\n", |
| 2545 | fileVers ? p->zFUuid : p->zMUuid, p->zDate, i+1, n, z); |
| 2546 | }else{ |
| 2547 | fossil_print("%21s %5d: %.*s\n", |
| 2548 | "", i+1, n, z); |
| 2549 | } |
| 2550 | } |
| 2551 | } |
| 2552 | } |
| 2553 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -41,11 +41,11 @@ | |
| 41 | #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ |
| 42 | #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ |
| 43 | #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */ |
| 44 | #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ |
| 45 | #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ |
| 46 | #define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */ |
| 47 | |
| 48 | /* |
| 49 | ** These error messages are shared in multiple locations. They are defined |
| 50 | ** here for consistency. |
| 51 | */ |
| @@ -2083,11 +2083,11 @@ | |
| 2083 | short int n; /* Number of bytes (omitting trailing \n) */ |
| 2084 | short int iVers; /* Level at which tag was set */ |
| 2085 | } *aOrig; |
| 2086 | int nOrig; /* Number of elements in aOrig[] */ |
| 2087 | int nVers; /* Number of versions analyzed */ |
| 2088 | int bMoreToDo; /* True if the limit was reached */ |
| 2089 | struct AnnVers { |
| 2090 | const char *zFUuid; /* File being analyzed */ |
| 2091 | const char *zMUuid; /* Check-in containing the file */ |
| 2092 | const char *zDate; /* Date of the check-in */ |
| 2093 | const char *zBgColor; /* Suggested background color */ |
| @@ -2176,32 +2176,64 @@ | |
| 2176 | |
| 2177 | /* Return no errors */ |
| 2178 | return 0; |
| 2179 | } |
| 2180 | |
| 2181 | /* Return the current time as milliseconds since the Julian epoch */ |
| 2182 | static sqlite3_int64 current_time_in_milliseconds(void){ |
| 2183 | static sqlite3_vfs *clockVfs = 0; |
| 2184 | sqlite3_int64 t; |
| 2185 | if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); |
| 2186 | if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ |
| 2187 | clockVfs->xCurrentTimeInt64(clockVfs, &t); |
| 2188 | }else{ |
| 2189 | double r; |
| 2190 | clockVfs->xCurrentTime(clockVfs, &r); |
| 2191 | t = (sqlite3_int64)(r*86400000.0); |
| 2192 | } |
| 2193 | return t; |
| 2194 | } |
| 2195 | |
| 2196 | /* |
| 2197 | ** Compute a complete annotation on a file. The file is identified by its |
| 2198 | ** filename and check-in name (NULL for current check-in). |
| 2199 | */ |
| 2200 | static void annotate_file( |
| 2201 | Annotator *p, /* The annotator */ |
| 2202 | const char *zFilename, /* The name of the file to be annotated */ |
| 2203 | const char *zRevision, /* Use the version of the file in this check-in */ |
| 2204 | const char *zLimit, /* Limit the number of versions analyzed */ |
| 2205 | u64 annFlags /* Flags to alter the annotation */ |
| 2206 | ){ |
| 2207 | Blob toAnnotate; /* Text of the final (mid) version of the file */ |
| 2208 | Blob step; /* Text of previous revision */ |
| 2209 | Blob treename; /* FILENAME translated to canonical form */ |
| 2210 | int cid; /* Selected check-in ID */ |
| 2211 | int rid; /* Artifact ID of the file being annotated */ |
| 2212 | int fnid; /* Filename ID */ |
| 2213 | Stmt q; /* Query returning all ancestor versions */ |
| 2214 | int cnt = 0; /* Number of versions analyzed */ |
| 2215 | int iLimit; /* Maximum number of versions to analyze */ |
| 2216 | sqlite3_int64 mxTime; /* Halt at this time if not already complete */ |
| 2217 | |
| 2218 | if( zLimit ){ |
| 2219 | if( strcmp(zLimit,"none")==0 ){ |
| 2220 | iLimit = 0; |
| 2221 | mxTime = 0; |
| 2222 | }else if( sqlite3_strglob("*[0-9]s", zLimit)==0 ){ |
| 2223 | iLimit = 0; |
| 2224 | mxTime = current_time_in_milliseconds() + 1000.0*atof(zLimit); |
| 2225 | }else{ |
| 2226 | iLimit = atoi(zLimit); |
| 2227 | if( iLimit<=0 ) iLimit = 30; |
| 2228 | mxTime = 0; |
| 2229 | } |
| 2230 | }else{ |
| 2231 | /* Default limit is as much as we can do in 1.000 seconds */ |
| 2232 | iLimit = 0; |
| 2233 | mxTime = current_time_in_milliseconds()+1000; |
| 2234 | } |
| 2235 | db_begin_transaction(); |
| 2236 | |
| 2237 | /* Get the artificate ID for the check-in begin analyzed */ |
| 2238 | if( zRevision ){ |
| 2239 | cid = name_to_typed_rid(zRevision, "ci"); |
| @@ -2233,19 +2265,27 @@ | |
| 2265 | " AND mlink.mid!=mlink.pid" |
| 2266 | " ORDER BY ancestor.generation;", |
| 2267 | fnid |
| 2268 | ); |
| 2269 | |
| 2270 | while( db_step(&q)==SQLITE_ROW ){ |
| 2271 | if( cnt>=3 ){ /* Process at least 3 rows before imposing limits */ |
| 2272 | if( (iLimit>0 && cnt>=iLimit) |
| 2273 | || (cnt>0 && mxTime>0 && current_time_in_milliseconds()>mxTime) |
| 2274 | ){ |
| 2275 | p->bMoreToDo = 1; |
| 2276 | break; |
| 2277 | } |
| 2278 | } |
| 2279 | rid = db_column_int(&q, 4); |
| 2280 | if( cnt==0 ){ |
| 2281 | if( !content_get(rid, &toAnnotate) ){ |
| 2282 | fossil_fatal("unable to retrieve content of artifact #%d", rid); |
| 2283 | } |
| 2284 | blob_to_utf8_no_bom(&toAnnotate, 0); |
| 2285 | annotation_start(p, &toAnnotate, annFlags); |
| 2286 | p->bMoreToDo = 0; |
| 2287 | } |
| 2288 | p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0])); |
| 2289 | p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0)); |
| 2290 | p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1)); |
| 2291 | p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2)); |
| @@ -2257,11 +2297,10 @@ | |
| 2297 | annotation_step(p, &step, p->nVers-1, annFlags); |
| 2298 | blob_reset(&step); |
| 2299 | } |
| 2300 | cnt++; |
| 2301 | } |
| 2302 | db_finalize(&q); |
| 2303 | db_end_transaction(0); |
| 2304 | } |
| 2305 | |
| 2306 | /* |
| @@ -2300,25 +2339,29 @@ | |
| 2339 | ** Query parameters: |
| 2340 | ** |
| 2341 | ** checkin=ID The manifest ID at which to start the annotation |
| 2342 | ** filename=FILENAME The filename. |
| 2343 | ** filevers=BOOLEAN Show file versions rather than check-in versions |
| 2344 | ** limit=LIMIT Limit the amount of analysis: |
| 2345 | ** "none" No limit |
| 2346 | ** "Xs" As much as can be computed in X seconds |
| 2347 | ** "N" N versions |
| 2348 | ** log=BOOLEAN Show a log of versions analyzed |
| 2349 | ** w=BOOLEAN Ignore whitespace |
| 2350 | ** |
| 2351 | */ |
| 2352 | void annotation_page(void){ |
| 2353 | int i; |
| 2354 | const char *zLimit; /* Depth limit */ |
| 2355 | u64 annFlags = DIFF_STRIP_EOLCR; |
| 2356 | int showLog; /* True to display the log */ |
| 2357 | int fileVers; /* Show file version instead of check-in versions */ |
| 2358 | int ignoreWs; /* Ignore whitespace */ |
| 2359 | const char *zFilename; /* Name of file to annotate */ |
| 2360 | const char *zRevision; /* Name of check-in from which to start annotation */ |
| 2361 | const char *zCI; /* The check-in containing zFilename */ |
| 2362 | int szHash; /* Number of characters in %S display */ |
| 2363 | char *zLink; |
| 2364 | Annotator ann; |
| 2365 | HQuery url; |
| 2366 | struct AnnVers *p; |
| 2367 | unsigned clr1, clr2, clr; |
| @@ -2329,18 +2372,18 @@ | |
| 2372 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2373 | if( exclude_spiders() ) return; |
| 2374 | load_control(); |
| 2375 | zFilename = P("filename"); |
| 2376 | zRevision = PD("checkin",0); |
| 2377 | zLimit = P("limit"); |
| 2378 | showLog = PB("log"); |
| 2379 | fileVers = PB("filevers"); |
| 2380 | ignoreWs = PB("w"); |
| 2381 | if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS; |
| 2382 | |
| 2383 | /* compute the annotation */ |
| 2384 | annotate_file(&ann, zFilename, zRevision, zLimit, annFlags); |
| 2385 | zCI = ann.aVers[0].zMUuid; |
| 2386 | |
| 2387 | /* generate the web page */ |
| 2388 | style_header("Annotation For %h", zFilename); |
| 2389 | if( bBlame ){ |
| @@ -2348,30 +2391,22 @@ | |
| 2391 | }else{ |
| 2392 | url_initialize(&url, "annotate"); |
| 2393 | } |
| 2394 | url_add_parameter(&url, "checkin", P("checkin")); |
| 2395 | url_add_parameter(&url, "filename", zFilename); |
| 2396 | if( zLimit ){ |
| 2397 | url_add_parameter(&url, "limit", zLimit); |
| 2398 | } |
| 2399 | url_add_parameter(&url, "w", ignoreWs ? "1" : "0"); |
| 2400 | url_add_parameter(&url, "log", showLog ? "1" : "0"); |
| 2401 | url_add_parameter(&url, "filevers", fileVers ? "1" : "0"); |
| 2402 | style_submenu_checkbox("w", "Ignore Whitespace", 0, 0); |
| 2403 | style_submenu_checkbox("log", "Log", 0, "toggle_annotation_log()"); |
| 2404 | style_submenu_checkbox("filevers", "Link to Files", 0, 0); |
| 2405 | if( ann.bMoreToDo ){ |
| 2406 | style_submenu_element("All Ancestors", "%s", |
| 2407 | url_render(&url, "limit", "none", 0, 0)); |
| 2408 | } |
| 2409 | if( skin_detail_boolean("white-foreground") ){ |
| 2410 | clr1 = 0xa04040; |
| 2411 | clr2 = 0x4059a0; |
| 2412 | }else{ |
| @@ -2381,11 +2416,11 @@ | |
| 2416 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2417 | clr = gradient_color(clr1, clr2, ann.nVers-1, i); |
| 2418 | ann.aVers[i].zBgColor = mprintf("#%06x", clr); |
| 2419 | } |
| 2420 | |
| 2421 | @ <div id="annotation_log" style='display:%s(showLog?"block":"none");'> |
| 2422 | zLink = href("%R/finfo?name=%t&ci=%!S",zFilename,zCI); |
| 2423 | @ <h2>Versions of %z(zLink)%h(zFilename)</a> analyzed:</h2> |
| 2424 | @ <ol> |
| 2425 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2426 | @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate) |
| @@ -2402,28 +2437,28 @@ | |
| 2437 | @ var x = document.forms["f01"].elements["log"].checked |
| 2438 | @ w.style.display = x ? "block" : "none"; |
| 2439 | @ } |
| 2440 | @ </script> |
| 2441 | |
| 2442 | if( !ann.bMoreToDo ){ |
| 2443 | @ <h2>Origin for each line in |
| 2444 | @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> |
| 2445 | @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> |
| 2446 | }else{ |
| 2447 | @ <h2>Lines added by the %d(ann.nVers) most recent ancestors of |
| 2448 | @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> |
| 2449 | @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> |
| 2450 | } |
| 2451 | @ <pre> |
| 2452 | szHash = length_of_S_display(); |
| 2453 | for(i=0; i<ann.nOrig; i++){ |
| 2454 | int iVers = ann.aOrig[i].iVers; |
| 2455 | char *z = (char*)ann.aOrig[i].z; |
| 2456 | int n = ann.aOrig[i].n; |
| 2457 | char zPrefix[300]; |
| 2458 | z[n] = 0; |
| 2459 | if( iVers<0 && !ann.bMoreToDo ) iVers = ann.nVers-1; |
| 2460 | |
| 2461 | if( bBlame ){ |
| 2462 | if( iVers>=0 ){ |
| 2463 | struct AnnVers *p = ann.aVers+iVers; |
| 2464 | const char *zUuid = fileVers ? p->zFUuid : p->zMUuid; |
| @@ -2432,11 +2467,11 @@ | |
| 2467 | "<span style='background-color:%s'>" |
| 2468 | "%s%.10s</a> %s</span> %13.13s:", |
| 2469 | p->zBgColor, zLink, zUuid, p->zDate, p->zUser); |
| 2470 | fossil_free(zLink); |
| 2471 | }else{ |
| 2472 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%*s", szHash+28, ""); |
| 2473 | } |
| 2474 | }else{ |
| 2475 | if( iVers>=0 ){ |
| 2476 | struct AnnVers *p = ann.aVers+iVers; |
| 2477 | const char *zUuid = fileVers ? p->zFUuid : p->zMUuid; |
| @@ -2445,11 +2480,11 @@ | |
| 2480 | "<span style='background-color:%s'>" |
| 2481 | "%s%.10s</a> %s</span> %4d:", |
| 2482 | p->zBgColor, zLink, zUuid, p->zDate, i+1); |
| 2483 | fossil_free(zLink); |
| 2484 | }else{ |
| 2485 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%*s%4d:",szHash+14,"",i+1); |
| 2486 | } |
| 2487 | } |
| 2488 | @ %s(zPrefix) %h(z) |
| 2489 | |
| 2490 | } |
| @@ -2473,33 +2508,33 @@ | |
| 2508 | ** Options: |
| 2509 | ** --filevers Show file version numbers rather than |
| 2510 | ** check-in versions |
| 2511 | ** -r|--revision VERSION The specific check-in containing the file |
| 2512 | ** -l|--log List all versions analyzed |
| 2513 | ** -n|--limit LIMIT Limit the amount of analysis: |
| 2514 | ** N Up to N versions |
| 2515 | ** Xs As much as possible in X seconds |
| 2516 | ** none No limit |
| 2517 | ** -w|--ignore-all-space Ignore white space when comparing lines |
| 2518 | ** -Z|--ignore-trailing-space Ignore whitespace at line end |
| 2519 | ** |
| 2520 | ** See also: info, finfo, timeline |
| 2521 | */ |
| 2522 | void annotate_cmd(void){ |
| 2523 | const char *zRevision; /* Revision name, or NULL for current check-in */ |
| 2524 | Annotator ann; /* The annotation of the file */ |
| 2525 | int i; /* Loop counter */ |
| 2526 | const char *zLimit; /* The value to the -n|--limit option */ |
| 2527 | int showLog; /* True to show the log */ |
| 2528 | int fileVers; /* Show file version instead of check-in versions */ |
| 2529 | u64 annFlags = 0; /* Flags to control annotation properties */ |
| 2530 | int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */ |
| 2531 | int szHash; /* Display size of a version hash */ |
| 2532 | |
| 2533 | bBlame = g.argv[1][0]!='a'; |
| 2534 | zRevision = find_option("r","revision",1); |
| 2535 | zLimit = find_option("limit","n",1); |
| 2536 | showLog = find_option("log","l",0)!=0; |
| 2537 | if( find_option("ignore-trailing-space","Z",0)!=0 ){ |
| 2538 | annFlags = DIFF_IGNORE_EOLWS; |
| 2539 | } |
| 2540 | if( find_option("ignore-all-space","w",0)!=0 ){ |
| @@ -2514,39 +2549,40 @@ | |
| 2549 | if( g.argc<3 ) { |
| 2550 | usage("FILENAME"); |
| 2551 | } |
| 2552 | |
| 2553 | annFlags |= DIFF_STRIP_EOLCR; |
| 2554 | annotate_file(&ann, g.argv[2], zRevision, zLimit, annFlags); |
| 2555 | if( showLog ){ |
| 2556 | struct AnnVers *p; |
| 2557 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2558 | fossil_print("version %3d: %s %S file %S\n", |
| 2559 | i+1, p->zDate, p->zMUuid, p->zFUuid); |
| 2560 | } |
| 2561 | fossil_print("---------------------------------------------------\n"); |
| 2562 | } |
| 2563 | szHash = length_of_S_display(); |
| 2564 | for(i=0; i<ann.nOrig; i++){ |
| 2565 | int iVers = ann.aOrig[i].iVers; |
| 2566 | char *z = (char*)ann.aOrig[i].z; |
| 2567 | int n = ann.aOrig[i].n; |
| 2568 | struct AnnVers *p; |
| 2569 | if( iVers<0 && !ann.bMoreToDo ) iVers = ann.nVers-1; |
| 2570 | if( bBlame ){ |
| 2571 | if( iVers>=0 ){ |
| 2572 | p = ann.aVers + iVers; |
| 2573 | fossil_print("%S %s %13.13s: %.*s\n", |
| 2574 | fileVers ? p->zFUuid : p->zMUuid, p->zDate, p->zUser, n, z); |
| 2575 | }else{ |
| 2576 | fossil_print("%*s %.*s\n", szHash+26, "", n, z); |
| 2577 | } |
| 2578 | }else{ |
| 2579 | if( iVers>=0 ){ |
| 2580 | p = ann.aVers + iVers; |
| 2581 | fossil_print("%S %s %5d: %.*s\n", |
| 2582 | fileVers ? p->zFUuid : p->zMUuid, p->zDate, i+1, n, z); |
| 2583 | }else{ |
| 2584 | fossil_print("%*s %5d: %.*s\n", szHash+11, "", i+1, n, z); |
| 2585 | } |
| 2586 | } |
| 2587 | } |
| 2588 | } |
| 2589 |
+7
| --- src/printf.c | ||
| +++ src/printf.c | ||
| @@ -61,10 +61,17 @@ | ||
| 61 | 61 | if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL; |
| 62 | 62 | if( nDigitUrl > 40 ) nDigitUrl = 40; |
| 63 | 63 | } |
| 64 | 64 | return bForUrl ? nDigitUrl : nDigitHuman; |
| 65 | 65 | } |
| 66 | + | |
| 67 | +/* | |
| 68 | +** Return the number of characters in a %S output. | |
| 69 | +*/ | |
| 70 | +int length_of_S_display(void){ | |
| 71 | + return hashDigits(0); | |
| 72 | +} | |
| 66 | 73 | |
| 67 | 74 | /* |
| 68 | 75 | ** Conversion types fall into various categories as defined by the |
| 69 | 76 | ** following enumeration. |
| 70 | 77 | */ |
| 71 | 78 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -61,10 +61,17 @@ | |
| 61 | if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL; |
| 62 | if( nDigitUrl > 40 ) nDigitUrl = 40; |
| 63 | } |
| 64 | return bForUrl ? nDigitUrl : nDigitHuman; |
| 65 | } |
| 66 | |
| 67 | /* |
| 68 | ** Conversion types fall into various categories as defined by the |
| 69 | ** following enumeration. |
| 70 | */ |
| 71 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -61,10 +61,17 @@ | |
| 61 | if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL; |
| 62 | if( nDigitUrl > 40 ) nDigitUrl = 40; |
| 63 | } |
| 64 | return bForUrl ? nDigitUrl : nDigitHuman; |
| 65 | } |
| 66 | |
| 67 | /* |
| 68 | ** Return the number of characters in a %S output. |
| 69 | */ |
| 70 | int length_of_S_display(void){ |
| 71 | return hashDigits(0); |
| 72 | } |
| 73 | |
| 74 | /* |
| 75 | ** Conversion types fall into various categories as defined by the |
| 76 | ** following enumeration. |
| 77 | */ |
| 78 |