Fossil SCM
Enhancements to the /finfo page so that it follows the file across name changes if the ci=HASH query parameter is used.
Commit
b54d9396f9de3cbcc47a210bf7c3f301e9d7033ff7d04d0393ebc61a203b7628
Parent
299800b29d85e71…
2 files changed
+134
-37
+21
-14
+134
-37
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -271,27 +271,42 @@ | ||
| 271 | 271 | /* Values for the debug= query parameter to finfo */ |
| 272 | 272 | #define FINFO_DEBUG_MLINK 0x01 |
| 273 | 273 | |
| 274 | 274 | /* |
| 275 | 275 | ** WEBPAGE: finfo |
| 276 | -** URL: /finfo?name=FILENAME | |
| 276 | +** Usage: | |
| 277 | +** * /finfo?name=FILENAME | |
| 278 | +** * /finfo?name=FILENAME&ci=HASH | |
| 277 | 279 | ** |
| 278 | -** Show the change history for a single file. | |
| 280 | +** Show the change history for a single file. The name=FILENAME query | |
| 281 | +** parameter gives the filename and is a required parameter. If the | |
| 282 | +** ci=HASH parameter is also supplied, then the FILENAME,HASH combination | |
| 283 | +** identifies a particular version of a file, and in that case all changes | |
| 284 | +** to that one file version are tracked across both edits and renames. | |
| 285 | +** If only the name=FILENAME parameter is supplied (if ci=HASH is omitted) | |
| 286 | +** then the graph shows all changes to any file while it happened | |
| 287 | +** to be called FILENAME and changes are not tracked across renames. | |
| 279 | 288 | ** |
| 280 | 289 | ** Additional query parameters: |
| 281 | 290 | ** |
| 282 | 291 | ** a=DATETIME Only show changes after DATETIME |
| 283 | 292 | ** b=DATETIME Only show changes before DATETIME |
| 284 | -** m=HASH Mark this particular file version | |
| 293 | +** ci=HASH identify a particular version of a file and then | |
| 294 | +** track changes to that file across renames | |
| 295 | +** m=HASH Mark this particular file version. | |
| 285 | 296 | ** n=NUM Show the first NUM changes only |
| 286 | 297 | ** name=FILENAME (Required) name of file whose history to show |
| 287 | 298 | ** brbg Background color by branch name |
| 288 | 299 | ** ubg Background color by user name |
| 289 | -** from=HASH Ancestors of a particular check-in | |
| 300 | +** from=HASH Ancestors only (not descendents) of the version of | |
| 301 | +** the file in this particular check-in. | |
| 290 | 302 | ** to=HASH If both from= and to= are supplied, only show those |
| 291 | -** changes on the direct path between them. | |
| 303 | +** changes on the direct path between the two given | |
| 304 | +** checkins. | |
| 292 | 305 | ** showid Show RID values for debugging |
| 306 | +** showsql Show the SQL query used to gather the data for | |
| 307 | +** the graph | |
| 293 | 308 | ** |
| 294 | 309 | ** DATETIME may be in any of usual formats, including "now", |
| 295 | 310 | ** "YYYY-MM-DDTHH:MM:SS.SSS", "YYYYMMDDHHMM", and others. |
| 296 | 311 | */ |
| 297 | 312 | void finfo_page(void){ |
| @@ -301,10 +316,12 @@ | ||
| 301 | 316 | const char *zA; |
| 302 | 317 | const char *zB; |
| 303 | 318 | int n; |
| 304 | 319 | int ridFrom; |
| 305 | 320 | int ridTo = 0; |
| 321 | + int ridCi = 0; | |
| 322 | + const char *zCI = P("ci"); | |
| 306 | 323 | int fnid; |
| 307 | 324 | Blob title; |
| 308 | 325 | Blob sql; |
| 309 | 326 | HQuery url; |
| 310 | 327 | GraphContext *pGraph; |
| @@ -320,14 +337,17 @@ | ||
| 320 | 337 | int selRid = 0; /* RID of the marked file version */ |
| 321 | 338 | |
| 322 | 339 | login_check_credentials(); |
| 323 | 340 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 324 | 341 | fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); |
| 342 | + ridCi = zCI ? name_to_rid_www("ci") : 0; | |
| 325 | 343 | if( fnid==0 ){ |
| 326 | 344 | style_header("No such file"); |
| 345 | + }else if( ridCi==0 ){ | |
| 346 | + style_header("All files named \"%s\"", zFilename); | |
| 327 | 347 | }else{ |
| 328 | - style_header("History for %s", zFilename); | |
| 348 | + style_header("History of %s of %s",zFilename, zCI); | |
| 329 | 349 | } |
| 330 | 350 | login_anonymous_available(); |
| 331 | 351 | tmFlags = timeline_ss_submenu(); |
| 332 | 352 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 333 | 353 | zStyle = "Columnar"; |
| @@ -362,46 +382,113 @@ | ||
| 362 | 382 | compute_direct_ancestors(ridFrom); |
| 363 | 383 | } |
| 364 | 384 | } |
| 365 | 385 | url_add_parameter(&url, "name", zFilename); |
| 366 | 386 | blob_zero(&sql); |
| 387 | + if( ridCi ){ | |
| 388 | + /* If we will be tracking changes across renames, some extra temp | |
| 389 | + ** tables (implemented as CTEs) are required */ | |
| 390 | + blob_append_sql(&sql, | |
| 391 | + /* The fns(fnid) table holds the list of all filename-IDs that | |
| 392 | + ** might possibly exist in the output. This is an optimization | |
| 393 | + ** used to reduce the size and computation efforts for subsequent | |
| 394 | + ** CTEs. | |
| 395 | + */ | |
| 396 | + "WITH RECURSIVE fns(fnid) AS (\n" | |
| 397 | + " SELECT %d\n" /* <---- fnid */ | |
| 398 | + " UNION\n" | |
| 399 | + " SELECT pfnid FROM mlink, fns\n" | |
| 400 | + " WHERE mlink.fnid=fns.fnid\n" | |
| 401 | + " AND pfnid>0\n" | |
| 402 | + "),\n" | |
| 403 | + | |
| 404 | + /* The flink(fid,fnid,pfid,pfnid) table indicates that there | |
| 405 | + ** is an edit and/or rename arc connecting two files (fid,fnid) | |
| 406 | + ** and (pfid,pfnid). This is similar to the built-in mlink | |
| 407 | + ** table except that flink() is bidirectional. Also the pfnid | |
| 408 | + ** column is always set even no rename occurs. | |
| 409 | + */ | |
| 410 | + "flink(fid,fnid,pfid,pfnid) AS (\n" | |
| 411 | + " SELECT fid, fnid, pid,\n" | |
| 412 | + " CASE WHEN pfnid>0 THEN pfnid ELSE fnid END\n" | |
| 413 | + " FROM mlink\n" | |
| 414 | + " WHERE NOT isaux AND fid>0 AND pid>0 AND fnid IN fns\n" | |
| 415 | + " UNION\n" | |
| 416 | + " SELECT pid,\n" | |
| 417 | + " CASE WHEN pfnid>0 THEN pfnid ELSE fnid END,\n" | |
| 418 | + " fid, fnid\n" | |
| 419 | + " FROM mlink\n" | |
| 420 | + " WHERE NOT isaux AND pid>0 AND fid>0 AND fnid IN fns\n" | |
| 421 | + "),\n" | |
| 422 | + | |
| 423 | + /* The clade(fid,fnid) table is the set of all (fid,fnid) pairs | |
| 424 | + ** that should participate in the output. Clade is computed by | |
| 425 | + ** walking the graph formed by the flink table. | |
| 426 | + */ | |
| 427 | + "clade(fid,fnid) AS (\n" | |
| 428 | + " SELECT blob.rid, %d FROM blob\n" /* %d is fnid */ | |
| 429 | + " WHERE blob.uuid=(SELECT uuid FROM files_of_checkin(%Q)\n" | |
| 430 | + " WHERE filename=%Q)\n" /* %Q is the filename */ | |
| 431 | + " UNION\n" | |
| 432 | + " SELECT flink.fid, flink.fnid\n" | |
| 433 | + " FROM clade, flink\n" | |
| 434 | + " WHERE clade.fid=flink.pfid AND clade.fnid=flink.pfnid\n" | |
| 435 | + ")\n", | |
| 436 | + fnid, fnid, zCI, zFilename | |
| 437 | + ); | |
| 438 | + }else{ | |
| 439 | + /* This is the case for all files with a given name. We will still | |
| 440 | + ** create a "clade(fid,fnid)" table that identifies all participates | |
| 441 | + ** in the output graph, so that subsequent queries can all be the same, | |
| 442 | + ** but in the case the clade table is much simplier, being just a | |
| 443 | + ** single direct query against the mlink table. | |
| 444 | + */ | |
| 445 | + blob_append_sql(&sql, | |
| 446 | + "WITH clade(fid,fnid) AS (\n" | |
| 447 | + " SELECT DISTINCT fid, %d\n" | |
| 448 | + " FROM mlink\n" | |
| 449 | + " WHERE fnid=%d)", | |
| 450 | + fnid, fnid | |
| 451 | + ); | |
| 452 | + } | |
| 367 | 453 | blob_append_sql(&sql, |
| 368 | - "SELECT" | |
| 369 | - " datetime(min(event.mtime),toLocal())," /* Date of change */ | |
| 370 | - " coalesce(event.ecomment, event.comment)," /* Check-in comment */ | |
| 371 | - " coalesce(event.euser, event.user)," /* User who made chng */ | |
| 372 | - " mlink.pid," /* Parent file rid */ | |
| 373 | - " mlink.fid," /* File rid */ | |
| 374 | - " (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file hash */ | |
| 375 | - " blob.uuid," /* Current file hash */ | |
| 376 | - " (SELECT uuid FROM blob WHERE rid=mlink.mid)," /* Check-in hash */ | |
| 377 | - " event.bgcolor," /* Background color */ | |
| 378 | - " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" | |
| 379 | - " AND tagxref.rid=mlink.mid)," /* Branchname */ | |
| 380 | - " mlink.mid," /* check-in ID */ | |
| 381 | - " mlink.pfnid," /* Previous filename */ | |
| 382 | - " blob.size" /* File size */ | |
| 383 | - " FROM mlink, event, blob" | |
| 384 | - " WHERE mlink.fnid=%d" | |
| 385 | - " AND event.objid=mlink.mid" | |
| 386 | - " AND mlink.fid=blob.rid", | |
| 387 | - TAG_BRANCH, fnid | |
| 454 | + "SELECT\n" | |
| 455 | + " datetime(min(event.mtime),toLocal()),\n" /* Date of change */ | |
| 456 | + " coalesce(event.ecomment, event.comment),\n" /* Check-in comment */ | |
| 457 | + " coalesce(event.euser, event.user),\n" /* User who made chng */ | |
| 458 | + " mlink.pid,\n" /* Parent file rid */ | |
| 459 | + " mlink.fid,\n" /* File rid */ | |
| 460 | + " (SELECT uuid FROM blob WHERE rid=mlink.pid),\n" /* Parent file hash */ | |
| 461 | + " blob.uuid,\n" /* Current file hash */ | |
| 462 | + " (SELECT uuid FROM blob WHERE rid=mlink.mid),\n" /* Check-in hash */ | |
| 463 | + " event.bgcolor,\n" /* Background color */ | |
| 464 | + " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" | |
| 465 | + " AND tagxref.rid=mlink.mid),\n" /* Branchname */ | |
| 466 | + " mlink.mid,\n" /* check-in ID */ | |
| 467 | + " mlink.pfnid,\n" /* Previous filename */ | |
| 468 | + " blob.size,\n" /* File size */ | |
| 469 | + " mlink.fnid\n" /* Current filename */ | |
| 470 | + "FROM clade, mlink, event, blob\n" | |
| 471 | + "WHERE mlink.fnid=clade.fnid AND mlink.fid=clade.fid\n" | |
| 472 | + " AND event.objid=mlink.mid\n" | |
| 473 | + " AND blob.rid=clade.fid\n", | |
| 474 | + TAG_BRANCH | |
| 388 | 475 | ); |
| 389 | 476 | if( (zA = P("a"))!=0 ){ |
| 390 | - blob_append_sql(&sql, " AND event.mtime>=%.16g", | |
| 477 | + blob_append_sql(&sql, " AND event.mtime>=%.16g\n", | |
| 391 | 478 | symbolic_name_to_mtime(zA,0)); |
| 392 | 479 | url_add_parameter(&url, "a", zA); |
| 393 | 480 | } |
| 394 | 481 | if( (zB = P("b"))!=0 ){ |
| 395 | - blob_append_sql(&sql, " AND event.mtime<=%.16g", | |
| 482 | + blob_append_sql(&sql, " AND event.mtime<=%.16g\n", | |
| 396 | 483 | symbolic_name_to_mtime(zB,0)); |
| 397 | 484 | url_add_parameter(&url, "b", zB); |
| 398 | 485 | } |
| 399 | 486 | if( ridFrom ){ |
| 400 | 487 | blob_append_sql(&sql, |
| 401 | - " AND mlink.mid IN (SELECT rid FROM ancestor)" | |
| 402 | - " GROUP BY mlink.fid" | |
| 488 | + " AND mlink.mid IN (SELECT rid FROM ancestor)\n" | |
| 489 | + "GROUP BY mlink.fid\n" | |
| 403 | 490 | ); |
| 404 | 491 | }else{ |
| 405 | 492 | /* We only want each version of a file to appear on the graph once, |
| 406 | 493 | ** at its earliest appearance. All the other times that it gets merged |
| 407 | 494 | ** into this or that branch can be ignored. An exception is for when |
| @@ -409,22 +496,23 @@ | ||
| 409 | 496 | ** is deleted in multiple places, we want to show each deletion, so |
| 410 | 497 | ** use a "fake fid" which is derived from the parent-fid for grouping. |
| 411 | 498 | ** The same fake-fid must be used on the graph. |
| 412 | 499 | */ |
| 413 | 500 | blob_append_sql(&sql, |
| 414 | - " GROUP BY" | |
| 415 | - " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END" | |
| 501 | + "GROUP BY" | |
| 502 | + " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END\n" | |
| 416 | 503 | ); |
| 417 | 504 | } |
| 418 | - blob_append_sql(&sql, " ORDER BY event.mtime DESC /*sort*/"); | |
| 505 | + blob_append_sql(&sql, "ORDER BY event.mtime DESC"); | |
| 419 | 506 | if( (n = atoi(PD("n","0")))>0 ){ |
| 420 | 507 | blob_append_sql(&sql, " LIMIT %d", n); |
| 421 | 508 | url_add_parameter(&url, "n", P("n")); |
| 422 | 509 | } |
| 510 | + blob_append_sql(&sql, " /*sort*/\n"); | |
| 423 | 511 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 424 | 512 | if( P("showsql")!=0 ){ |
| 425 | - @ <p>SQL: %h(blob_str(&sql))</p> | |
| 513 | + @ <p>SQL: <blockquote><pre>%h(blob_str(&sql))</blockquote></pre> | |
| 426 | 514 | } |
| 427 | 515 | zMark = P("m"); |
| 428 | 516 | if( zMark ){ |
| 429 | 517 | selRid = symbolic_name_to_rid(zMark, "*"); |
| 430 | 518 | } |
| @@ -452,10 +540,16 @@ | ||
| 452 | 540 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ridTo); |
| 453 | 541 | zLink = href("%R/info/%!S", zUuid); |
| 454 | 542 | blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid); |
| 455 | 543 | fossil_free(zUuid); |
| 456 | 544 | } |
| 545 | + }else if( ridCi ){ | |
| 546 | + blob_appendf(&title, "History of the file that is called "); | |
| 547 | + hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); | |
| 548 | + if( fShowId ) blob_appendf(&title, " (%d)", fnid); | |
| 549 | + blob_appendf(&title, " at checkin %z%h</a>", | |
| 550 | + href("%R/info?name=%t",zCI), zCI); | |
| 457 | 551 | }else{ |
| 458 | 552 | blob_appendf(&title, "History for "); |
| 459 | 553 | hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); |
| 460 | 554 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 461 | 555 | } |
| @@ -492,10 +586,11 @@ | ||
| 492 | 586 | const char *zBgClr = db_column_text(&q, 8); |
| 493 | 587 | const char *zBr = db_column_text(&q, 9); |
| 494 | 588 | int fmid = db_column_int(&q, 10); |
| 495 | 589 | int pfnid = db_column_int(&q, 11); |
| 496 | 590 | int szFile = db_column_int(&q, 12); |
| 591 | + int fnid = db_column_int(&q, 13); | |
| 497 | 592 | int gidx; |
| 498 | 593 | char zTime[10]; |
| 499 | 594 | int nParent = 0; |
| 500 | 595 | int aParent[GR_MAX_RAIL]; |
| 501 | 596 | |
| @@ -565,11 +660,12 @@ | ||
| 565 | 660 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 566 | 661 | } |
| 567 | 662 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 568 | 663 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); |
| 569 | 664 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 570 | - @ file: %z(href("%R/file?name=%T&ci=%!S",zFilename,zCkin))[%S(zUuid)]</a> | |
| 665 | + @ file: %z(href("%R/file?name=%T&ci=%!S",zFilename,zCkin))\ | |
| 666 | + @ [%S(zUuid)]</a> | |
| 571 | 667 | if( fShowId ){ |
| 572 | 668 | int srcId = delta_source_rid(frid); |
| 573 | 669 | if( srcId>0 ){ |
| 574 | 670 | @ id: %d(frid)←%d(srcId) |
| 575 | 671 | }else{ |
| @@ -626,11 +722,12 @@ | ||
| 626 | 722 | @ %z(href("%R/timeline?n=all&uf=%!S",zUuid))[check-ins using]</a> |
| 627 | 723 | if( fpid>0 ){ |
| 628 | 724 | @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a> |
| 629 | 725 | } |
| 630 | 726 | if( fileedit_is_editable(zFilename) ){ |
| 631 | - @ %z(href("%R/fileedit?filename=%T&checkin=%!S",zFilename,zCkin))[edit]</a> | |
| 727 | + @ %z(href("%R/fileedit?filename=%T&checkin=%!S",zFilename,zCkin))\ | |
| 728 | + @ [edit]</a> | |
| 632 | 729 | } |
| 633 | 730 | @ </span></span> |
| 634 | 731 | } |
| 635 | 732 | if( fDebug & FINFO_DEBUG_MLINK ){ |
| 636 | 733 | int ii; |
| @@ -640,11 +737,11 @@ | ||
| 640 | 737 | @ parents=%d(aParent[0]) |
| 641 | 738 | for(ii=1; ii<nParent; ii++){ |
| 642 | 739 | @ %d(aParent[ii]) |
| 643 | 740 | } |
| 644 | 741 | } |
| 645 | - zAncLink = href("%R/finfo?name=%T&ci=%!S&debug=1",zFilename,zCkin); | |
| 742 | + zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFilename,zCkin); | |
| 646 | 743 | @ %z(zAncLink)[ancestry]</a> |
| 647 | 744 | } |
| 648 | 745 | tag_private_status(frid); |
| 649 | 746 | /* End timelineDetail */ |
| 650 | 747 | if( tmFlags & TIMELINE_COMPACT ){ |
| 651 | 748 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -271,27 +271,42 @@ | |
| 271 | /* Values for the debug= query parameter to finfo */ |
| 272 | #define FINFO_DEBUG_MLINK 0x01 |
| 273 | |
| 274 | /* |
| 275 | ** WEBPAGE: finfo |
| 276 | ** URL: /finfo?name=FILENAME |
| 277 | ** |
| 278 | ** Show the change history for a single file. |
| 279 | ** |
| 280 | ** Additional query parameters: |
| 281 | ** |
| 282 | ** a=DATETIME Only show changes after DATETIME |
| 283 | ** b=DATETIME Only show changes before DATETIME |
| 284 | ** m=HASH Mark this particular file version |
| 285 | ** n=NUM Show the first NUM changes only |
| 286 | ** name=FILENAME (Required) name of file whose history to show |
| 287 | ** brbg Background color by branch name |
| 288 | ** ubg Background color by user name |
| 289 | ** from=HASH Ancestors of a particular check-in |
| 290 | ** to=HASH If both from= and to= are supplied, only show those |
| 291 | ** changes on the direct path between them. |
| 292 | ** showid Show RID values for debugging |
| 293 | ** |
| 294 | ** DATETIME may be in any of usual formats, including "now", |
| 295 | ** "YYYY-MM-DDTHH:MM:SS.SSS", "YYYYMMDDHHMM", and others. |
| 296 | */ |
| 297 | void finfo_page(void){ |
| @@ -301,10 +316,12 @@ | |
| 301 | const char *zA; |
| 302 | const char *zB; |
| 303 | int n; |
| 304 | int ridFrom; |
| 305 | int ridTo = 0; |
| 306 | int fnid; |
| 307 | Blob title; |
| 308 | Blob sql; |
| 309 | HQuery url; |
| 310 | GraphContext *pGraph; |
| @@ -320,14 +337,17 @@ | |
| 320 | int selRid = 0; /* RID of the marked file version */ |
| 321 | |
| 322 | login_check_credentials(); |
| 323 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 324 | fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); |
| 325 | if( fnid==0 ){ |
| 326 | style_header("No such file"); |
| 327 | }else{ |
| 328 | style_header("History for %s", zFilename); |
| 329 | } |
| 330 | login_anonymous_available(); |
| 331 | tmFlags = timeline_ss_submenu(); |
| 332 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 333 | zStyle = "Columnar"; |
| @@ -362,46 +382,113 @@ | |
| 362 | compute_direct_ancestors(ridFrom); |
| 363 | } |
| 364 | } |
| 365 | url_add_parameter(&url, "name", zFilename); |
| 366 | blob_zero(&sql); |
| 367 | blob_append_sql(&sql, |
| 368 | "SELECT" |
| 369 | " datetime(min(event.mtime),toLocal())," /* Date of change */ |
| 370 | " coalesce(event.ecomment, event.comment)," /* Check-in comment */ |
| 371 | " coalesce(event.euser, event.user)," /* User who made chng */ |
| 372 | " mlink.pid," /* Parent file rid */ |
| 373 | " mlink.fid," /* File rid */ |
| 374 | " (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file hash */ |
| 375 | " blob.uuid," /* Current file hash */ |
| 376 | " (SELECT uuid FROM blob WHERE rid=mlink.mid)," /* Check-in hash */ |
| 377 | " event.bgcolor," /* Background color */ |
| 378 | " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" |
| 379 | " AND tagxref.rid=mlink.mid)," /* Branchname */ |
| 380 | " mlink.mid," /* check-in ID */ |
| 381 | " mlink.pfnid," /* Previous filename */ |
| 382 | " blob.size" /* File size */ |
| 383 | " FROM mlink, event, blob" |
| 384 | " WHERE mlink.fnid=%d" |
| 385 | " AND event.objid=mlink.mid" |
| 386 | " AND mlink.fid=blob.rid", |
| 387 | TAG_BRANCH, fnid |
| 388 | ); |
| 389 | if( (zA = P("a"))!=0 ){ |
| 390 | blob_append_sql(&sql, " AND event.mtime>=%.16g", |
| 391 | symbolic_name_to_mtime(zA,0)); |
| 392 | url_add_parameter(&url, "a", zA); |
| 393 | } |
| 394 | if( (zB = P("b"))!=0 ){ |
| 395 | blob_append_sql(&sql, " AND event.mtime<=%.16g", |
| 396 | symbolic_name_to_mtime(zB,0)); |
| 397 | url_add_parameter(&url, "b", zB); |
| 398 | } |
| 399 | if( ridFrom ){ |
| 400 | blob_append_sql(&sql, |
| 401 | " AND mlink.mid IN (SELECT rid FROM ancestor)" |
| 402 | " GROUP BY mlink.fid" |
| 403 | ); |
| 404 | }else{ |
| 405 | /* We only want each version of a file to appear on the graph once, |
| 406 | ** at its earliest appearance. All the other times that it gets merged |
| 407 | ** into this or that branch can be ignored. An exception is for when |
| @@ -409,22 +496,23 @@ | |
| 409 | ** is deleted in multiple places, we want to show each deletion, so |
| 410 | ** use a "fake fid" which is derived from the parent-fid for grouping. |
| 411 | ** The same fake-fid must be used on the graph. |
| 412 | */ |
| 413 | blob_append_sql(&sql, |
| 414 | " GROUP BY" |
| 415 | " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END" |
| 416 | ); |
| 417 | } |
| 418 | blob_append_sql(&sql, " ORDER BY event.mtime DESC /*sort*/"); |
| 419 | if( (n = atoi(PD("n","0")))>0 ){ |
| 420 | blob_append_sql(&sql, " LIMIT %d", n); |
| 421 | url_add_parameter(&url, "n", P("n")); |
| 422 | } |
| 423 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 424 | if( P("showsql")!=0 ){ |
| 425 | @ <p>SQL: %h(blob_str(&sql))</p> |
| 426 | } |
| 427 | zMark = P("m"); |
| 428 | if( zMark ){ |
| 429 | selRid = symbolic_name_to_rid(zMark, "*"); |
| 430 | } |
| @@ -452,10 +540,16 @@ | |
| 452 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ridTo); |
| 453 | zLink = href("%R/info/%!S", zUuid); |
| 454 | blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid); |
| 455 | fossil_free(zUuid); |
| 456 | } |
| 457 | }else{ |
| 458 | blob_appendf(&title, "History for "); |
| 459 | hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); |
| 460 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 461 | } |
| @@ -492,10 +586,11 @@ | |
| 492 | const char *zBgClr = db_column_text(&q, 8); |
| 493 | const char *zBr = db_column_text(&q, 9); |
| 494 | int fmid = db_column_int(&q, 10); |
| 495 | int pfnid = db_column_int(&q, 11); |
| 496 | int szFile = db_column_int(&q, 12); |
| 497 | int gidx; |
| 498 | char zTime[10]; |
| 499 | int nParent = 0; |
| 500 | int aParent[GR_MAX_RAIL]; |
| 501 | |
| @@ -565,11 +660,12 @@ | |
| 565 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 566 | } |
| 567 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 568 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); |
| 569 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 570 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFilename,zCkin))[%S(zUuid)]</a> |
| 571 | if( fShowId ){ |
| 572 | int srcId = delta_source_rid(frid); |
| 573 | if( srcId>0 ){ |
| 574 | @ id: %d(frid)←%d(srcId) |
| 575 | }else{ |
| @@ -626,11 +722,12 @@ | |
| 626 | @ %z(href("%R/timeline?n=all&uf=%!S",zUuid))[check-ins using]</a> |
| 627 | if( fpid>0 ){ |
| 628 | @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a> |
| 629 | } |
| 630 | if( fileedit_is_editable(zFilename) ){ |
| 631 | @ %z(href("%R/fileedit?filename=%T&checkin=%!S",zFilename,zCkin))[edit]</a> |
| 632 | } |
| 633 | @ </span></span> |
| 634 | } |
| 635 | if( fDebug & FINFO_DEBUG_MLINK ){ |
| 636 | int ii; |
| @@ -640,11 +737,11 @@ | |
| 640 | @ parents=%d(aParent[0]) |
| 641 | for(ii=1; ii<nParent; ii++){ |
| 642 | @ %d(aParent[ii]) |
| 643 | } |
| 644 | } |
| 645 | zAncLink = href("%R/finfo?name=%T&ci=%!S&debug=1",zFilename,zCkin); |
| 646 | @ %z(zAncLink)[ancestry]</a> |
| 647 | } |
| 648 | tag_private_status(frid); |
| 649 | /* End timelineDetail */ |
| 650 | if( tmFlags & TIMELINE_COMPACT ){ |
| 651 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -271,27 +271,42 @@ | |
| 271 | /* Values for the debug= query parameter to finfo */ |
| 272 | #define FINFO_DEBUG_MLINK 0x01 |
| 273 | |
| 274 | /* |
| 275 | ** WEBPAGE: finfo |
| 276 | ** Usage: |
| 277 | ** * /finfo?name=FILENAME |
| 278 | ** * /finfo?name=FILENAME&ci=HASH |
| 279 | ** |
| 280 | ** Show the change history for a single file. The name=FILENAME query |
| 281 | ** parameter gives the filename and is a required parameter. If the |
| 282 | ** ci=HASH parameter is also supplied, then the FILENAME,HASH combination |
| 283 | ** identifies a particular version of a file, and in that case all changes |
| 284 | ** to that one file version are tracked across both edits and renames. |
| 285 | ** If only the name=FILENAME parameter is supplied (if ci=HASH is omitted) |
| 286 | ** then the graph shows all changes to any file while it happened |
| 287 | ** to be called FILENAME and changes are not tracked across renames. |
| 288 | ** |
| 289 | ** Additional query parameters: |
| 290 | ** |
| 291 | ** a=DATETIME Only show changes after DATETIME |
| 292 | ** b=DATETIME Only show changes before DATETIME |
| 293 | ** ci=HASH identify a particular version of a file and then |
| 294 | ** track changes to that file across renames |
| 295 | ** m=HASH Mark this particular file version. |
| 296 | ** n=NUM Show the first NUM changes only |
| 297 | ** name=FILENAME (Required) name of file whose history to show |
| 298 | ** brbg Background color by branch name |
| 299 | ** ubg Background color by user name |
| 300 | ** from=HASH Ancestors only (not descendents) of the version of |
| 301 | ** the file in this particular check-in. |
| 302 | ** to=HASH If both from= and to= are supplied, only show those |
| 303 | ** changes on the direct path between the two given |
| 304 | ** checkins. |
| 305 | ** showid Show RID values for debugging |
| 306 | ** showsql Show the SQL query used to gather the data for |
| 307 | ** the graph |
| 308 | ** |
| 309 | ** DATETIME may be in any of usual formats, including "now", |
| 310 | ** "YYYY-MM-DDTHH:MM:SS.SSS", "YYYYMMDDHHMM", and others. |
| 311 | */ |
| 312 | void finfo_page(void){ |
| @@ -301,10 +316,12 @@ | |
| 316 | const char *zA; |
| 317 | const char *zB; |
| 318 | int n; |
| 319 | int ridFrom; |
| 320 | int ridTo = 0; |
| 321 | int ridCi = 0; |
| 322 | const char *zCI = P("ci"); |
| 323 | int fnid; |
| 324 | Blob title; |
| 325 | Blob sql; |
| 326 | HQuery url; |
| 327 | GraphContext *pGraph; |
| @@ -320,14 +337,17 @@ | |
| 337 | int selRid = 0; /* RID of the marked file version */ |
| 338 | |
| 339 | login_check_credentials(); |
| 340 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 341 | fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); |
| 342 | ridCi = zCI ? name_to_rid_www("ci") : 0; |
| 343 | if( fnid==0 ){ |
| 344 | style_header("No such file"); |
| 345 | }else if( ridCi==0 ){ |
| 346 | style_header("All files named \"%s\"", zFilename); |
| 347 | }else{ |
| 348 | style_header("History of %s of %s",zFilename, zCI); |
| 349 | } |
| 350 | login_anonymous_available(); |
| 351 | tmFlags = timeline_ss_submenu(); |
| 352 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 353 | zStyle = "Columnar"; |
| @@ -362,46 +382,113 @@ | |
| 382 | compute_direct_ancestors(ridFrom); |
| 383 | } |
| 384 | } |
| 385 | url_add_parameter(&url, "name", zFilename); |
| 386 | blob_zero(&sql); |
| 387 | if( ridCi ){ |
| 388 | /* If we will be tracking changes across renames, some extra temp |
| 389 | ** tables (implemented as CTEs) are required */ |
| 390 | blob_append_sql(&sql, |
| 391 | /* The fns(fnid) table holds the list of all filename-IDs that |
| 392 | ** might possibly exist in the output. This is an optimization |
| 393 | ** used to reduce the size and computation efforts for subsequent |
| 394 | ** CTEs. |
| 395 | */ |
| 396 | "WITH RECURSIVE fns(fnid) AS (\n" |
| 397 | " SELECT %d\n" /* <---- fnid */ |
| 398 | " UNION\n" |
| 399 | " SELECT pfnid FROM mlink, fns\n" |
| 400 | " WHERE mlink.fnid=fns.fnid\n" |
| 401 | " AND pfnid>0\n" |
| 402 | "),\n" |
| 403 | |
| 404 | /* The flink(fid,fnid,pfid,pfnid) table indicates that there |
| 405 | ** is an edit and/or rename arc connecting two files (fid,fnid) |
| 406 | ** and (pfid,pfnid). This is similar to the built-in mlink |
| 407 | ** table except that flink() is bidirectional. Also the pfnid |
| 408 | ** column is always set even no rename occurs. |
| 409 | */ |
| 410 | "flink(fid,fnid,pfid,pfnid) AS (\n" |
| 411 | " SELECT fid, fnid, pid,\n" |
| 412 | " CASE WHEN pfnid>0 THEN pfnid ELSE fnid END\n" |
| 413 | " FROM mlink\n" |
| 414 | " WHERE NOT isaux AND fid>0 AND pid>0 AND fnid IN fns\n" |
| 415 | " UNION\n" |
| 416 | " SELECT pid,\n" |
| 417 | " CASE WHEN pfnid>0 THEN pfnid ELSE fnid END,\n" |
| 418 | " fid, fnid\n" |
| 419 | " FROM mlink\n" |
| 420 | " WHERE NOT isaux AND pid>0 AND fid>0 AND fnid IN fns\n" |
| 421 | "),\n" |
| 422 | |
| 423 | /* The clade(fid,fnid) table is the set of all (fid,fnid) pairs |
| 424 | ** that should participate in the output. Clade is computed by |
| 425 | ** walking the graph formed by the flink table. |
| 426 | */ |
| 427 | "clade(fid,fnid) AS (\n" |
| 428 | " SELECT blob.rid, %d FROM blob\n" /* %d is fnid */ |
| 429 | " WHERE blob.uuid=(SELECT uuid FROM files_of_checkin(%Q)\n" |
| 430 | " WHERE filename=%Q)\n" /* %Q is the filename */ |
| 431 | " UNION\n" |
| 432 | " SELECT flink.fid, flink.fnid\n" |
| 433 | " FROM clade, flink\n" |
| 434 | " WHERE clade.fid=flink.pfid AND clade.fnid=flink.pfnid\n" |
| 435 | ")\n", |
| 436 | fnid, fnid, zCI, zFilename |
| 437 | ); |
| 438 | }else{ |
| 439 | /* This is the case for all files with a given name. We will still |
| 440 | ** create a "clade(fid,fnid)" table that identifies all participates |
| 441 | ** in the output graph, so that subsequent queries can all be the same, |
| 442 | ** but in the case the clade table is much simplier, being just a |
| 443 | ** single direct query against the mlink table. |
| 444 | */ |
| 445 | blob_append_sql(&sql, |
| 446 | "WITH clade(fid,fnid) AS (\n" |
| 447 | " SELECT DISTINCT fid, %d\n" |
| 448 | " FROM mlink\n" |
| 449 | " WHERE fnid=%d)", |
| 450 | fnid, fnid |
| 451 | ); |
| 452 | } |
| 453 | blob_append_sql(&sql, |
| 454 | "SELECT\n" |
| 455 | " datetime(min(event.mtime),toLocal()),\n" /* Date of change */ |
| 456 | " coalesce(event.ecomment, event.comment),\n" /* Check-in comment */ |
| 457 | " coalesce(event.euser, event.user),\n" /* User who made chng */ |
| 458 | " mlink.pid,\n" /* Parent file rid */ |
| 459 | " mlink.fid,\n" /* File rid */ |
| 460 | " (SELECT uuid FROM blob WHERE rid=mlink.pid),\n" /* Parent file hash */ |
| 461 | " blob.uuid,\n" /* Current file hash */ |
| 462 | " (SELECT uuid FROM blob WHERE rid=mlink.mid),\n" /* Check-in hash */ |
| 463 | " event.bgcolor,\n" /* Background color */ |
| 464 | " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" |
| 465 | " AND tagxref.rid=mlink.mid),\n" /* Branchname */ |
| 466 | " mlink.mid,\n" /* check-in ID */ |
| 467 | " mlink.pfnid,\n" /* Previous filename */ |
| 468 | " blob.size,\n" /* File size */ |
| 469 | " mlink.fnid\n" /* Current filename */ |
| 470 | "FROM clade, mlink, event, blob\n" |
| 471 | "WHERE mlink.fnid=clade.fnid AND mlink.fid=clade.fid\n" |
| 472 | " AND event.objid=mlink.mid\n" |
| 473 | " AND blob.rid=clade.fid\n", |
| 474 | TAG_BRANCH |
| 475 | ); |
| 476 | if( (zA = P("a"))!=0 ){ |
| 477 | blob_append_sql(&sql, " AND event.mtime>=%.16g\n", |
| 478 | symbolic_name_to_mtime(zA,0)); |
| 479 | url_add_parameter(&url, "a", zA); |
| 480 | } |
| 481 | if( (zB = P("b"))!=0 ){ |
| 482 | blob_append_sql(&sql, " AND event.mtime<=%.16g\n", |
| 483 | symbolic_name_to_mtime(zB,0)); |
| 484 | url_add_parameter(&url, "b", zB); |
| 485 | } |
| 486 | if( ridFrom ){ |
| 487 | blob_append_sql(&sql, |
| 488 | " AND mlink.mid IN (SELECT rid FROM ancestor)\n" |
| 489 | "GROUP BY mlink.fid\n" |
| 490 | ); |
| 491 | }else{ |
| 492 | /* We only want each version of a file to appear on the graph once, |
| 493 | ** at its earliest appearance. All the other times that it gets merged |
| 494 | ** into this or that branch can be ignored. An exception is for when |
| @@ -409,22 +496,23 @@ | |
| 496 | ** is deleted in multiple places, we want to show each deletion, so |
| 497 | ** use a "fake fid" which is derived from the parent-fid for grouping. |
| 498 | ** The same fake-fid must be used on the graph. |
| 499 | */ |
| 500 | blob_append_sql(&sql, |
| 501 | "GROUP BY" |
| 502 | " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END\n" |
| 503 | ); |
| 504 | } |
| 505 | blob_append_sql(&sql, "ORDER BY event.mtime DESC"); |
| 506 | if( (n = atoi(PD("n","0")))>0 ){ |
| 507 | blob_append_sql(&sql, " LIMIT %d", n); |
| 508 | url_add_parameter(&url, "n", P("n")); |
| 509 | } |
| 510 | blob_append_sql(&sql, " /*sort*/\n"); |
| 511 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 512 | if( P("showsql")!=0 ){ |
| 513 | @ <p>SQL: <blockquote><pre>%h(blob_str(&sql))</blockquote></pre> |
| 514 | } |
| 515 | zMark = P("m"); |
| 516 | if( zMark ){ |
| 517 | selRid = symbolic_name_to_rid(zMark, "*"); |
| 518 | } |
| @@ -452,10 +540,16 @@ | |
| 540 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ridTo); |
| 541 | zLink = href("%R/info/%!S", zUuid); |
| 542 | blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid); |
| 543 | fossil_free(zUuid); |
| 544 | } |
| 545 | }else if( ridCi ){ |
| 546 | blob_appendf(&title, "History of the file that is called "); |
| 547 | hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); |
| 548 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 549 | blob_appendf(&title, " at checkin %z%h</a>", |
| 550 | href("%R/info?name=%t",zCI), zCI); |
| 551 | }else{ |
| 552 | blob_appendf(&title, "History for "); |
| 553 | hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); |
| 554 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 555 | } |
| @@ -492,10 +586,11 @@ | |
| 586 | const char *zBgClr = db_column_text(&q, 8); |
| 587 | const char *zBr = db_column_text(&q, 9); |
| 588 | int fmid = db_column_int(&q, 10); |
| 589 | int pfnid = db_column_int(&q, 11); |
| 590 | int szFile = db_column_int(&q, 12); |
| 591 | int fnid = db_column_int(&q, 13); |
| 592 | int gidx; |
| 593 | char zTime[10]; |
| 594 | int nParent = 0; |
| 595 | int aParent[GR_MAX_RAIL]; |
| 596 | |
| @@ -565,11 +660,12 @@ | |
| 660 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 661 | } |
| 662 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 663 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); |
| 664 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 665 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFilename,zCkin))\ |
| 666 | @ [%S(zUuid)]</a> |
| 667 | if( fShowId ){ |
| 668 | int srcId = delta_source_rid(frid); |
| 669 | if( srcId>0 ){ |
| 670 | @ id: %d(frid)←%d(srcId) |
| 671 | }else{ |
| @@ -626,11 +722,12 @@ | |
| 722 | @ %z(href("%R/timeline?n=all&uf=%!S",zUuid))[check-ins using]</a> |
| 723 | if( fpid>0 ){ |
| 724 | @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a> |
| 725 | } |
| 726 | if( fileedit_is_editable(zFilename) ){ |
| 727 | @ %z(href("%R/fileedit?filename=%T&checkin=%!S",zFilename,zCkin))\ |
| 728 | @ [edit]</a> |
| 729 | } |
| 730 | @ </span></span> |
| 731 | } |
| 732 | if( fDebug & FINFO_DEBUG_MLINK ){ |
| 733 | int ii; |
| @@ -640,11 +737,11 @@ | |
| 737 | @ parents=%d(aParent[0]) |
| 738 | for(ii=1; ii<nParent; ii++){ |
| 739 | @ %d(aParent[ii]) |
| 740 | } |
| 741 | } |
| 742 | zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFilename,zCkin); |
| 743 | @ %z(zAncLink)[ancestry]</a> |
| 744 | } |
| 745 | tag_private_status(frid); |
| 746 | /* End timelineDetail */ |
| 747 | if( tmFlags & TIMELINE_COMPACT ){ |
| 748 |
+21
-14
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -368,10 +368,11 @@ | ||
| 368 | 368 | /* |
| 369 | 369 | ** Write a line of web-page output that shows changes that have occurred |
| 370 | 370 | ** to a file between two check-ins. |
| 371 | 371 | */ |
| 372 | 372 | static void append_file_change_line( |
| 373 | + const char *zCkin, /* The checkin on which the change occurs */ | |
| 373 | 374 | const char *zName, /* Name of the file that has changed */ |
| 374 | 375 | const char *zOld, /* blob.uuid before change. NULL for added files */ |
| 375 | 376 | const char *zNew, /* blob.uuid after change. NULL for deletes */ |
| 376 | 377 | const char *zOldName, /* Prior name. NULL if no name change. */ |
| 377 | 378 | u64 diffFlags, /* Flags for text_diff(). Zero to omit diffs */ |
| @@ -401,19 +402,23 @@ | ||
| 401 | 402 | append_diff(zOld, zNew, diffFlags, pRe); |
| 402 | 403 | } |
| 403 | 404 | }else{ |
| 404 | 405 | if( zOld && zNew ){ |
| 405 | 406 | if( fossil_strcmp(zOld, zNew)!=0 ){ |
| 406 | - @ Modified %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a> | |
| 407 | + @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 408 | + @ %h(zName)</a> | |
| 407 | 409 | @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 408 | 410 | @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 409 | 411 | }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 410 | 412 | @ Name change |
| 411 | - @ from %z(href("%R/finfo?name=%T&m=%!S",zOldName,zOld))%h(zOldName)</a> | |
| 412 | - @ to %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>. | |
| 413 | + @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\ | |
| 414 | + @ %h(zOldName)</a> | |
| 415 | + @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 416 | + @ %h(zName)</a>. | |
| 413 | 417 | }else{ |
| 414 | - @ %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a> became | |
| 418 | + @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 419 | + @ %h(zName)</a> became | |
| 415 | 420 | if( mperm==PERM_EXE ){ |
| 416 | 421 | @ executable with contents |
| 417 | 422 | }else if( mperm==PERM_LNK ){ |
| 418 | 423 | @ a symlink with target |
| 419 | 424 | }else{ |
| @@ -420,15 +425,15 @@ | ||
| 420 | 425 | @ a regular file with contents |
| 421 | 426 | } |
| 422 | 427 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 423 | 428 | } |
| 424 | 429 | }else if( zOld ){ |
| 425 | - @ Deleted %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a> | |
| 426 | - @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>. | |
| 430 | + @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zOld,zCkin))\ | |
| 431 | + @ %h(zName)</a> version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>. | |
| 427 | 432 | }else{ |
| 428 | - @ Added %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a> | |
| 429 | - @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. | |
| 433 | + @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 434 | + @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. | |
| 430 | 435 | } |
| 431 | 436 | if( diffFlags ){ |
| 432 | 437 | append_diff(zOld, zNew, diffFlags, pRe); |
| 433 | 438 | }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 434 | 439 | @ |
| @@ -929,11 +934,12 @@ | ||
| 929 | 934 | const char *zName = db_column_text(&q3,0); |
| 930 | 935 | int mperm = db_column_int(&q3, 1); |
| 931 | 936 | const char *zOld = db_column_text(&q3,2); |
| 932 | 937 | const char *zNew = db_column_text(&q3,3); |
| 933 | 938 | const char *zOldName = db_column_text(&q3, 4); |
| 934 | - append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm); | |
| 939 | + append_file_change_line(zUuid, zName, zOld, zNew, zOldName, | |
| 940 | + diffFlags,pRe,mperm); | |
| 935 | 941 | } |
| 936 | 942 | db_finalize(&q3); |
| 937 | 943 | append_diff_javascript(diffType==2); |
| 938 | 944 | cookie_render(); |
| 939 | 945 | style_footer(); |
| @@ -1292,17 +1298,17 @@ | ||
| 1292 | 1298 | }else{ |
| 1293 | 1299 | cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); |
| 1294 | 1300 | } |
| 1295 | 1301 | if( cmp<0 ){ |
| 1296 | 1302 | if( !zGlob || sqlite3_strglob(zGlob, pFileFrom->zName)==0 ){ |
| 1297 | - append_file_change_line(pFileFrom->zName, | |
| 1303 | + append_file_change_line(zFrom, pFileFrom->zName, | |
| 1298 | 1304 | pFileFrom->zUuid, 0, 0, diffFlags, pRe, 0); |
| 1299 | 1305 | } |
| 1300 | 1306 | pFileFrom = manifest_file_next(pFrom, 0); |
| 1301 | 1307 | }else if( cmp>0 ){ |
| 1302 | 1308 | if( !zGlob || sqlite3_strglob(zGlob, pFileTo->zName)==0 ){ |
| 1303 | - append_file_change_line(pFileTo->zName, | |
| 1309 | + append_file_change_line(zTo, pFileTo->zName, | |
| 1304 | 1310 | 0, pFileTo->zUuid, 0, diffFlags, pRe, |
| 1305 | 1311 | manifest_file_mperm(pFileTo)); |
| 1306 | 1312 | } |
| 1307 | 1313 | pFileTo = manifest_file_next(pTo, 0); |
| 1308 | 1314 | }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ |
| @@ -1309,11 +1315,11 @@ | ||
| 1309 | 1315 | pFileFrom = manifest_file_next(pFrom, 0); |
| 1310 | 1316 | pFileTo = manifest_file_next(pTo, 0); |
| 1311 | 1317 | }else{ |
| 1312 | 1318 | if(!zGlob || (sqlite3_strglob(zGlob, pFileFrom->zName)==0 |
| 1313 | 1319 | || sqlite3_strglob(zGlob, pFileTo->zName)==0) ){ |
| 1314 | - append_file_change_line(pFileFrom->zName, | |
| 1320 | + append_file_change_line(zFrom, pFileFrom->zName, | |
| 1315 | 1321 | pFileFrom->zUuid, |
| 1316 | 1322 | pFileTo->zUuid, 0, diffFlags, pRe, |
| 1317 | 1323 | manifest_file_mperm(pFileTo)); |
| 1318 | 1324 | } |
| 1319 | 1325 | pFileFrom = manifest_file_next(pFrom, 0); |
| @@ -1419,11 +1425,12 @@ | ||
| 1419 | 1425 | bNeedBase = 0; |
| 1420 | 1426 | style_set_current_page("doc/%S/%s",zVers,zName); |
| 1421 | 1427 | } |
| 1422 | 1428 | } |
| 1423 | 1429 | objType |= OBJTYPE_CONTENT; |
| 1424 | - @ %z(href("%R/finfo?name=%T&m=%!S",zName,zUuid))%h(zName)</a> | |
| 1430 | + @ %z(href("%R/finfo?name=%T&ci=%!S&m=%!S",zName,zVers,zUuid))\ | |
| 1431 | + @ %h(zName)</a> | |
| 1425 | 1432 | tag_private_status(rid); |
| 1426 | 1433 | if( showDetail ){ |
| 1427 | 1434 | @ <ul> |
| 1428 | 1435 | } |
| 1429 | 1436 | prevName = fossil_strdup(zName); |
| @@ -2311,11 +2318,11 @@ | ||
| 2311 | 2318 | |
| 2312 | 2319 | asText = P("txt")!=0; |
| 2313 | 2320 | if( isFile ){ |
| 2314 | 2321 | if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2315 | 2322 | zCI = "tip"; |
| 2316 | - @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a> | |
| 2323 | + @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a> | |
| 2317 | 2324 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2318 | 2325 | }else{ |
| 2319 | 2326 | const char *zPath; |
| 2320 | 2327 | Blob path; |
| 2321 | 2328 | blob_zero(&path); |
| 2322 | 2329 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -368,10 +368,11 @@ | |
| 368 | /* |
| 369 | ** Write a line of web-page output that shows changes that have occurred |
| 370 | ** to a file between two check-ins. |
| 371 | */ |
| 372 | static void append_file_change_line( |
| 373 | const char *zName, /* Name of the file that has changed */ |
| 374 | const char *zOld, /* blob.uuid before change. NULL for added files */ |
| 375 | const char *zNew, /* blob.uuid after change. NULL for deletes */ |
| 376 | const char *zOldName, /* Prior name. NULL if no name change. */ |
| 377 | u64 diffFlags, /* Flags for text_diff(). Zero to omit diffs */ |
| @@ -401,19 +402,23 @@ | |
| 401 | append_diff(zOld, zNew, diffFlags, pRe); |
| 402 | } |
| 403 | }else{ |
| 404 | if( zOld && zNew ){ |
| 405 | if( fossil_strcmp(zOld, zNew)!=0 ){ |
| 406 | @ Modified %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a> |
| 407 | @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 408 | @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 409 | }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 410 | @ Name change |
| 411 | @ from %z(href("%R/finfo?name=%T&m=%!S",zOldName,zOld))%h(zOldName)</a> |
| 412 | @ to %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>. |
| 413 | }else{ |
| 414 | @ %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a> became |
| 415 | if( mperm==PERM_EXE ){ |
| 416 | @ executable with contents |
| 417 | }else if( mperm==PERM_LNK ){ |
| 418 | @ a symlink with target |
| 419 | }else{ |
| @@ -420,15 +425,15 @@ | |
| 420 | @ a regular file with contents |
| 421 | } |
| 422 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 423 | } |
| 424 | }else if( zOld ){ |
| 425 | @ Deleted %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a> |
| 426 | @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>. |
| 427 | }else{ |
| 428 | @ Added %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a> |
| 429 | @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 430 | } |
| 431 | if( diffFlags ){ |
| 432 | append_diff(zOld, zNew, diffFlags, pRe); |
| 433 | }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 434 | @ |
| @@ -929,11 +934,12 @@ | |
| 929 | const char *zName = db_column_text(&q3,0); |
| 930 | int mperm = db_column_int(&q3, 1); |
| 931 | const char *zOld = db_column_text(&q3,2); |
| 932 | const char *zNew = db_column_text(&q3,3); |
| 933 | const char *zOldName = db_column_text(&q3, 4); |
| 934 | append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm); |
| 935 | } |
| 936 | db_finalize(&q3); |
| 937 | append_diff_javascript(diffType==2); |
| 938 | cookie_render(); |
| 939 | style_footer(); |
| @@ -1292,17 +1298,17 @@ | |
| 1292 | }else{ |
| 1293 | cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); |
| 1294 | } |
| 1295 | if( cmp<0 ){ |
| 1296 | if( !zGlob || sqlite3_strglob(zGlob, pFileFrom->zName)==0 ){ |
| 1297 | append_file_change_line(pFileFrom->zName, |
| 1298 | pFileFrom->zUuid, 0, 0, diffFlags, pRe, 0); |
| 1299 | } |
| 1300 | pFileFrom = manifest_file_next(pFrom, 0); |
| 1301 | }else if( cmp>0 ){ |
| 1302 | if( !zGlob || sqlite3_strglob(zGlob, pFileTo->zName)==0 ){ |
| 1303 | append_file_change_line(pFileTo->zName, |
| 1304 | 0, pFileTo->zUuid, 0, diffFlags, pRe, |
| 1305 | manifest_file_mperm(pFileTo)); |
| 1306 | } |
| 1307 | pFileTo = manifest_file_next(pTo, 0); |
| 1308 | }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ |
| @@ -1309,11 +1315,11 @@ | |
| 1309 | pFileFrom = manifest_file_next(pFrom, 0); |
| 1310 | pFileTo = manifest_file_next(pTo, 0); |
| 1311 | }else{ |
| 1312 | if(!zGlob || (sqlite3_strglob(zGlob, pFileFrom->zName)==0 |
| 1313 | || sqlite3_strglob(zGlob, pFileTo->zName)==0) ){ |
| 1314 | append_file_change_line(pFileFrom->zName, |
| 1315 | pFileFrom->zUuid, |
| 1316 | pFileTo->zUuid, 0, diffFlags, pRe, |
| 1317 | manifest_file_mperm(pFileTo)); |
| 1318 | } |
| 1319 | pFileFrom = manifest_file_next(pFrom, 0); |
| @@ -1419,11 +1425,12 @@ | |
| 1419 | bNeedBase = 0; |
| 1420 | style_set_current_page("doc/%S/%s",zVers,zName); |
| 1421 | } |
| 1422 | } |
| 1423 | objType |= OBJTYPE_CONTENT; |
| 1424 | @ %z(href("%R/finfo?name=%T&m=%!S",zName,zUuid))%h(zName)</a> |
| 1425 | tag_private_status(rid); |
| 1426 | if( showDetail ){ |
| 1427 | @ <ul> |
| 1428 | } |
| 1429 | prevName = fossil_strdup(zName); |
| @@ -2311,11 +2318,11 @@ | |
| 2311 | |
| 2312 | asText = P("txt")!=0; |
| 2313 | if( isFile ){ |
| 2314 | if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2315 | zCI = "tip"; |
| 2316 | @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a> |
| 2317 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2318 | }else{ |
| 2319 | const char *zPath; |
| 2320 | Blob path; |
| 2321 | blob_zero(&path); |
| 2322 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -368,10 +368,11 @@ | |
| 368 | /* |
| 369 | ** Write a line of web-page output that shows changes that have occurred |
| 370 | ** to a file between two check-ins. |
| 371 | */ |
| 372 | static void append_file_change_line( |
| 373 | const char *zCkin, /* The checkin on which the change occurs */ |
| 374 | const char *zName, /* Name of the file that has changed */ |
| 375 | const char *zOld, /* blob.uuid before change. NULL for added files */ |
| 376 | const char *zNew, /* blob.uuid after change. NULL for deletes */ |
| 377 | const char *zOldName, /* Prior name. NULL if no name change. */ |
| 378 | u64 diffFlags, /* Flags for text_diff(). Zero to omit diffs */ |
| @@ -401,19 +402,23 @@ | |
| 402 | append_diff(zOld, zNew, diffFlags, pRe); |
| 403 | } |
| 404 | }else{ |
| 405 | if( zOld && zNew ){ |
| 406 | if( fossil_strcmp(zOld, zNew)!=0 ){ |
| 407 | @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 408 | @ %h(zName)</a> |
| 409 | @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 410 | @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 411 | }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 412 | @ Name change |
| 413 | @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\ |
| 414 | @ %h(zOldName)</a> |
| 415 | @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 416 | @ %h(zName)</a>. |
| 417 | }else{ |
| 418 | @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 419 | @ %h(zName)</a> became |
| 420 | if( mperm==PERM_EXE ){ |
| 421 | @ executable with contents |
| 422 | }else if( mperm==PERM_LNK ){ |
| 423 | @ a symlink with target |
| 424 | }else{ |
| @@ -420,15 +425,15 @@ | |
| 425 | @ a regular file with contents |
| 426 | } |
| 427 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 428 | } |
| 429 | }else if( zOld ){ |
| 430 | @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zOld,zCkin))\ |
| 431 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>. |
| 432 | }else{ |
| 433 | @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 434 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 435 | } |
| 436 | if( diffFlags ){ |
| 437 | append_diff(zOld, zNew, diffFlags, pRe); |
| 438 | }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 439 | @ |
| @@ -929,11 +934,12 @@ | |
| 934 | const char *zName = db_column_text(&q3,0); |
| 935 | int mperm = db_column_int(&q3, 1); |
| 936 | const char *zOld = db_column_text(&q3,2); |
| 937 | const char *zNew = db_column_text(&q3,3); |
| 938 | const char *zOldName = db_column_text(&q3, 4); |
| 939 | append_file_change_line(zUuid, zName, zOld, zNew, zOldName, |
| 940 | diffFlags,pRe,mperm); |
| 941 | } |
| 942 | db_finalize(&q3); |
| 943 | append_diff_javascript(diffType==2); |
| 944 | cookie_render(); |
| 945 | style_footer(); |
| @@ -1292,17 +1298,17 @@ | |
| 1298 | }else{ |
| 1299 | cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); |
| 1300 | } |
| 1301 | if( cmp<0 ){ |
| 1302 | if( !zGlob || sqlite3_strglob(zGlob, pFileFrom->zName)==0 ){ |
| 1303 | append_file_change_line(zFrom, pFileFrom->zName, |
| 1304 | pFileFrom->zUuid, 0, 0, diffFlags, pRe, 0); |
| 1305 | } |
| 1306 | pFileFrom = manifest_file_next(pFrom, 0); |
| 1307 | }else if( cmp>0 ){ |
| 1308 | if( !zGlob || sqlite3_strglob(zGlob, pFileTo->zName)==0 ){ |
| 1309 | append_file_change_line(zTo, pFileTo->zName, |
| 1310 | 0, pFileTo->zUuid, 0, diffFlags, pRe, |
| 1311 | manifest_file_mperm(pFileTo)); |
| 1312 | } |
| 1313 | pFileTo = manifest_file_next(pTo, 0); |
| 1314 | }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ |
| @@ -1309,11 +1315,11 @@ | |
| 1315 | pFileFrom = manifest_file_next(pFrom, 0); |
| 1316 | pFileTo = manifest_file_next(pTo, 0); |
| 1317 | }else{ |
| 1318 | if(!zGlob || (sqlite3_strglob(zGlob, pFileFrom->zName)==0 |
| 1319 | || sqlite3_strglob(zGlob, pFileTo->zName)==0) ){ |
| 1320 | append_file_change_line(zFrom, pFileFrom->zName, |
| 1321 | pFileFrom->zUuid, |
| 1322 | pFileTo->zUuid, 0, diffFlags, pRe, |
| 1323 | manifest_file_mperm(pFileTo)); |
| 1324 | } |
| 1325 | pFileFrom = manifest_file_next(pFrom, 0); |
| @@ -1419,11 +1425,12 @@ | |
| 1425 | bNeedBase = 0; |
| 1426 | style_set_current_page("doc/%S/%s",zVers,zName); |
| 1427 | } |
| 1428 | } |
| 1429 | objType |= OBJTYPE_CONTENT; |
| 1430 | @ %z(href("%R/finfo?name=%T&ci=%!S&m=%!S",zName,zVers,zUuid))\ |
| 1431 | @ %h(zName)</a> |
| 1432 | tag_private_status(rid); |
| 1433 | if( showDetail ){ |
| 1434 | @ <ul> |
| 1435 | } |
| 1436 | prevName = fossil_strdup(zName); |
| @@ -2311,11 +2318,11 @@ | |
| 2318 | |
| 2319 | asText = P("txt")!=0; |
| 2320 | if( isFile ){ |
| 2321 | if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2322 | zCI = "tip"; |
| 2323 | @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a> |
| 2324 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2325 | }else{ |
| 2326 | const char *zPath; |
| 2327 | Blob path; |
| 2328 | blob_zero(&path); |
| 2329 |