Fossil SCM
Merged in trunk for various menu item fixes before deployment to my server.
Commit
8cc7953b9a709167f019fc7fe8242aef19e9f0dc5140547050509884af8a6021
Parent
2bec2c758f5a1a1…
10 files changed
+14
+119
-79
+17
-11
+4
-3
+4
-3
+140
-137
+140
-137
+38
+2
-2
+10
+14
| --- src/branch.c | ||
| +++ src/branch.c | ||
| @@ -18,10 +18,24 @@ | ||
| 18 | 18 | ** This file contains code used to create new branches within a repository. |
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include "branch.h" |
| 22 | 22 | #include <assert.h> |
| 23 | + | |
| 24 | +/* | |
| 25 | +** Return true if zBr is the branch name associated with check-in with | |
| 26 | +** blob.uuid value of zUuid | |
| 27 | +*/ | |
| 28 | +int branch_includes_uuid(const char *zBr, const char *zUuid){ | |
| 29 | + return db_exists( | |
| 30 | + "SELECT 1 FROM tagxref, blob" | |
| 31 | + " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid" | |
| 32 | + " AND tagxref.value=%Q AND tagxref.tagtype>0" | |
| 33 | + " AND tagxref.tagid=%d", | |
| 34 | + zUuid, zBr, TAG_BRANCH | |
| 35 | + ); | |
| 36 | +} | |
| 23 | 37 | |
| 24 | 38 | /* |
| 25 | 39 | ** If RID refers to a check-in, return the name of the branch for that |
| 26 | 40 | ** check-in. |
| 27 | 41 | ** |
| 28 | 42 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -18,10 +18,24 @@ | |
| 18 | ** This file contains code used to create new branches within a repository. |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include "branch.h" |
| 22 | #include <assert.h> |
| 23 | |
| 24 | /* |
| 25 | ** If RID refers to a check-in, return the name of the branch for that |
| 26 | ** check-in. |
| 27 | ** |
| 28 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -18,10 +18,24 @@ | |
| 18 | ** This file contains code used to create new branches within a repository. |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include "branch.h" |
| 22 | #include <assert.h> |
| 23 | |
| 24 | /* |
| 25 | ** Return true if zBr is the branch name associated with check-in with |
| 26 | ** blob.uuid value of zUuid |
| 27 | */ |
| 28 | int branch_includes_uuid(const char *zBr, const char *zUuid){ |
| 29 | return db_exists( |
| 30 | "SELECT 1 FROM tagxref, blob" |
| 31 | " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid" |
| 32 | " AND tagxref.value=%Q AND tagxref.tagtype>0" |
| 33 | " AND tagxref.tagid=%d", |
| 34 | zUuid, zBr, TAG_BRANCH |
| 35 | ); |
| 36 | } |
| 37 | |
| 38 | /* |
| 39 | ** If RID refers to a check-in, return the name of the branch for that |
| 40 | ** check-in. |
| 41 | ** |
| 42 |
+119
-79
| --- src/browse.c | ||
| +++ src/browse.c | ||
| @@ -59,10 +59,18 @@ | ||
| 59 | 59 | zOut = sqlite3_mprintf("/%.*s", i-n, &z[n]); |
| 60 | 60 | sqlite3_result_text(context, zOut, i-n+1, sqlite3_free); |
| 61 | 61 | } |
| 62 | 62 | } |
| 63 | 63 | |
| 64 | +/* | |
| 65 | +** Flag arguments for hyperlinked_path() | |
| 66 | +*/ | |
| 67 | +#if INTERFACE | |
| 68 | +# define LINKPATH_FINFO 0x0001 /* Link final term to /finfo */ | |
| 69 | +# define LINKPATH_FILE 0x0002 /* Link final term to /file */ | |
| 70 | +#endif | |
| 71 | + | |
| 64 | 72 | /* |
| 65 | 73 | ** Given a pathname which is a relative path from the root of |
| 66 | 74 | ** the repository to a file or directory, compute a string which |
| 67 | 75 | ** is an HTML rendering of that path with hyperlinks on each |
| 68 | 76 | ** directory component of the path where the hyperlink redirects |
| @@ -76,29 +84,36 @@ | ||
| 76 | 84 | void hyperlinked_path( |
| 77 | 85 | const char *zPath, /* Path to render */ |
| 78 | 86 | Blob *pOut, /* Write into this blob */ |
| 79 | 87 | const char *zCI, /* check-in name, or NULL */ |
| 80 | 88 | const char *zURI, /* "dir" or "tree" */ |
| 81 | - const char *zREx /* Extra query parameters */ | |
| 89 | + const char *zREx, /* Extra query parameters */ | |
| 90 | + unsigned int mFlags /* Extra flags */ | |
| 82 | 91 | ){ |
| 83 | 92 | int i, j; |
| 84 | 93 | char *zSep = ""; |
| 85 | 94 | |
| 86 | 95 | for(i=0; zPath[i]; i=j){ |
| 87 | 96 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 88 | - if( zPath[j] && g.perm.Hyperlink ){ | |
| 89 | - if( zCI ){ | |
| 90 | - char *zLink = href("%R/%s?name=%#T%s&ci=%!S", zURI, j, zPath, zREx,zCI); | |
| 91 | - blob_appendf(pOut, "%s%z%#h</a>", | |
| 92 | - zSep, zLink, j-i, &zPath[i]); | |
| 93 | - }else{ | |
| 94 | - char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); | |
| 95 | - blob_appendf(pOut, "%s%z%#h</a>", | |
| 96 | - zSep, zLink, j-i, &zPath[i]); | |
| 97 | - } | |
| 98 | - }else{ | |
| 99 | - blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); | |
| 97 | + if( zPath[j]==0 ){ | |
| 98 | + if( mFlags & LINKPATH_FILE ){ | |
| 99 | + zURI = "file"; | |
| 100 | + }else if( mFlags & LINKPATH_FINFO ){ | |
| 101 | + zURI = "finfo"; | |
| 102 | + }else{ | |
| 103 | + blob_appendf(pOut, "/%h", zPath+i); | |
| 104 | + break; | |
| 105 | + } | |
| 106 | + } | |
| 107 | + if( zCI ){ | |
| 108 | + char *zLink = href("%R/%s?name=%#T%s&ci=%T", zURI, j, zPath, zREx,zCI); | |
| 109 | + blob_appendf(pOut, "%s%z%#h</a>", | |
| 110 | + zSep, zLink, j-i, &zPath[i]); | |
| 111 | + }else{ | |
| 112 | + char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); | |
| 113 | + blob_appendf(pOut, "%s%z%#h</a>", | |
| 114 | + zSep, zLink, j-i, &zPath[i]); | |
| 100 | 115 | } |
| 101 | 116 | zSep = "/"; |
| 102 | 117 | while( zPath[j]=='/' ){ j++; } |
| 103 | 118 | } |
| 104 | 119 | } |
| @@ -127,17 +142,17 @@ | ||
| 127 | 142 | char *zPrefix; |
| 128 | 143 | Stmt q; |
| 129 | 144 | const char *zCI = P("ci"); |
| 130 | 145 | int rid = 0; |
| 131 | 146 | char *zUuid = 0; |
| 132 | - Blob dirname; | |
| 133 | 147 | Manifest *pM = 0; |
| 134 | 148 | const char *zSubdirLink; |
| 135 | 149 | int linkTrunk = 1; |
| 136 | 150 | int linkTip = 1; |
| 137 | 151 | HQuery sURI; |
| 138 | 152 | int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ |
| 153 | + int isBranchCI = 0; /* True if ci= refers to a branch name */ | |
| 139 | 154 | char *zHeader = 0; |
| 140 | 155 | |
| 141 | 156 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 142 | 157 | if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; } |
| 143 | 158 | login_check_credentials(); |
| @@ -157,22 +172,29 @@ | ||
| 157 | 172 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 158 | 173 | linkTrunk = trunkRid && rid != trunkRid; |
| 159 | 174 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 160 | 175 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 161 | 176 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 177 | + isBranchCI = branch_includes_uuid(zCI, zUuid); | |
| 162 | 178 | }else{ |
| 163 | 179 | zCI = 0; |
| 164 | 180 | } |
| 165 | 181 | } |
| 166 | 182 | |
| 167 | 183 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 168 | - if( isSymbolicCI ) { | |
| 169 | - zHeader = mprintf("%s at %s", (zD ? zD : "Files"), zCI); | |
| 170 | - }else if( zUuid && strlen(zUuid) ){ | |
| 171 | - zHeader = mprintf("%s at [%S]", (zD ? zD : "Files"), zUuid); | |
| 184 | + if( zD==0 ){ | |
| 185 | + if( zCI ){ | |
| 186 | + zHeader = mprintf("Top-level Files of %s", zCI); | |
| 187 | + }else{ | |
| 188 | + zHeader = mprintf("All Top-level Files"); | |
| 189 | + } | |
| 172 | 190 | }else{ |
| 173 | - zHeader = mprintf("%s", (zD ? zD : "All Files")); | |
| 191 | + if( zCI ){ | |
| 192 | + zHeader = mprintf("Files in %s/ of %s", zD, zCI); | |
| 193 | + }else{ | |
| 194 | + zHeader = mprintf("All File in %s/", zD); | |
| 195 | + } | |
| 174 | 196 | } |
| 175 | 197 | style_header("%s", zHeader); |
| 176 | 198 | fossil_free(zHeader); |
| 177 | 199 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 178 | 200 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| @@ -179,47 +201,49 @@ | ||
| 179 | 201 | pathelementFunc, 0, 0); |
| 180 | 202 | url_initialize(&sURI, "dir"); |
| 181 | 203 | cgi_query_parameters_to_url(&sURI); |
| 182 | 204 | |
| 183 | 205 | /* Compute the title of the page */ |
| 184 | - blob_zero(&dirname); | |
| 185 | 206 | if( zD ){ |
| 186 | - blob_append(&dirname, "in directory ", -1); | |
| 187 | - hyperlinked_path(zD, &dirname, zCI, "dir", ""); | |
| 207 | + Blob dirname; | |
| 208 | + blob_init(&dirname, 0, 0); | |
| 209 | + hyperlinked_path(zD, &dirname, zCI, "dir", "", 0); | |
| 210 | + @ <h2>Files in directory %s(blob_str(&dirname)) \ | |
| 211 | + blob_reset(&dirname); | |
| 188 | 212 | zPrefix = mprintf("%s/", zD); |
| 189 | 213 | style_submenu_element("Top-Level", "%s", |
| 190 | 214 | url_render(&sURI, "name", 0, 0, 0)); |
| 191 | 215 | }else{ |
| 192 | - blob_append(&dirname, "in the top-level directory", -1); | |
| 216 | + @ <h2>Files in the top-level directory \ | |
| 193 | 217 | zPrefix = ""; |
| 194 | 218 | } |
| 219 | + if( zCI ){ | |
| 220 | + if( fossil_strcmp(zCI,"tip")==0 ){ | |
| 221 | + @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a></h2> | |
| 222 | + }else if( isBranchCI ){ | |
| 223 | + @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a> \ | |
| 224 | + @ of branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> | |
| 225 | + }else { | |
| 226 | + @ of check-in %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2> | |
| 227 | + } | |
| 228 | + zSubdirLink = mprintf("%R/dir?ci=%T&name=%T", zCI, zPrefix); | |
| 229 | + if( nD==0 ){ | |
| 230 | + style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); | |
| 231 | + } | |
| 232 | + }else{ | |
| 233 | + @ in any check-in</h2> | |
| 234 | + zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); | |
| 235 | + } | |
| 195 | 236 | if( linkTrunk ){ |
| 196 | 237 | style_submenu_element("Trunk", "%s", |
| 197 | 238 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| 198 | 239 | } |
| 199 | 240 | if( linkTip ){ |
| 200 | 241 | style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); |
| 201 | 242 | } |
| 202 | - if( zCI ){ | |
| 203 | - @ <h2>Files at check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] | |
| 204 | - @ %s(blob_str(&dirname)) | |
| 205 | - if( zD ){ | |
| 206 | - @ %z(href("%R/timeline?chng=%T/*", zD))[history]</a> | |
| 207 | - } | |
| 208 | - @ </h2> | |
| 209 | - zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix); | |
| 210 | - if( nD==0 ){ | |
| 211 | - style_submenu_element("File Ages", "%R/fileage?name=%!S", zUuid); | |
| 212 | - } | |
| 213 | - }else{ | |
| 214 | - @ <h2>All files known in the repository | |
| 215 | - @ %s(blob_str(&dirname)) | |
| 216 | - if( zD ){ | |
| 217 | - @ %z(href("%R/timeline?chng=%T/*", zD))[history]</a> | |
| 218 | - } | |
| 219 | - @ </h2> | |
| 220 | - zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); | |
| 243 | + if( zD ){ | |
| 244 | + style_submenu_element("History","%R/timeline?chng=%T/*", zD); | |
| 221 | 245 | } |
| 222 | 246 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 223 | 247 | style_submenu_element("Tree-View", "%s", |
| 224 | 248 | url_render(&sURI, "type", "tree", 0, 0)); |
| 225 | 249 | |
| @@ -296,11 +320,11 @@ | ||
| 296 | 320 | zFN++; |
| 297 | 321 | @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li> |
| 298 | 322 | }else{ |
| 299 | 323 | const char *zLink; |
| 300 | 324 | if( zCI ){ |
| 301 | - zLink = href("%R/file?name=%T%T&ci=%!S",zPrefix,zFN,zCI); | |
| 325 | + zLink = href("%R/file?name=%T%T&ci=%T",zPrefix,zFN,zCI); | |
| 302 | 326 | }else{ |
| 303 | 327 | zLink = href("%R/finfo?name=%T%T",zPrefix,zFN); |
| 304 | 328 | } |
| 305 | 329 | @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li> |
| 306 | 330 | } |
| @@ -626,10 +650,11 @@ | ||
| 626 | 650 | int startExpanded; /* True to start out with the tree expanded */ |
| 627 | 651 | int showDirOnly; /* Show directories only. Omit files */ |
| 628 | 652 | int nDir = 0; /* Number of directories. Used for ID attributes */ |
| 629 | 653 | char *zProjectName = db_get("project-name", 0); |
| 630 | 654 | int isSymbolicCI = 0; /* ci= is a symbolic name, not a hash prefix */ |
| 655 | + int isBranchCI = 0; /* ci= refers to a branch name */ | |
| 631 | 656 | char *zHeader = 0; |
| 632 | 657 | |
| 633 | 658 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 634 | 659 | if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; } |
| 635 | 660 | memset(&sTree, 0, sizeof(sTree)); |
| @@ -675,10 +700,11 @@ | ||
| 675 | 700 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 676 | 701 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 677 | 702 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 678 | 703 | " FROM event WHERE objid=%d", rid); |
| 679 | 704 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| 705 | + isBranchCI = branch_includes_uuid(zCI, zUuid); | |
| 680 | 706 | }else{ |
| 681 | 707 | zCI = 0; |
| 682 | 708 | } |
| 683 | 709 | } |
| 684 | 710 | if( zCI==0 ){ |
| @@ -685,41 +711,42 @@ | ||
| 685 | 711 | rNow = db_double(0.0, "SELECT max(mtime) FROM event"); |
| 686 | 712 | zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); |
| 687 | 713 | } |
| 688 | 714 | |
| 689 | 715 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 690 | - if( isSymbolicCI ) { | |
| 691 | - zHeader = mprintf("%s at %s", | |
| 692 | - (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zCI); | |
| 693 | - }else if( zUuid && strlen(zUuid) ){ | |
| 694 | - zHeader = mprintf("%s at [%S]", | |
| 695 | - (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zUuid); | |
| 716 | + if( zD==0 ){ | |
| 717 | + if( zCI ){ | |
| 718 | + zHeader = mprintf("Top-level Files of %s", zCI); | |
| 719 | + }else{ | |
| 720 | + zHeader = mprintf("All Top-level Files"); | |
| 721 | + } | |
| 696 | 722 | }else{ |
| 697 | - zHeader = mprintf("%s", | |
| 698 | - (zD ? zD : (showDirOnly ?"All Folders Hierarchy":"All Files Tree-View"))); | |
| 723 | + if( zCI ){ | |
| 724 | + zHeader = mprintf("Files in %s/ of %s", zD, zCI); | |
| 725 | + }else{ | |
| 726 | + zHeader = mprintf("All File in %s/", zD); | |
| 727 | + } | |
| 699 | 728 | } |
| 700 | 729 | style_header("%s", zHeader); |
| 701 | 730 | fossil_free(zHeader); |
| 702 | 731 | |
| 703 | 732 | /* Compute the title of the page */ |
| 704 | 733 | blob_zero(&dirname); |
| 705 | 734 | if( zD ){ |
| 706 | 735 | blob_append(&dirname, "within directory ", -1); |
| 707 | - hyperlinked_path(zD, &dirname, zCI, "tree", zREx); | |
| 736 | + hyperlinked_path(zD, &dirname, zCI, "tree", zREx, 0); | |
| 708 | 737 | if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); |
| 709 | 738 | style_submenu_element("Top-Level", "%s", |
| 710 | 739 | url_render(&sURI, "name", 0, 0, 0)); |
| 711 | - }else{ | |
| 712 | - if( zRE ){ | |
| 713 | - blob_appendf(&dirname, "matching \"%s\"", zRE); | |
| 714 | - } | |
| 740 | + }else if( zRE ){ | |
| 741 | + blob_appendf(&dirname, "matching \"%s\"", zRE); | |
| 715 | 742 | } |
| 716 | 743 | style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0); |
| 717 | 744 | if( zCI ){ |
| 718 | 745 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 719 | 746 | if( nD==0 && !showDirOnly ){ |
| 720 | - style_submenu_element("File Ages", "%R/fileage?name=%s", zUuid); | |
| 747 | + style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); | |
| 721 | 748 | } |
| 722 | 749 | } |
| 723 | 750 | if( linkTrunk ){ |
| 724 | 751 | style_submenu_element("Trunk", "%s", |
| 725 | 752 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| @@ -774,10 +801,11 @@ | ||
| 774 | 801 | tree_add_node(&sTree, zName, zUuid, mtime); |
| 775 | 802 | nFile++; |
| 776 | 803 | } |
| 777 | 804 | db_finalize(&q); |
| 778 | 805 | } |
| 806 | + style_submenu_checkbox("nofiles", "Folders Only", 0, 0); | |
| 779 | 807 | |
| 780 | 808 | if( showDirOnly ){ |
| 781 | 809 | for(nFile=0, p=sTree.pFirst; p; p=p->pNext){ |
| 782 | 810 | if( p->pChild!=0 && p->nFullName>nD ) nFile++; |
| 783 | 811 | } |
| @@ -784,18 +812,24 @@ | ||
| 784 | 812 | zObjType = "Folders"; |
| 785 | 813 | }else{ |
| 786 | 814 | zObjType = "Files"; |
| 787 | 815 | } |
| 788 | 816 | |
| 789 | - style_submenu_checkbox("nofiles", "Folders Only", 0, 0); | |
| 790 | - | |
| 791 | - if( zCI ){ | |
| 792 | - @ <h2>%s(zObjType) at check-in | |
| 793 | - if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){ | |
| 794 | - @ "%h(zCI)" | |
| 795 | - } | |
| 796 | - @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname)) | |
| 817 | + if( zCI && strcmp(zCI,"tip")==0 ){ | |
| 818 | + @ <h2>%s(zObjType) in the %z(href("%R/info?name=tip"))latest check-in</a> | |
| 819 | + }else if( isBranchCI ){ | |
| 820 | + @ <h2>%s(zObjType) in the %z(href("%R/info?name=%T",zCI))latest check-in\ | |
| 821 | + @ </a> for branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a> | |
| 822 | + if( blob_size(&dirname) ){ | |
| 823 | + @ and %s(blob_str(&dirname))</h2> | |
| 824 | + } | |
| 825 | + }else if( zCI ){ | |
| 826 | + @ <h2>%s(zObjType) for check-in \ | |
| 827 | + @ %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2> | |
| 828 | + if( blob_size(&dirname) ){ | |
| 829 | + @ and %s(blob_str(&dirname))</h2> | |
| 830 | + } | |
| 797 | 831 | }else{ |
| 798 | 832 | int n = db_int(0, "SELECT count(*) FROM plink"); |
| 799 | 833 | @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname)) |
| 800 | 834 | } |
| 801 | 835 | if( useMtime ){ |
| @@ -853,11 +887,11 @@ | ||
| 853 | 887 | nDir++; |
| 854 | 888 | }else if( !showDirOnly ){ |
| 855 | 889 | const char *zFileClass = fileext_class(p->zName); |
| 856 | 890 | char *zLink; |
| 857 | 891 | if( zCI ){ |
| 858 | - zLink = href("%R/file?name=%T&ci=%!S",p->zFullName,zCI); | |
| 892 | + zLink = href("%R/file?name=%T&ci=%T",p->zFullName,zCI); | |
| 859 | 893 | }else{ |
| 860 | 894 | zLink = href("%R/finfo?name=%T",p->zFullName); |
| 861 | 895 | } |
| 862 | 896 | @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline"> |
| 863 | 897 | @ %z(zLink)%h(p->zName)</a> |
| @@ -1030,10 +1064,11 @@ | ||
| 1030 | 1064 | int rid; |
| 1031 | 1065 | const char *zName; |
| 1032 | 1066 | const char *zGlob; |
| 1033 | 1067 | const char *zUuid; |
| 1034 | 1068 | const char *zNow; /* Time of check-in */ |
| 1069 | + int isBranchCI; /* name= is a branch name */ | |
| 1035 | 1070 | int showId = PB("showid"); |
| 1036 | 1071 | Stmt q1, q2; |
| 1037 | 1072 | double baseTime; |
| 1038 | 1073 | login_check_credentials(); |
| 1039 | 1074 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| @@ -1043,28 +1078,34 @@ | ||
| 1043 | 1078 | rid = symbolic_name_to_rid(zName, "ci"); |
| 1044 | 1079 | if( rid==0 ){ |
| 1045 | 1080 | fossil_fatal("not a valid check-in: %s", zName); |
| 1046 | 1081 | } |
| 1047 | 1082 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1083 | + isBranchCI = branch_includes_uuid(zName,zUuid); | |
| 1048 | 1084 | baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); |
| 1049 | 1085 | zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event" |
| 1050 | 1086 | " WHERE objid=%d", rid); |
| 1051 | 1087 | style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName); |
| 1052 | 1088 | style_header("File Ages"); |
| 1053 | 1089 | zGlob = P("glob"); |
| 1054 | 1090 | compute_fileage(rid,zGlob); |
| 1055 | 1091 | db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); |
| 1056 | 1092 | |
| 1057 | - @ <h1>Files in | |
| 1058 | - @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a> | |
| 1093 | + if( fossil_strcmp(zName,"tip")==0 ){ | |
| 1094 | + @ <h1>Files in the %z(href("%R/info?name=tip"))latest check-in</a> | |
| 1095 | + }else if( isBranchCI ){ | |
| 1096 | + @ <h1>Files in the %z(href("%R/info?name=%T",zName))latest check-in</a> | |
| 1097 | + @ of branch %z(href("%R/timeline?r=%T",zName))%h(zName)</a> | |
| 1098 | + }else{ | |
| 1099 | + @ <h1>Files in check-in %z(href("%R/info?name=%T",zName))%h(zName)</a> | |
| 1100 | + } | |
| 1059 | 1101 | if( zGlob && zGlob[0] ){ |
| 1060 | 1102 | @ that match "%h(zGlob)" |
| 1061 | 1103 | } |
| 1062 | 1104 | @ ordered by age</h1> |
| 1063 | 1105 | @ |
| 1064 | - @ <p>File ages are expressed relative to the | |
| 1065 | - @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> check-in time of | |
| 1106 | + @ <p>File ages are expressed relative to the check-in time of | |
| 1066 | 1107 | @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p> |
| 1067 | 1108 | @ |
| 1068 | 1109 | @ <div class='fileage'><table> |
| 1069 | 1110 | @ <tr><th>Age</th><th>Files</th><th>Check-in</th></tr> |
| 1070 | 1111 | db_prepare(&q1, |
| @@ -1079,14 +1120,13 @@ | ||
| 1079 | 1120 | " AND blob.rid=event.objid\n" |
| 1080 | 1121 | " ORDER BY event.mtime DESC;", |
| 1081 | 1122 | TAG_BRANCH |
| 1082 | 1123 | ); |
| 1083 | 1124 | db_prepare(&q2, |
| 1084 | - "SELECT blob.uuid, filename.name, fileage.fid\n" | |
| 1085 | - " FROM fileage, blob, filename\n" | |
| 1125 | + "SELECT filename.name, fileage.fid\n" | |
| 1126 | + " FROM fileage, filename\n" | |
| 1086 | 1127 | " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid" |
| 1087 | - " AND blob.rid=fileage.fid;" | |
| 1088 | 1128 | ); |
| 1089 | 1129 | while( db_step(&q1)==SQLITE_ROW ){ |
| 1090 | 1130 | double age = baseTime - db_column_double(&q1, 0); |
| 1091 | 1131 | int mid = db_column_int(&q1, 1); |
| 1092 | 1132 | const char *zUuid = db_column_text(&q1, 2); |
| @@ -1096,24 +1136,24 @@ | ||
| 1096 | 1136 | char *zAge = human_readable_age(age); |
| 1097 | 1137 | @ <tr><td>%s(zAge)</td> |
| 1098 | 1138 | @ <td> |
| 1099 | 1139 | db_bind_int(&q2, ":mid", mid); |
| 1100 | 1140 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1101 | - const char *zFUuid = db_column_text(&q2,0); | |
| 1102 | - const char *zFile = db_column_text(&q2,1); | |
| 1103 | - int fid = db_column_int(&q2,2); | |
| 1141 | + const char *zFile = db_column_text(&q2,0); | |
| 1142 | + @ %z(href("%R/file?name=%T&ci=%!S",zFile,zUuid))%h(zFile)</a> \ | |
| 1104 | 1143 | if( showId ){ |
| 1105 | - @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a> (%d(fid))<br /> | |
| 1144 | + int fid = db_column_int(&q2,1); | |
| 1145 | + @ (%d(fid))<br /> | |
| 1106 | 1146 | }else{ |
| 1107 | - @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a><br /> | |
| 1147 | + @ </a><br /> | |
| 1108 | 1148 | } |
| 1109 | 1149 | } |
| 1110 | 1150 | db_reset(&q2); |
| 1111 | 1151 | @ </td> |
| 1112 | 1152 | @ <td> |
| 1113 | 1153 | @ %W(zComment) |
| 1114 | - @ (check-in: %z(href("%R/ci/%!S",zUuid))%S(zUuid)</a>, | |
| 1154 | + @ (check-in: %z(href("%R/info/%!S",zUuid))%S(zUuid)</a>, | |
| 1115 | 1155 | if( showId ){ |
| 1116 | 1156 | @ id: %d(mid) |
| 1117 | 1157 | } |
| 1118 | 1158 | @ user: %z(href("%R/timeline?u=%t&c=%!S&nd",zUser,zUuid))%h(zUser)</a>, |
| 1119 | 1159 | @ branch: \ |
| 1120 | 1160 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -59,10 +59,18 @@ | |
| 59 | zOut = sqlite3_mprintf("/%.*s", i-n, &z[n]); |
| 60 | sqlite3_result_text(context, zOut, i-n+1, sqlite3_free); |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | /* |
| 65 | ** Given a pathname which is a relative path from the root of |
| 66 | ** the repository to a file or directory, compute a string which |
| 67 | ** is an HTML rendering of that path with hyperlinks on each |
| 68 | ** directory component of the path where the hyperlink redirects |
| @@ -76,29 +84,36 @@ | |
| 76 | void hyperlinked_path( |
| 77 | const char *zPath, /* Path to render */ |
| 78 | Blob *pOut, /* Write into this blob */ |
| 79 | const char *zCI, /* check-in name, or NULL */ |
| 80 | const char *zURI, /* "dir" or "tree" */ |
| 81 | const char *zREx /* Extra query parameters */ |
| 82 | ){ |
| 83 | int i, j; |
| 84 | char *zSep = ""; |
| 85 | |
| 86 | for(i=0; zPath[i]; i=j){ |
| 87 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 88 | if( zPath[j] && g.perm.Hyperlink ){ |
| 89 | if( zCI ){ |
| 90 | char *zLink = href("%R/%s?name=%#T%s&ci=%!S", zURI, j, zPath, zREx,zCI); |
| 91 | blob_appendf(pOut, "%s%z%#h</a>", |
| 92 | zSep, zLink, j-i, &zPath[i]); |
| 93 | }else{ |
| 94 | char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); |
| 95 | blob_appendf(pOut, "%s%z%#h</a>", |
| 96 | zSep, zLink, j-i, &zPath[i]); |
| 97 | } |
| 98 | }else{ |
| 99 | blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); |
| 100 | } |
| 101 | zSep = "/"; |
| 102 | while( zPath[j]=='/' ){ j++; } |
| 103 | } |
| 104 | } |
| @@ -127,17 +142,17 @@ | |
| 127 | char *zPrefix; |
| 128 | Stmt q; |
| 129 | const char *zCI = P("ci"); |
| 130 | int rid = 0; |
| 131 | char *zUuid = 0; |
| 132 | Blob dirname; |
| 133 | Manifest *pM = 0; |
| 134 | const char *zSubdirLink; |
| 135 | int linkTrunk = 1; |
| 136 | int linkTip = 1; |
| 137 | HQuery sURI; |
| 138 | int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ |
| 139 | char *zHeader = 0; |
| 140 | |
| 141 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 142 | if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; } |
| 143 | login_check_credentials(); |
| @@ -157,22 +172,29 @@ | |
| 157 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 158 | linkTrunk = trunkRid && rid != trunkRid; |
| 159 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 160 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 161 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 162 | }else{ |
| 163 | zCI = 0; |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 168 | if( isSymbolicCI ) { |
| 169 | zHeader = mprintf("%s at %s", (zD ? zD : "Files"), zCI); |
| 170 | }else if( zUuid && strlen(zUuid) ){ |
| 171 | zHeader = mprintf("%s at [%S]", (zD ? zD : "Files"), zUuid); |
| 172 | }else{ |
| 173 | zHeader = mprintf("%s", (zD ? zD : "All Files")); |
| 174 | } |
| 175 | style_header("%s", zHeader); |
| 176 | fossil_free(zHeader); |
| 177 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 178 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| @@ -179,47 +201,49 @@ | |
| 179 | pathelementFunc, 0, 0); |
| 180 | url_initialize(&sURI, "dir"); |
| 181 | cgi_query_parameters_to_url(&sURI); |
| 182 | |
| 183 | /* Compute the title of the page */ |
| 184 | blob_zero(&dirname); |
| 185 | if( zD ){ |
| 186 | blob_append(&dirname, "in directory ", -1); |
| 187 | hyperlinked_path(zD, &dirname, zCI, "dir", ""); |
| 188 | zPrefix = mprintf("%s/", zD); |
| 189 | style_submenu_element("Top-Level", "%s", |
| 190 | url_render(&sURI, "name", 0, 0, 0)); |
| 191 | }else{ |
| 192 | blob_append(&dirname, "in the top-level directory", -1); |
| 193 | zPrefix = ""; |
| 194 | } |
| 195 | if( linkTrunk ){ |
| 196 | style_submenu_element("Trunk", "%s", |
| 197 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| 198 | } |
| 199 | if( linkTip ){ |
| 200 | style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); |
| 201 | } |
| 202 | if( zCI ){ |
| 203 | @ <h2>Files at check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] |
| 204 | @ %s(blob_str(&dirname)) |
| 205 | if( zD ){ |
| 206 | @ %z(href("%R/timeline?chng=%T/*", zD))[history]</a> |
| 207 | } |
| 208 | @ </h2> |
| 209 | zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix); |
| 210 | if( nD==0 ){ |
| 211 | style_submenu_element("File Ages", "%R/fileage?name=%!S", zUuid); |
| 212 | } |
| 213 | }else{ |
| 214 | @ <h2>All files known in the repository |
| 215 | @ %s(blob_str(&dirname)) |
| 216 | if( zD ){ |
| 217 | @ %z(href("%R/timeline?chng=%T/*", zD))[history]</a> |
| 218 | } |
| 219 | @ </h2> |
| 220 | zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); |
| 221 | } |
| 222 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 223 | style_submenu_element("Tree-View", "%s", |
| 224 | url_render(&sURI, "type", "tree", 0, 0)); |
| 225 | |
| @@ -296,11 +320,11 @@ | |
| 296 | zFN++; |
| 297 | @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li> |
| 298 | }else{ |
| 299 | const char *zLink; |
| 300 | if( zCI ){ |
| 301 | zLink = href("%R/file?name=%T%T&ci=%!S",zPrefix,zFN,zCI); |
| 302 | }else{ |
| 303 | zLink = href("%R/finfo?name=%T%T",zPrefix,zFN); |
| 304 | } |
| 305 | @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li> |
| 306 | } |
| @@ -626,10 +650,11 @@ | |
| 626 | int startExpanded; /* True to start out with the tree expanded */ |
| 627 | int showDirOnly; /* Show directories only. Omit files */ |
| 628 | int nDir = 0; /* Number of directories. Used for ID attributes */ |
| 629 | char *zProjectName = db_get("project-name", 0); |
| 630 | int isSymbolicCI = 0; /* ci= is a symbolic name, not a hash prefix */ |
| 631 | char *zHeader = 0; |
| 632 | |
| 633 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 634 | if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; } |
| 635 | memset(&sTree, 0, sizeof(sTree)); |
| @@ -675,10 +700,11 @@ | |
| 675 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 676 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 677 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 678 | " FROM event WHERE objid=%d", rid); |
| 679 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| 680 | }else{ |
| 681 | zCI = 0; |
| 682 | } |
| 683 | } |
| 684 | if( zCI==0 ){ |
| @@ -685,41 +711,42 @@ | |
| 685 | rNow = db_double(0.0, "SELECT max(mtime) FROM event"); |
| 686 | zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); |
| 687 | } |
| 688 | |
| 689 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 690 | if( isSymbolicCI ) { |
| 691 | zHeader = mprintf("%s at %s", |
| 692 | (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zCI); |
| 693 | }else if( zUuid && strlen(zUuid) ){ |
| 694 | zHeader = mprintf("%s at [%S]", |
| 695 | (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zUuid); |
| 696 | }else{ |
| 697 | zHeader = mprintf("%s", |
| 698 | (zD ? zD : (showDirOnly ?"All Folders Hierarchy":"All Files Tree-View"))); |
| 699 | } |
| 700 | style_header("%s", zHeader); |
| 701 | fossil_free(zHeader); |
| 702 | |
| 703 | /* Compute the title of the page */ |
| 704 | blob_zero(&dirname); |
| 705 | if( zD ){ |
| 706 | blob_append(&dirname, "within directory ", -1); |
| 707 | hyperlinked_path(zD, &dirname, zCI, "tree", zREx); |
| 708 | if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); |
| 709 | style_submenu_element("Top-Level", "%s", |
| 710 | url_render(&sURI, "name", 0, 0, 0)); |
| 711 | }else{ |
| 712 | if( zRE ){ |
| 713 | blob_appendf(&dirname, "matching \"%s\"", zRE); |
| 714 | } |
| 715 | } |
| 716 | style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0); |
| 717 | if( zCI ){ |
| 718 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 719 | if( nD==0 && !showDirOnly ){ |
| 720 | style_submenu_element("File Ages", "%R/fileage?name=%s", zUuid); |
| 721 | } |
| 722 | } |
| 723 | if( linkTrunk ){ |
| 724 | style_submenu_element("Trunk", "%s", |
| 725 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| @@ -774,10 +801,11 @@ | |
| 774 | tree_add_node(&sTree, zName, zUuid, mtime); |
| 775 | nFile++; |
| 776 | } |
| 777 | db_finalize(&q); |
| 778 | } |
| 779 | |
| 780 | if( showDirOnly ){ |
| 781 | for(nFile=0, p=sTree.pFirst; p; p=p->pNext){ |
| 782 | if( p->pChild!=0 && p->nFullName>nD ) nFile++; |
| 783 | } |
| @@ -784,18 +812,24 @@ | |
| 784 | zObjType = "Folders"; |
| 785 | }else{ |
| 786 | zObjType = "Files"; |
| 787 | } |
| 788 | |
| 789 | style_submenu_checkbox("nofiles", "Folders Only", 0, 0); |
| 790 | |
| 791 | if( zCI ){ |
| 792 | @ <h2>%s(zObjType) at check-in |
| 793 | if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){ |
| 794 | @ "%h(zCI)" |
| 795 | } |
| 796 | @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname)) |
| 797 | }else{ |
| 798 | int n = db_int(0, "SELECT count(*) FROM plink"); |
| 799 | @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname)) |
| 800 | } |
| 801 | if( useMtime ){ |
| @@ -853,11 +887,11 @@ | |
| 853 | nDir++; |
| 854 | }else if( !showDirOnly ){ |
| 855 | const char *zFileClass = fileext_class(p->zName); |
| 856 | char *zLink; |
| 857 | if( zCI ){ |
| 858 | zLink = href("%R/file?name=%T&ci=%!S",p->zFullName,zCI); |
| 859 | }else{ |
| 860 | zLink = href("%R/finfo?name=%T",p->zFullName); |
| 861 | } |
| 862 | @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline"> |
| 863 | @ %z(zLink)%h(p->zName)</a> |
| @@ -1030,10 +1064,11 @@ | |
| 1030 | int rid; |
| 1031 | const char *zName; |
| 1032 | const char *zGlob; |
| 1033 | const char *zUuid; |
| 1034 | const char *zNow; /* Time of check-in */ |
| 1035 | int showId = PB("showid"); |
| 1036 | Stmt q1, q2; |
| 1037 | double baseTime; |
| 1038 | login_check_credentials(); |
| 1039 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| @@ -1043,28 +1078,34 @@ | |
| 1043 | rid = symbolic_name_to_rid(zName, "ci"); |
| 1044 | if( rid==0 ){ |
| 1045 | fossil_fatal("not a valid check-in: %s", zName); |
| 1046 | } |
| 1047 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1048 | baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); |
| 1049 | zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event" |
| 1050 | " WHERE objid=%d", rid); |
| 1051 | style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName); |
| 1052 | style_header("File Ages"); |
| 1053 | zGlob = P("glob"); |
| 1054 | compute_fileage(rid,zGlob); |
| 1055 | db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); |
| 1056 | |
| 1057 | @ <h1>Files in |
| 1058 | @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a> |
| 1059 | if( zGlob && zGlob[0] ){ |
| 1060 | @ that match "%h(zGlob)" |
| 1061 | } |
| 1062 | @ ordered by age</h1> |
| 1063 | @ |
| 1064 | @ <p>File ages are expressed relative to the |
| 1065 | @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> check-in time of |
| 1066 | @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p> |
| 1067 | @ |
| 1068 | @ <div class='fileage'><table> |
| 1069 | @ <tr><th>Age</th><th>Files</th><th>Check-in</th></tr> |
| 1070 | db_prepare(&q1, |
| @@ -1079,14 +1120,13 @@ | |
| 1079 | " AND blob.rid=event.objid\n" |
| 1080 | " ORDER BY event.mtime DESC;", |
| 1081 | TAG_BRANCH |
| 1082 | ); |
| 1083 | db_prepare(&q2, |
| 1084 | "SELECT blob.uuid, filename.name, fileage.fid\n" |
| 1085 | " FROM fileage, blob, filename\n" |
| 1086 | " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid" |
| 1087 | " AND blob.rid=fileage.fid;" |
| 1088 | ); |
| 1089 | while( db_step(&q1)==SQLITE_ROW ){ |
| 1090 | double age = baseTime - db_column_double(&q1, 0); |
| 1091 | int mid = db_column_int(&q1, 1); |
| 1092 | const char *zUuid = db_column_text(&q1, 2); |
| @@ -1096,24 +1136,24 @@ | |
| 1096 | char *zAge = human_readable_age(age); |
| 1097 | @ <tr><td>%s(zAge)</td> |
| 1098 | @ <td> |
| 1099 | db_bind_int(&q2, ":mid", mid); |
| 1100 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1101 | const char *zFUuid = db_column_text(&q2,0); |
| 1102 | const char *zFile = db_column_text(&q2,1); |
| 1103 | int fid = db_column_int(&q2,2); |
| 1104 | if( showId ){ |
| 1105 | @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a> (%d(fid))<br /> |
| 1106 | }else{ |
| 1107 | @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a><br /> |
| 1108 | } |
| 1109 | } |
| 1110 | db_reset(&q2); |
| 1111 | @ </td> |
| 1112 | @ <td> |
| 1113 | @ %W(zComment) |
| 1114 | @ (check-in: %z(href("%R/ci/%!S",zUuid))%S(zUuid)</a>, |
| 1115 | if( showId ){ |
| 1116 | @ id: %d(mid) |
| 1117 | } |
| 1118 | @ user: %z(href("%R/timeline?u=%t&c=%!S&nd",zUser,zUuid))%h(zUser)</a>, |
| 1119 | @ branch: \ |
| 1120 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -59,10 +59,18 @@ | |
| 59 | zOut = sqlite3_mprintf("/%.*s", i-n, &z[n]); |
| 60 | sqlite3_result_text(context, zOut, i-n+1, sqlite3_free); |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | /* |
| 65 | ** Flag arguments for hyperlinked_path() |
| 66 | */ |
| 67 | #if INTERFACE |
| 68 | # define LINKPATH_FINFO 0x0001 /* Link final term to /finfo */ |
| 69 | # define LINKPATH_FILE 0x0002 /* Link final term to /file */ |
| 70 | #endif |
| 71 | |
| 72 | /* |
| 73 | ** Given a pathname which is a relative path from the root of |
| 74 | ** the repository to a file or directory, compute a string which |
| 75 | ** is an HTML rendering of that path with hyperlinks on each |
| 76 | ** directory component of the path where the hyperlink redirects |
| @@ -76,29 +84,36 @@ | |
| 84 | void hyperlinked_path( |
| 85 | const char *zPath, /* Path to render */ |
| 86 | Blob *pOut, /* Write into this blob */ |
| 87 | const char *zCI, /* check-in name, or NULL */ |
| 88 | const char *zURI, /* "dir" or "tree" */ |
| 89 | const char *zREx, /* Extra query parameters */ |
| 90 | unsigned int mFlags /* Extra flags */ |
| 91 | ){ |
| 92 | int i, j; |
| 93 | char *zSep = ""; |
| 94 | |
| 95 | for(i=0; zPath[i]; i=j){ |
| 96 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 97 | if( zPath[j]==0 ){ |
| 98 | if( mFlags & LINKPATH_FILE ){ |
| 99 | zURI = "file"; |
| 100 | }else if( mFlags & LINKPATH_FINFO ){ |
| 101 | zURI = "finfo"; |
| 102 | }else{ |
| 103 | blob_appendf(pOut, "/%h", zPath+i); |
| 104 | break; |
| 105 | } |
| 106 | } |
| 107 | if( zCI ){ |
| 108 | char *zLink = href("%R/%s?name=%#T%s&ci=%T", zURI, j, zPath, zREx,zCI); |
| 109 | blob_appendf(pOut, "%s%z%#h</a>", |
| 110 | zSep, zLink, j-i, &zPath[i]); |
| 111 | }else{ |
| 112 | char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); |
| 113 | blob_appendf(pOut, "%s%z%#h</a>", |
| 114 | zSep, zLink, j-i, &zPath[i]); |
| 115 | } |
| 116 | zSep = "/"; |
| 117 | while( zPath[j]=='/' ){ j++; } |
| 118 | } |
| 119 | } |
| @@ -127,17 +142,17 @@ | |
| 142 | char *zPrefix; |
| 143 | Stmt q; |
| 144 | const char *zCI = P("ci"); |
| 145 | int rid = 0; |
| 146 | char *zUuid = 0; |
| 147 | Manifest *pM = 0; |
| 148 | const char *zSubdirLink; |
| 149 | int linkTrunk = 1; |
| 150 | int linkTip = 1; |
| 151 | HQuery sURI; |
| 152 | int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ |
| 153 | int isBranchCI = 0; /* True if ci= refers to a branch name */ |
| 154 | char *zHeader = 0; |
| 155 | |
| 156 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 157 | if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; } |
| 158 | login_check_credentials(); |
| @@ -157,22 +172,29 @@ | |
| 172 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 173 | linkTrunk = trunkRid && rid != trunkRid; |
| 174 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 175 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 176 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 177 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 178 | }else{ |
| 179 | zCI = 0; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 184 | if( zD==0 ){ |
| 185 | if( zCI ){ |
| 186 | zHeader = mprintf("Top-level Files of %s", zCI); |
| 187 | }else{ |
| 188 | zHeader = mprintf("All Top-level Files"); |
| 189 | } |
| 190 | }else{ |
| 191 | if( zCI ){ |
| 192 | zHeader = mprintf("Files in %s/ of %s", zD, zCI); |
| 193 | }else{ |
| 194 | zHeader = mprintf("All File in %s/", zD); |
| 195 | } |
| 196 | } |
| 197 | style_header("%s", zHeader); |
| 198 | fossil_free(zHeader); |
| 199 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 200 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| @@ -179,47 +201,49 @@ | |
| 201 | pathelementFunc, 0, 0); |
| 202 | url_initialize(&sURI, "dir"); |
| 203 | cgi_query_parameters_to_url(&sURI); |
| 204 | |
| 205 | /* Compute the title of the page */ |
| 206 | if( zD ){ |
| 207 | Blob dirname; |
| 208 | blob_init(&dirname, 0, 0); |
| 209 | hyperlinked_path(zD, &dirname, zCI, "dir", "", 0); |
| 210 | @ <h2>Files in directory %s(blob_str(&dirname)) \ |
| 211 | blob_reset(&dirname); |
| 212 | zPrefix = mprintf("%s/", zD); |
| 213 | style_submenu_element("Top-Level", "%s", |
| 214 | url_render(&sURI, "name", 0, 0, 0)); |
| 215 | }else{ |
| 216 | @ <h2>Files in the top-level directory \ |
| 217 | zPrefix = ""; |
| 218 | } |
| 219 | if( zCI ){ |
| 220 | if( fossil_strcmp(zCI,"tip")==0 ){ |
| 221 | @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a></h2> |
| 222 | }else if( isBranchCI ){ |
| 223 | @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a> \ |
| 224 | @ of branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 225 | }else { |
| 226 | @ of check-in %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2> |
| 227 | } |
| 228 | zSubdirLink = mprintf("%R/dir?ci=%T&name=%T", zCI, zPrefix); |
| 229 | if( nD==0 ){ |
| 230 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 231 | } |
| 232 | }else{ |
| 233 | @ in any check-in</h2> |
| 234 | zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); |
| 235 | } |
| 236 | if( linkTrunk ){ |
| 237 | style_submenu_element("Trunk", "%s", |
| 238 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| 239 | } |
| 240 | if( linkTip ){ |
| 241 | style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); |
| 242 | } |
| 243 | if( zD ){ |
| 244 | style_submenu_element("History","%R/timeline?chng=%T/*", zD); |
| 245 | } |
| 246 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 247 | style_submenu_element("Tree-View", "%s", |
| 248 | url_render(&sURI, "type", "tree", 0, 0)); |
| 249 | |
| @@ -296,11 +320,11 @@ | |
| 320 | zFN++; |
| 321 | @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li> |
| 322 | }else{ |
| 323 | const char *zLink; |
| 324 | if( zCI ){ |
| 325 | zLink = href("%R/file?name=%T%T&ci=%T",zPrefix,zFN,zCI); |
| 326 | }else{ |
| 327 | zLink = href("%R/finfo?name=%T%T",zPrefix,zFN); |
| 328 | } |
| 329 | @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li> |
| 330 | } |
| @@ -626,10 +650,11 @@ | |
| 650 | int startExpanded; /* True to start out with the tree expanded */ |
| 651 | int showDirOnly; /* Show directories only. Omit files */ |
| 652 | int nDir = 0; /* Number of directories. Used for ID attributes */ |
| 653 | char *zProjectName = db_get("project-name", 0); |
| 654 | int isSymbolicCI = 0; /* ci= is a symbolic name, not a hash prefix */ |
| 655 | int isBranchCI = 0; /* ci= refers to a branch name */ |
| 656 | char *zHeader = 0; |
| 657 | |
| 658 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 659 | if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; } |
| 660 | memset(&sTree, 0, sizeof(sTree)); |
| @@ -675,10 +700,11 @@ | |
| 700 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 701 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 702 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 703 | " FROM event WHERE objid=%d", rid); |
| 704 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| 705 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 706 | }else{ |
| 707 | zCI = 0; |
| 708 | } |
| 709 | } |
| 710 | if( zCI==0 ){ |
| @@ -685,41 +711,42 @@ | |
| 711 | rNow = db_double(0.0, "SELECT max(mtime) FROM event"); |
| 712 | zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); |
| 713 | } |
| 714 | |
| 715 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 716 | if( zD==0 ){ |
| 717 | if( zCI ){ |
| 718 | zHeader = mprintf("Top-level Files of %s", zCI); |
| 719 | }else{ |
| 720 | zHeader = mprintf("All Top-level Files"); |
| 721 | } |
| 722 | }else{ |
| 723 | if( zCI ){ |
| 724 | zHeader = mprintf("Files in %s/ of %s", zD, zCI); |
| 725 | }else{ |
| 726 | zHeader = mprintf("All File in %s/", zD); |
| 727 | } |
| 728 | } |
| 729 | style_header("%s", zHeader); |
| 730 | fossil_free(zHeader); |
| 731 | |
| 732 | /* Compute the title of the page */ |
| 733 | blob_zero(&dirname); |
| 734 | if( zD ){ |
| 735 | blob_append(&dirname, "within directory ", -1); |
| 736 | hyperlinked_path(zD, &dirname, zCI, "tree", zREx, 0); |
| 737 | if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); |
| 738 | style_submenu_element("Top-Level", "%s", |
| 739 | url_render(&sURI, "name", 0, 0, 0)); |
| 740 | }else if( zRE ){ |
| 741 | blob_appendf(&dirname, "matching \"%s\"", zRE); |
| 742 | } |
| 743 | style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0); |
| 744 | if( zCI ){ |
| 745 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 746 | if( nD==0 && !showDirOnly ){ |
| 747 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 748 | } |
| 749 | } |
| 750 | if( linkTrunk ){ |
| 751 | style_submenu_element("Trunk", "%s", |
| 752 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| @@ -774,10 +801,11 @@ | |
| 801 | tree_add_node(&sTree, zName, zUuid, mtime); |
| 802 | nFile++; |
| 803 | } |
| 804 | db_finalize(&q); |
| 805 | } |
| 806 | style_submenu_checkbox("nofiles", "Folders Only", 0, 0); |
| 807 | |
| 808 | if( showDirOnly ){ |
| 809 | for(nFile=0, p=sTree.pFirst; p; p=p->pNext){ |
| 810 | if( p->pChild!=0 && p->nFullName>nD ) nFile++; |
| 811 | } |
| @@ -784,18 +812,24 @@ | |
| 812 | zObjType = "Folders"; |
| 813 | }else{ |
| 814 | zObjType = "Files"; |
| 815 | } |
| 816 | |
| 817 | if( zCI && strcmp(zCI,"tip")==0 ){ |
| 818 | @ <h2>%s(zObjType) in the %z(href("%R/info?name=tip"))latest check-in</a> |
| 819 | }else if( isBranchCI ){ |
| 820 | @ <h2>%s(zObjType) in the %z(href("%R/info?name=%T",zCI))latest check-in\ |
| 821 | @ </a> for branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a> |
| 822 | if( blob_size(&dirname) ){ |
| 823 | @ and %s(blob_str(&dirname))</h2> |
| 824 | } |
| 825 | }else if( zCI ){ |
| 826 | @ <h2>%s(zObjType) for check-in \ |
| 827 | @ %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2> |
| 828 | if( blob_size(&dirname) ){ |
| 829 | @ and %s(blob_str(&dirname))</h2> |
| 830 | } |
| 831 | }else{ |
| 832 | int n = db_int(0, "SELECT count(*) FROM plink"); |
| 833 | @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname)) |
| 834 | } |
| 835 | if( useMtime ){ |
| @@ -853,11 +887,11 @@ | |
| 887 | nDir++; |
| 888 | }else if( !showDirOnly ){ |
| 889 | const char *zFileClass = fileext_class(p->zName); |
| 890 | char *zLink; |
| 891 | if( zCI ){ |
| 892 | zLink = href("%R/file?name=%T&ci=%T",p->zFullName,zCI); |
| 893 | }else{ |
| 894 | zLink = href("%R/finfo?name=%T",p->zFullName); |
| 895 | } |
| 896 | @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline"> |
| 897 | @ %z(zLink)%h(p->zName)</a> |
| @@ -1030,10 +1064,11 @@ | |
| 1064 | int rid; |
| 1065 | const char *zName; |
| 1066 | const char *zGlob; |
| 1067 | const char *zUuid; |
| 1068 | const char *zNow; /* Time of check-in */ |
| 1069 | int isBranchCI; /* name= is a branch name */ |
| 1070 | int showId = PB("showid"); |
| 1071 | Stmt q1, q2; |
| 1072 | double baseTime; |
| 1073 | login_check_credentials(); |
| 1074 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| @@ -1043,28 +1078,34 @@ | |
| 1078 | rid = symbolic_name_to_rid(zName, "ci"); |
| 1079 | if( rid==0 ){ |
| 1080 | fossil_fatal("not a valid check-in: %s", zName); |
| 1081 | } |
| 1082 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1083 | isBranchCI = branch_includes_uuid(zName,zUuid); |
| 1084 | baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); |
| 1085 | zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event" |
| 1086 | " WHERE objid=%d", rid); |
| 1087 | style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName); |
| 1088 | style_header("File Ages"); |
| 1089 | zGlob = P("glob"); |
| 1090 | compute_fileage(rid,zGlob); |
| 1091 | db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); |
| 1092 | |
| 1093 | if( fossil_strcmp(zName,"tip")==0 ){ |
| 1094 | @ <h1>Files in the %z(href("%R/info?name=tip"))latest check-in</a> |
| 1095 | }else if( isBranchCI ){ |
| 1096 | @ <h1>Files in the %z(href("%R/info?name=%T",zName))latest check-in</a> |
| 1097 | @ of branch %z(href("%R/timeline?r=%T",zName))%h(zName)</a> |
| 1098 | }else{ |
| 1099 | @ <h1>Files in check-in %z(href("%R/info?name=%T",zName))%h(zName)</a> |
| 1100 | } |
| 1101 | if( zGlob && zGlob[0] ){ |
| 1102 | @ that match "%h(zGlob)" |
| 1103 | } |
| 1104 | @ ordered by age</h1> |
| 1105 | @ |
| 1106 | @ <p>File ages are expressed relative to the check-in time of |
| 1107 | @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p> |
| 1108 | @ |
| 1109 | @ <div class='fileage'><table> |
| 1110 | @ <tr><th>Age</th><th>Files</th><th>Check-in</th></tr> |
| 1111 | db_prepare(&q1, |
| @@ -1079,14 +1120,13 @@ | |
| 1120 | " AND blob.rid=event.objid\n" |
| 1121 | " ORDER BY event.mtime DESC;", |
| 1122 | TAG_BRANCH |
| 1123 | ); |
| 1124 | db_prepare(&q2, |
| 1125 | "SELECT filename.name, fileage.fid\n" |
| 1126 | " FROM fileage, filename\n" |
| 1127 | " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid" |
| 1128 | ); |
| 1129 | while( db_step(&q1)==SQLITE_ROW ){ |
| 1130 | double age = baseTime - db_column_double(&q1, 0); |
| 1131 | int mid = db_column_int(&q1, 1); |
| 1132 | const char *zUuid = db_column_text(&q1, 2); |
| @@ -1096,24 +1136,24 @@ | |
| 1136 | char *zAge = human_readable_age(age); |
| 1137 | @ <tr><td>%s(zAge)</td> |
| 1138 | @ <td> |
| 1139 | db_bind_int(&q2, ":mid", mid); |
| 1140 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1141 | const char *zFile = db_column_text(&q2,0); |
| 1142 | @ %z(href("%R/file?name=%T&ci=%!S",zFile,zUuid))%h(zFile)</a> \ |
| 1143 | if( showId ){ |
| 1144 | int fid = db_column_int(&q2,1); |
| 1145 | @ (%d(fid))<br /> |
| 1146 | }else{ |
| 1147 | @ </a><br /> |
| 1148 | } |
| 1149 | } |
| 1150 | db_reset(&q2); |
| 1151 | @ </td> |
| 1152 | @ <td> |
| 1153 | @ %W(zComment) |
| 1154 | @ (check-in: %z(href("%R/info/%!S",zUuid))%S(zUuid)</a>, |
| 1155 | if( showId ){ |
| 1156 | @ id: %d(mid) |
| 1157 | } |
| 1158 | @ user: %z(href("%R/timeline?u=%t&c=%!S&nd",zUser,zUuid))%h(zUser)</a>, |
| 1159 | @ branch: \ |
| 1160 |
+17
-11
| --- src/etag.c | ||
| +++ src/etag.c | ||
| @@ -24,10 +24,12 @@ | ||
| 24 | 24 | ** (1) The mtime on the Fossil executable |
| 25 | 25 | ** (2) The last change to the CONFIG table |
| 26 | 26 | ** (3) The last change to the EVENT table |
| 27 | 27 | ** (4) The value of the display cookie |
| 28 | 28 | ** (5) A hash value supplied by the page generator |
| 29 | +** (6) The details of the request URI | |
| 30 | +** (7) The name user as determined by the login cookie | |
| 29 | 31 | ** |
| 30 | 32 | ** Item (1) is always included in the ETag. The other elements are |
| 31 | 33 | ** optional. Because (1) is always included as part of the ETag, all |
| 32 | 34 | ** outstanding ETags can be invalidated by touching the fossil executable. |
| 33 | 35 | ** |
| @@ -61,10 +63,11 @@ | ||
| 61 | 63 | #define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */ |
| 62 | 64 | #define ETAG_DATA 0x02 /* Output depends on the EVENT table */ |
| 63 | 65 | #define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */ |
| 64 | 66 | #define ETAG_HASH 0x08 /* Output depends on a hash */ |
| 65 | 67 | #define ETAG_QUERY 0x10 /* Output depends on PATH_INFO and QUERY_STRING */ |
| 68 | + /* and the g.zLogin value */ | |
| 66 | 69 | #endif |
| 67 | 70 | |
| 68 | 71 | static char zETag[33]; /* The generated ETag */ |
| 69 | 72 | static int iMaxAge = 0; /* The max-age parameter in the reply */ |
| 70 | 73 | static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */ |
| @@ -71,23 +74,21 @@ | ||
| 71 | 74 | |
| 72 | 75 | /* |
| 73 | 76 | ** Return a hash that changes every time the Fossil source code is |
| 74 | 77 | ** rebuilt. |
| 75 | 78 | ** |
| 76 | -** The current implementation is a hash of MANIFEST_UUID, __DATE__, and | |
| 77 | -** __TIME__. But this might change in the future if we think of a better | |
| 78 | -** way to compute an identifier that changes with each build. | |
| 79 | +** The FOSSIL_BUILD_HASH string that is returned here gets computed by | |
| 80 | +** the mkversion utility program. The result is a hash of MANIFEST_UUID | |
| 81 | +** and the unix timestamp for when the mkversion utility program is run. | |
| 82 | +** | |
| 83 | +** During development rebuilds, if you need the source code id to change | |
| 84 | +** in order to invalidate caches, simply "touch" the "manifest" file in | |
| 85 | +** the top of the source directory prior to running "make" and a new | |
| 86 | +** FOSSIL_BUILD_HASH will be generated automatically. | |
| 79 | 87 | */ |
| 80 | 88 | const char *fossil_exe_id(void){ |
| 81 | - static char zExecId[33]; | |
| 82 | - if( zExecId[0]==0 ){ | |
| 83 | - Blob x; | |
| 84 | - blob_init(&x, MANIFEST_UUID "," __DATE__ "," __TIME__, -1); | |
| 85 | - md5sum_blob(&x, &x); | |
| 86 | - memcpy(zExecId, x.aData, 32); | |
| 87 | - } | |
| 88 | - return zExecId; | |
| 89 | + return FOSSIL_BUILD_HASH; | |
| 89 | 90 | } |
| 90 | 91 | |
| 91 | 92 | /* |
| 92 | 93 | ** Generate an ETag |
| 93 | 94 | */ |
| @@ -141,10 +142,15 @@ | ||
| 141 | 142 | if( zQS ){ |
| 142 | 143 | md5sum_step_text("?", 1); |
| 143 | 144 | md5sum_step_text(zQS, -1); |
| 144 | 145 | } |
| 145 | 146 | md5sum_step_text("\n",1); |
| 147 | + if( g.zLogin ){ | |
| 148 | + md5sum_step_text("login: ", -1); | |
| 149 | + md5sum_step_text(g.zLogin, -1); | |
| 150 | + md5sum_step_text("\n", 1); | |
| 151 | + } | |
| 146 | 152 | } |
| 147 | 153 | |
| 148 | 154 | /* Generate the ETag */ |
| 149 | 155 | memcpy(zETag, md5sum_finish(0), 33); |
| 150 | 156 | |
| 151 | 157 |
| --- src/etag.c | |
| +++ src/etag.c | |
| @@ -24,10 +24,12 @@ | |
| 24 | ** (1) The mtime on the Fossil executable |
| 25 | ** (2) The last change to the CONFIG table |
| 26 | ** (3) The last change to the EVENT table |
| 27 | ** (4) The value of the display cookie |
| 28 | ** (5) A hash value supplied by the page generator |
| 29 | ** |
| 30 | ** Item (1) is always included in the ETag. The other elements are |
| 31 | ** optional. Because (1) is always included as part of the ETag, all |
| 32 | ** outstanding ETags can be invalidated by touching the fossil executable. |
| 33 | ** |
| @@ -61,10 +63,11 @@ | |
| 61 | #define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */ |
| 62 | #define ETAG_DATA 0x02 /* Output depends on the EVENT table */ |
| 63 | #define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */ |
| 64 | #define ETAG_HASH 0x08 /* Output depends on a hash */ |
| 65 | #define ETAG_QUERY 0x10 /* Output depends on PATH_INFO and QUERY_STRING */ |
| 66 | #endif |
| 67 | |
| 68 | static char zETag[33]; /* The generated ETag */ |
| 69 | static int iMaxAge = 0; /* The max-age parameter in the reply */ |
| 70 | static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */ |
| @@ -71,23 +74,21 @@ | |
| 71 | |
| 72 | /* |
| 73 | ** Return a hash that changes every time the Fossil source code is |
| 74 | ** rebuilt. |
| 75 | ** |
| 76 | ** The current implementation is a hash of MANIFEST_UUID, __DATE__, and |
| 77 | ** __TIME__. But this might change in the future if we think of a better |
| 78 | ** way to compute an identifier that changes with each build. |
| 79 | */ |
| 80 | const char *fossil_exe_id(void){ |
| 81 | static char zExecId[33]; |
| 82 | if( zExecId[0]==0 ){ |
| 83 | Blob x; |
| 84 | blob_init(&x, MANIFEST_UUID "," __DATE__ "," __TIME__, -1); |
| 85 | md5sum_blob(&x, &x); |
| 86 | memcpy(zExecId, x.aData, 32); |
| 87 | } |
| 88 | return zExecId; |
| 89 | } |
| 90 | |
| 91 | /* |
| 92 | ** Generate an ETag |
| 93 | */ |
| @@ -141,10 +142,15 @@ | |
| 141 | if( zQS ){ |
| 142 | md5sum_step_text("?", 1); |
| 143 | md5sum_step_text(zQS, -1); |
| 144 | } |
| 145 | md5sum_step_text("\n",1); |
| 146 | } |
| 147 | |
| 148 | /* Generate the ETag */ |
| 149 | memcpy(zETag, md5sum_finish(0), 33); |
| 150 | |
| 151 |
| --- src/etag.c | |
| +++ src/etag.c | |
| @@ -24,10 +24,12 @@ | |
| 24 | ** (1) The mtime on the Fossil executable |
| 25 | ** (2) The last change to the CONFIG table |
| 26 | ** (3) The last change to the EVENT table |
| 27 | ** (4) The value of the display cookie |
| 28 | ** (5) A hash value supplied by the page generator |
| 29 | ** (6) The details of the request URI |
| 30 | ** (7) The name user as determined by the login cookie |
| 31 | ** |
| 32 | ** Item (1) is always included in the ETag. The other elements are |
| 33 | ** optional. Because (1) is always included as part of the ETag, all |
| 34 | ** outstanding ETags can be invalidated by touching the fossil executable. |
| 35 | ** |
| @@ -61,10 +63,11 @@ | |
| 63 | #define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */ |
| 64 | #define ETAG_DATA 0x02 /* Output depends on the EVENT table */ |
| 65 | #define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */ |
| 66 | #define ETAG_HASH 0x08 /* Output depends on a hash */ |
| 67 | #define ETAG_QUERY 0x10 /* Output depends on PATH_INFO and QUERY_STRING */ |
| 68 | /* and the g.zLogin value */ |
| 69 | #endif |
| 70 | |
| 71 | static char zETag[33]; /* The generated ETag */ |
| 72 | static int iMaxAge = 0; /* The max-age parameter in the reply */ |
| 73 | static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */ |
| @@ -71,23 +74,21 @@ | |
| 74 | |
| 75 | /* |
| 76 | ** Return a hash that changes every time the Fossil source code is |
| 77 | ** rebuilt. |
| 78 | ** |
| 79 | ** The FOSSIL_BUILD_HASH string that is returned here gets computed by |
| 80 | ** the mkversion utility program. The result is a hash of MANIFEST_UUID |
| 81 | ** and the unix timestamp for when the mkversion utility program is run. |
| 82 | ** |
| 83 | ** During development rebuilds, if you need the source code id to change |
| 84 | ** in order to invalidate caches, simply "touch" the "manifest" file in |
| 85 | ** the top of the source directory prior to running "make" and a new |
| 86 | ** FOSSIL_BUILD_HASH will be generated automatically. |
| 87 | */ |
| 88 | const char *fossil_exe_id(void){ |
| 89 | return FOSSIL_BUILD_HASH; |
| 90 | } |
| 91 | |
| 92 | /* |
| 93 | ** Generate an ETag |
| 94 | */ |
| @@ -141,10 +142,15 @@ | |
| 142 | if( zQS ){ |
| 143 | md5sum_step_text("?", 1); |
| 144 | md5sum_step_text(zQS, -1); |
| 145 | } |
| 146 | md5sum_step_text("\n",1); |
| 147 | if( g.zLogin ){ |
| 148 | md5sum_step_text("login: ", -1); |
| 149 | md5sum_step_text(g.zLogin, -1); |
| 150 | md5sum_step_text("\n", 1); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | /* Generate the ETag */ |
| 155 | memcpy(zETag, md5sum_finish(0), 33); |
| 156 | |
| 157 |
+4
-3
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -438,12 +438,13 @@ | ||
| 438 | 438 | }else if( n>0 ){ |
| 439 | 439 | blob_appendf(&title, "First %d ancestors of file ", n); |
| 440 | 440 | }else{ |
| 441 | 441 | blob_appendf(&title, "Ancestors of file "); |
| 442 | 442 | } |
| 443 | - blob_appendf(&title,"<a href='%R/finfo?name=%T'>%h</a>", | |
| 444 | - zFilename, zFilename); | |
| 443 | + blob_appendf(&title,"%z%h</a>", | |
| 444 | + href("%R/file?name=%T&ci=%!S", zFilename, zUuid), | |
| 445 | + zFilename); | |
| 445 | 446 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 446 | 447 | blob_append(&title, origCheckin ? " between " : " from ", -1); |
| 447 | 448 | blob_appendf(&title, "check-in %z%S</a>", zLink, zUuid); |
| 448 | 449 | if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); |
| 449 | 450 | fossil_free(zUuid); |
| @@ -453,11 +454,11 @@ | ||
| 453 | 454 | blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid); |
| 454 | 455 | fossil_free(zUuid); |
| 455 | 456 | } |
| 456 | 457 | }else{ |
| 457 | 458 | blob_appendf(&title, "History for "); |
| 458 | - hyperlinked_path(zFilename, &title, 0, "tree", ""); | |
| 459 | + hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); | |
| 459 | 460 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 460 | 461 | } |
| 461 | 462 | if( uBg ){ |
| 462 | 463 | blob_append(&title, " (color-coded by user)", -1); |
| 463 | 464 | } |
| 464 | 465 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -438,12 +438,13 @@ | |
| 438 | }else if( n>0 ){ |
| 439 | blob_appendf(&title, "First %d ancestors of file ", n); |
| 440 | }else{ |
| 441 | blob_appendf(&title, "Ancestors of file "); |
| 442 | } |
| 443 | blob_appendf(&title,"<a href='%R/finfo?name=%T'>%h</a>", |
| 444 | zFilename, zFilename); |
| 445 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 446 | blob_append(&title, origCheckin ? " between " : " from ", -1); |
| 447 | blob_appendf(&title, "check-in %z%S</a>", zLink, zUuid); |
| 448 | if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); |
| 449 | fossil_free(zUuid); |
| @@ -453,11 +454,11 @@ | |
| 453 | blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid); |
| 454 | fossil_free(zUuid); |
| 455 | } |
| 456 | }else{ |
| 457 | blob_appendf(&title, "History for "); |
| 458 | hyperlinked_path(zFilename, &title, 0, "tree", ""); |
| 459 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 460 | } |
| 461 | if( uBg ){ |
| 462 | blob_append(&title, " (color-coded by user)", -1); |
| 463 | } |
| 464 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -438,12 +438,13 @@ | |
| 438 | }else if( n>0 ){ |
| 439 | blob_appendf(&title, "First %d ancestors of file ", n); |
| 440 | }else{ |
| 441 | blob_appendf(&title, "Ancestors of file "); |
| 442 | } |
| 443 | blob_appendf(&title,"%z%h</a>", |
| 444 | href("%R/file?name=%T&ci=%!S", zFilename, zUuid), |
| 445 | zFilename); |
| 446 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 447 | blob_append(&title, origCheckin ? " between " : " from ", -1); |
| 448 | blob_appendf(&title, "check-in %z%S</a>", zLink, zUuid); |
| 449 | if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); |
| 450 | fossil_free(zUuid); |
| @@ -453,11 +454,11 @@ | |
| 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 | } |
| 462 | if( uBg ){ |
| 463 | blob_append(&title, " (color-coded by user)", -1); |
| 464 | } |
| 465 |
+4
-3
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -438,12 +438,13 @@ | ||
| 438 | 438 | }else if( n>0 ){ |
| 439 | 439 | blob_appendf(&title, "First %d ancestors of file ", n); |
| 440 | 440 | }else{ |
| 441 | 441 | blob_appendf(&title, "Ancestors of file "); |
| 442 | 442 | } |
| 443 | - blob_appendf(&title,"<a href='%R/finfo?name=%T'>%h</a>", | |
| 444 | - zFilename, zFilename); | |
| 443 | + blob_appendf(&title,"%z%h</a>", | |
| 444 | + href("%R/file?name=%T&ci=%!S", zFilename, zUuid), | |
| 445 | + zFilename); | |
| 445 | 446 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 446 | 447 | blob_append(&title, origCheckin ? " between " : " from ", -1); |
| 447 | 448 | blob_appendf(&title, "check-in %z%S</a>", zLink, zUuid); |
| 448 | 449 | if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); |
| 449 | 450 | fossil_free(zUuid); |
| @@ -453,11 +454,11 @@ | ||
| 453 | 454 | blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid); |
| 454 | 455 | fossil_free(zUuid); |
| 455 | 456 | } |
| 456 | 457 | }else{ |
| 457 | 458 | blob_appendf(&title, "History for "); |
| 458 | - hyperlinked_path(zFilename, &title, 0, "tree", ""); | |
| 459 | + hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); | |
| 459 | 460 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 460 | 461 | } |
| 461 | 462 | if( uBg ){ |
| 462 | 463 | blob_append(&title, " (color-coded by user)", -1); |
| 463 | 464 | } |
| 464 | 465 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -438,12 +438,13 @@ | |
| 438 | }else if( n>0 ){ |
| 439 | blob_appendf(&title, "First %d ancestors of file ", n); |
| 440 | }else{ |
| 441 | blob_appendf(&title, "Ancestors of file "); |
| 442 | } |
| 443 | blob_appendf(&title,"<a href='%R/finfo?name=%T'>%h</a>", |
| 444 | zFilename, zFilename); |
| 445 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 446 | blob_append(&title, origCheckin ? " between " : " from ", -1); |
| 447 | blob_appendf(&title, "check-in %z%S</a>", zLink, zUuid); |
| 448 | if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); |
| 449 | fossil_free(zUuid); |
| @@ -453,11 +454,11 @@ | |
| 453 | blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid); |
| 454 | fossil_free(zUuid); |
| 455 | } |
| 456 | }else{ |
| 457 | blob_appendf(&title, "History for "); |
| 458 | hyperlinked_path(zFilename, &title, 0, "tree", ""); |
| 459 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 460 | } |
| 461 | if( uBg ){ |
| 462 | blob_append(&title, " (color-coded by user)", -1); |
| 463 | } |
| 464 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -438,12 +438,13 @@ | |
| 438 | }else if( n>0 ){ |
| 439 | blob_appendf(&title, "First %d ancestors of file ", n); |
| 440 | }else{ |
| 441 | blob_appendf(&title, "Ancestors of file "); |
| 442 | } |
| 443 | blob_appendf(&title,"%z%h</a>", |
| 444 | href("%R/file?name=%T&ci=%!S", zFilename, zUuid), |
| 445 | zFilename); |
| 446 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 447 | blob_append(&title, origCheckin ? " between " : " from ", -1); |
| 448 | blob_appendf(&title, "check-in %z%S</a>", zLink, zUuid); |
| 449 | if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); |
| 450 | fossil_free(zUuid); |
| @@ -453,11 +454,11 @@ | |
| 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 | } |
| 462 | if( uBg ){ |
| 463 | blob_append(&title, " (color-coded by user)", -1); |
| 464 | } |
| 465 |
+140
-137
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -1664,12 +1664,12 @@ | ||
| 1664 | 1664 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1665 | 1665 | cookie_link_parameter("diff","diff","2"); |
| 1666 | 1666 | diffType = atoi(PD("diff","2")); |
| 1667 | 1667 | cookie_render(); |
| 1668 | 1668 | if( P("from") && P("to") ){ |
| 1669 | - v1 = artifact_from_ci_and_filename(0, "from"); | |
| 1670 | - v2 = artifact_from_ci_and_filename(0, "to"); | |
| 1669 | + v1 = artifact_from_ci_and_filename("from"); | |
| 1670 | + v2 = artifact_from_ci_and_filename("to"); | |
| 1671 | 1671 | }else{ |
| 1672 | 1672 | Stmt q; |
| 1673 | 1673 | v1 = name_to_rid_www("v1"); |
| 1674 | 1674 | v2 = name_to_rid_www("v2"); |
| 1675 | 1675 | |
| @@ -1766,12 +1766,12 @@ | ||
| 1766 | 1766 | */ |
| 1767 | 1767 | void rawartifact_page(void){ |
| 1768 | 1768 | int rid = 0; |
| 1769 | 1769 | char *zUuid; |
| 1770 | 1770 | |
| 1771 | - if( P("ci") && P("filename") ){ | |
| 1772 | - rid = artifact_from_ci_and_filename(0, 0); | |
| 1771 | + if( P("ci") ){ | |
| 1772 | + rid = artifact_from_ci_and_filename(0); | |
| 1773 | 1773 | } |
| 1774 | 1774 | if( rid==0 ){ |
| 1775 | 1775 | rid = name_to_rid_www("name"); |
| 1776 | 1776 | } |
| 1777 | 1777 | login_check_credentials(); |
| @@ -1945,59 +1945,53 @@ | ||
| 1945 | 1945 | |
| 1946 | 1946 | /* |
| 1947 | 1947 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1948 | 1948 | ** use them to extract the record ID of an artifact for the file. |
| 1949 | 1949 | ** |
| 1950 | -** Also look for "fn" as an alias for "filename". If either "filename" | |
| 1951 | -** or "fn" is present but "ci" is missing, use "tip" as a default value | |
| 1952 | -** for "ci". | |
| 1953 | -** | |
| 1954 | -** If zNameParam is not NULL, this use that parameter as the filename | |
| 1955 | -** rather than "fn" or "filename". | |
| 1956 | -** | |
| 1957 | -** If pUrl is not NULL, then record the "ci" and "filename" values in | |
| 1958 | -** pUrl. | |
| 1959 | -** | |
| 1960 | -** At least one of pUrl or zNameParam must be NULL. | |
| 1950 | +** Also look for "fn" and "name" as an aliases for "filename". If any | |
| 1951 | +** "filename" or "fn" or "name" are present but "ci" is missing, then | |
| 1952 | +** use "tip" as the default value for "ci". | |
| 1953 | +** | |
| 1954 | +** If zNameParam is not NULL, then use that parameter as the filename | |
| 1955 | +** rather than "fn" or "filename" or "name". the zNameParam is used | |
| 1956 | +** for the from= and to= query parameters of /fdiff. | |
| 1961 | 1957 | */ |
| 1962 | -int artifact_from_ci_and_filename(HQuery *pUrl, const char *zNameParam){ | |
| 1958 | +int artifact_from_ci_and_filename(const char *zNameParam){ | |
| 1963 | 1959 | const char *zFilename; |
| 1964 | 1960 | const char *zCI; |
| 1965 | 1961 | int cirid; |
| 1966 | 1962 | Manifest *pManifest; |
| 1967 | 1963 | ManifestFile *pFile; |
| 1964 | + int rid = 0; | |
| 1968 | 1965 | |
| 1969 | 1966 | if( zNameParam ){ |
| 1970 | 1967 | zFilename = P(zNameParam); |
| 1971 | 1968 | }else{ |
| 1972 | 1969 | zFilename = P("filename"); |
| 1973 | 1970 | if( zFilename==0 ){ |
| 1974 | 1971 | zFilename = P("fn"); |
| 1975 | 1972 | } |
| 1973 | + if( zFilename==0 ){ | |
| 1974 | + zFilename = P("name"); | |
| 1975 | + } | |
| 1976 | 1976 | } |
| 1977 | 1977 | if( zFilename==0 ) return 0; |
| 1978 | 1978 | |
| 1979 | - zCI = P("ci"); | |
| 1980 | - cirid = name_to_typed_rid(zCI ? zCI : "tip", "ci"); | |
| 1979 | + zCI = PD("ci", "tip"); | |
| 1980 | + cirid = name_to_typed_rid(zCI, "ci"); | |
| 1981 | 1981 | if( cirid<=0 ) return 0; |
| 1982 | 1982 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1983 | 1983 | if( pManifest==0 ) return 0; |
| 1984 | 1984 | manifest_file_rewind(pManifest); |
| 1985 | 1985 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1986 | 1986 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1987 | - int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); | |
| 1988 | - manifest_destroy(pManifest); | |
| 1989 | - if( pUrl ){ | |
| 1990 | - assert( zNameParam==0 ); | |
| 1991 | - url_add_parameter(pUrl, "fn", zFilename); | |
| 1992 | - if( zCI ) url_add_parameter(pUrl, "ci", zCI); | |
| 1993 | - } | |
| 1994 | - return rid; | |
| 1987 | + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); | |
| 1988 | + break; | |
| 1995 | 1989 | } |
| 1996 | 1990 | } |
| 1997 | 1991 | manifest_destroy(pManifest); |
| 1998 | - return 0; | |
| 1992 | + return rid; | |
| 1999 | 1993 | } |
| 2000 | 1994 | |
| 2001 | 1995 | /* |
| 2002 | 1996 | ** The "z" argument is a string that contains the text of a source code |
| 2003 | 1997 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2099,21 +2093,31 @@ | ||
| 2099 | 2093 | ** ln=N - highlight line number N |
| 2100 | 2094 | ** ln=M-N - highlight lines M through N inclusive |
| 2101 | 2095 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2102 | 2096 | ** verbose - show more detail in the description |
| 2103 | 2097 | ** download - redirect to the download (artifact page only) |
| 2104 | -** name=SHA1HASH - Provide the SHA1HASH as a query parameter | |
| 2105 | -** filename=NAME - Show information for content file NAME | |
| 2106 | -** fn=NAME - "fn" is shorthand for "filename" | |
| 2107 | -** ci=VERSION - The specific check-in to use for "filename=". | |
| 2098 | +** name=NAME - filename or hash as a query parameter | |
| 2099 | +** filename=NAME - alternative spelling for "name=" | |
| 2100 | +** fn=NAME - alternative spelling for "name=" | |
| 2101 | +** ci=VERSION - The specific check-in to use with "name=" to | |
| 2102 | +** identify the file. | |
| 2108 | 2103 | ** |
| 2109 | 2104 | ** The /artifact page show the complete content of a file |
| 2110 | -** identified by HASH as preformatted text. The | |
| 2111 | -** /whatis page shows only a description of the file. The /file | |
| 2112 | -** page shows the most recent version of the file or directory | |
| 2113 | -** called NAME, or a list of the top-level directory if NAME is | |
| 2114 | -** omitted. | |
| 2105 | +** identified by HASH. The /whatis page shows only a description | |
| 2106 | +** of how the artifact is used. The /file page shows the most recent | |
| 2107 | +** version of the file or directory called NAME, or a list of the | |
| 2108 | +** top-level directory if NAME is omitted. | |
| 2109 | +** | |
| 2110 | +** For /artifact and /whatis, the name= query parameter can refer to | |
| 2111 | +** either the name of a file, or an artifact hash. If the ci= query | |
| 2112 | +** parameter is also present, then name= must refer to a file name. | |
| 2113 | +** If ci= is omitted, then the hash interpretation is preferred but | |
| 2114 | +** if name= cannot be understood as a hash, a default "tip" value is | |
| 2115 | +** used for ci=. | |
| 2116 | +** | |
| 2117 | +** For /file, name= can only be interpreted as a filename. As before, | |
| 2118 | +** a default value of "tip" is used for ci= if ci= is omitted. | |
| 2115 | 2119 | */ |
| 2116 | 2120 | void artifact_page(void){ |
| 2117 | 2121 | int rid = 0; |
| 2118 | 2122 | Blob content; |
| 2119 | 2123 | const char *zMime; |
| @@ -2128,139 +2132,138 @@ | ||
| 2128 | 2132 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2129 | 2133 | const char *zLn = P("ln"); |
| 2130 | 2134 | const char *zName = P("name"); |
| 2131 | 2135 | const char *zCI = P("ci"); |
| 2132 | 2136 | HQuery url; |
| 2133 | - Blob dirname; | |
| 2134 | 2137 | char *zCIUuid = 0; |
| 2135 | 2138 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2139 | + int isBranchCI = 0; /* ci= refers to a branch name */ | |
| 2136 | 2140 | char *zHeader = 0; |
| 2137 | 2141 | |
| 2138 | 2142 | login_check_credentials(); |
| 2139 | 2143 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2140 | - url_initialize(&url, g.zPath); | |
| 2141 | - if( zCI && strlen(zCI)==0 ){ zCI = 0; } | |
| 2142 | - if( zCI ){ | |
| 2143 | - blob_zero(&dirname); | |
| 2144 | - hyperlinked_path(zName, &dirname, zCI, "dir", ""); | |
| 2145 | - blob_reset(&dirname); | |
| 2146 | - | |
| 2147 | - if( name_to_uuid2(zCI, "ci", &zCIUuid) ){ | |
| 2148 | - isSymbolicCI = (sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI)) != 0); | |
| 2149 | - } | |
| 2150 | - } | |
| 2151 | - if( isFile && zName ) { | |
| 2152 | - rid = artifact_from_ci_and_filename(0, "name"); | |
| 2153 | - }else{ | |
| 2154 | - rid = artifact_from_ci_and_filename(&url, 0); | |
| 2155 | - } | |
| 2156 | - if( rid==0 ){ | |
| 2157 | - url_add_parameter(&url, "name", zName); | |
| 2158 | - if( isFile ){ | |
| 2159 | - int isUnknownAtCI = 0; | |
| 2160 | - | |
| 2161 | - /* Do a top-level directory listing in /file mode if no argument | |
| 2162 | - ** specified */ | |
| 2163 | - if( zName==0 || zName[0]==0 ){ | |
| 2164 | - if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2165 | - page_tree(); | |
| 2166 | - return; | |
| 2167 | - } | |
| 2168 | - /* Look for a single file with the given name */ | |
| 2169 | - rid = db_int(0, | |
| 2170 | - "SELECT fid FROM filename, mlink, event" | |
| 2171 | - " WHERE name=%Q" | |
| 2172 | - " AND mlink.fnid=filename.fnid" | |
| 2173 | - " AND event.objid=mlink.mid" | |
| 2174 | - " ORDER BY event.mtime DESC LIMIT 1", | |
| 2175 | - zName | |
| 2176 | - ); | |
| 2177 | - /* If found only by the name, then the file is unknown in the check-in. | |
| 2178 | - ** Possibly, the file was renamed/deleted. | |
| 2179 | - */ | |
| 2180 | - if( rid && zCIUuid ){ | |
| 2181 | - rid = 0; | |
| 2182 | - isUnknownAtCI = 1; | |
| 2183 | - } | |
| 2184 | - /* If no file called NAME exists, instead look for a directory | |
| 2185 | - ** with that name, and do a directory listing */ | |
| 2186 | - if( rid==0 ){ | |
| 2187 | - int nName = (int)strlen(zName); | |
| 2188 | - if( nName && zName[nName-1]=='/' ) nName--; | |
| 2189 | - if( db_exists( | |
| 2190 | - "SELECT 1 FROM filename" | |
| 2191 | - " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", | |
| 2192 | - nName, zName, nName+1, nName, zName | |
| 2193 | - ) ){ | |
| 2194 | - if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2195 | - page_tree(); | |
| 2196 | - return; | |
| 2197 | - } | |
| 2198 | - } | |
| 2199 | - /* If no file or directory called NAME: issue an error */ | |
| 2200 | - if( rid==0 ){ | |
| 2201 | - if( isUnknownAtCI ){ | |
| 2202 | - if( isSymbolicCI ){ | |
| 2203 | - zHeader = mprintf("No such file at %s", zCI); | |
| 2204 | - }else{ | |
| 2205 | - zHeader = mprintf("No such file at [%S]", zCIUuid); | |
| 2206 | - } | |
| 2207 | - style_header("%s", zHeader); | |
| 2208 | - fossil_free(zHeader); | |
| 2209 | - @ File %z(href("%R/finfo?name=%T",zName))%h(zName)</a> is not known | |
| 2210 | - @ at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]. | |
| 2211 | - }else{ | |
| 2212 | - style_header("No such file"); | |
| 2213 | - @ File '%h(zName)' is not known in this repository. | |
| 2214 | - } | |
| 2215 | - style_footer(); | |
| 2216 | - return; | |
| 2217 | - } | |
| 2218 | - }else{ | |
| 2219 | - rid = name_to_rid_www("name"); | |
| 2220 | - } | |
| 2221 | - } | |
| 2222 | - | |
| 2223 | - if( rid==0 ){ | |
| 2224 | - style_header("No such artifact"); | |
| 2225 | - @ Artifact '%h(zName)' does not exist in this repository. | |
| 2226 | - style_footer(); | |
| 2227 | - return; | |
| 2228 | - } | |
| 2144 | + | |
| 2145 | + /* Capture and normalize the name= and ci= query parameters */ | |
| 2146 | + if( zName==0 ){ | |
| 2147 | + zName = P("filename"); | |
| 2148 | + if( zName==0 ){ | |
| 2149 | + zName = P("fn"); | |
| 2150 | + } | |
| 2151 | + } | |
| 2152 | + if( zCI && strlen(zCI)==0 ){ zCI = 0; } | |
| 2153 | + if( zCI | |
| 2154 | + && name_to_uuid2(zCI, "ci", &zCIUuid) | |
| 2155 | + && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0 | |
| 2156 | + ){ | |
| 2157 | + isSymbolicCI = 1; | |
| 2158 | + isBranchCI = branch_includes_uuid(zCI, zCIUuid); | |
| 2159 | + } | |
| 2160 | + | |
| 2161 | + /* The name= query parameter (or at least one of its alternative | |
| 2162 | + ** spellings) is required. Except for /file, show a top-level | |
| 2163 | + ** directory listing if name= is omitted. | |
| 2164 | + */ | |
| 2165 | + if( zName==0 ){ | |
| 2166 | + if( isFile ){ | |
| 2167 | + if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2168 | + page_tree(); | |
| 2169 | + return; | |
| 2170 | + } | |
| 2171 | + style_header("Missing name= query parameter"); | |
| 2172 | + @ The name= query parameter is missing | |
| 2173 | + style_footer(); | |
| 2174 | + return; | |
| 2175 | + } | |
| 2176 | + | |
| 2177 | + url_initialize(&url, g.zPath); | |
| 2178 | + url_add_parameter(&url, "name", zName); | |
| 2179 | + url_add_parameter(&url, "ci", zCI); | |
| 2180 | + | |
| 2181 | + if( zCI==0 && !isFile ){ | |
| 2182 | + /* If there is no ci= query parameter, then prefer to interpret | |
| 2183 | + ** name= as a hash for /artifact and /whatis. But for not for /file. | |
| 2184 | + ** For /file, a name= without a ci= while prefer to use the default | |
| 2185 | + ** "tip" value for ci=. */ | |
| 2186 | + rid = name_to_rid(zName); | |
| 2187 | + } | |
| 2188 | + if( rid==0 ){ | |
| 2189 | + rid = artifact_from_ci_and_filename(0); | |
| 2190 | + } | |
| 2191 | + | |
| 2192 | + if( rid==0 ){ /* Artifact not found */ | |
| 2193 | + if( isFile ){ | |
| 2194 | + /* For /file, also check to see if name= refers to a directory, | |
| 2195 | + ** and if so, do a listing for that directory */ | |
| 2196 | + int nName = (int)strlen(zName); | |
| 2197 | + if( nName && zName[nName-1]=='/' ) nName--; | |
| 2198 | + if( db_exists( | |
| 2199 | + "SELECT 1 FROM filename" | |
| 2200 | + " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", | |
| 2201 | + nName, zName, nName+1, nName, zName | |
| 2202 | + ) ){ | |
| 2203 | + if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2204 | + page_tree(); | |
| 2205 | + return; | |
| 2206 | + } | |
| 2207 | + style_header("No such file"); | |
| 2208 | + @ File '%h(zName)' does not exist in this repository. | |
| 2209 | + }else{ | |
| 2210 | + style_header("No such artifact"); | |
| 2211 | + @ Artifact '%h(zName)' does not exist in this repository. | |
| 2212 | + } | |
| 2213 | + style_footer(); | |
| 2214 | + return; | |
| 2215 | + } | |
| 2216 | + | |
| 2229 | 2217 | if( descOnly || P("verbose")!=0 ){ |
| 2230 | 2218 | url_add_parameter(&url, "verbose", "1"); |
| 2231 | 2219 | objdescFlags |= OBJDESC_DETAIL; |
| 2232 | 2220 | } |
| 2233 | 2221 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2234 | 2222 | |
| 2223 | + asText = P("txt")!=0; | |
| 2235 | 2224 | if( isFile ){ |
| 2236 | - if( zCI ){ | |
| 2225 | + if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ | |
| 2226 | + zCI = "tip"; | |
| 2227 | + @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a> | |
| 2228 | + @ from the %z(href("%R/info/tip"))latest check-in</a></h2> | |
| 2229 | + }else{ | |
| 2237 | 2230 | const char *zPath; |
| 2238 | 2231 | Blob path; |
| 2239 | 2232 | blob_zero(&path); |
| 2240 | - hyperlinked_path(zName, &path, zCI, "dir", ""); | |
| 2233 | + hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); | |
| 2241 | 2234 | zPath = blob_str(&path); |
| 2242 | - @ <h2>File %s(zPath) at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>] | |
| 2243 | - @ </h2> | |
| 2235 | + @ <h2>File %s(zPath) \ | |
| 2236 | + if( isBranchCI ){ | |
| 2237 | + @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> | |
| 2238 | + }else if( isSymbolicCI ){ | |
| 2239 | + @ as of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2> | |
| 2240 | + }else{ | |
| 2241 | + @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2> | |
| 2242 | + } | |
| 2244 | 2243 | blob_reset(&path); |
| 2245 | - }else{ | |
| 2246 | - @ <h2>Latest version of file '%h(zName)':</h2> | |
| 2247 | 2244 | } |
| 2248 | 2245 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2246 | + style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", | |
| 2247 | + zName, zCI); | |
| 2248 | + style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", | |
| 2249 | + zName, zCI); | |
| 2250 | + blob_init(&downloadName, zName, -1); | |
| 2251 | + objType = OBJTYPE_CONTENT; | |
| 2249 | 2252 | }else{ |
| 2250 | 2253 | @ <h2>Artifact |
| 2251 | 2254 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2252 | 2255 | if( g.perm.Setup ){ |
| 2253 | 2256 | @ (%d(rid)):</h2> |
| 2254 | 2257 | }else{ |
| 2255 | 2258 | @ :</h2> |
| 2256 | 2259 | } |
| 2260 | + blob_zero(&downloadName); | |
| 2261 | + if( asText ) objdescFlags &= ~OBJDESC_BASE; | |
| 2262 | + objType = object_description(rid, objdescFlags, | |
| 2263 | + (isFile?zName:0), &downloadName); | |
| 2257 | 2264 | } |
| 2258 | - blob_zero(&downloadName); | |
| 2259 | - asText = P("txt")!=0; | |
| 2260 | - if( asText ) objdescFlags &= ~OBJDESC_BASE; | |
| 2261 | - objType = object_description(rid, objdescFlags, (isFile ? zName : 0),&downloadName); | |
| 2262 | 2265 | if( !descOnly && P("download")!=0 ){ |
| 2263 | 2266 | cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName), |
| 2264 | 2267 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2265 | 2268 | /*NOTREACHED*/ |
| 2266 | 2269 | } |
| @@ -2288,11 +2291,11 @@ | ||
| 2288 | 2291 | zHeader = mprintf("Artifact [%S]", zUuid); |
| 2289 | 2292 | } |
| 2290 | 2293 | style_header("%s", zHeader); |
| 2291 | 2294 | fossil_free(zCIUuid); |
| 2292 | 2295 | fossil_free(zHeader); |
| 2293 | - if( g.perm.Admin ){ | |
| 2296 | + if( !isFile && g.perm.Admin ){ | |
| 2294 | 2297 | Stmt q; |
| 2295 | 2298 | db_prepare(&q, |
| 2296 | 2299 | "SELECT coalesce(user.login,rcvfrom.uid)," |
| 2297 | 2300 | " datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr" |
| 2298 | 2301 | " FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2299 | 2302 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1664,12 +1664,12 @@ | |
| 1664 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1665 | cookie_link_parameter("diff","diff","2"); |
| 1666 | diffType = atoi(PD("diff","2")); |
| 1667 | cookie_render(); |
| 1668 | if( P("from") && P("to") ){ |
| 1669 | v1 = artifact_from_ci_and_filename(0, "from"); |
| 1670 | v2 = artifact_from_ci_and_filename(0, "to"); |
| 1671 | }else{ |
| 1672 | Stmt q; |
| 1673 | v1 = name_to_rid_www("v1"); |
| 1674 | v2 = name_to_rid_www("v2"); |
| 1675 | |
| @@ -1766,12 +1766,12 @@ | |
| 1766 | */ |
| 1767 | void rawartifact_page(void){ |
| 1768 | int rid = 0; |
| 1769 | char *zUuid; |
| 1770 | |
| 1771 | if( P("ci") && P("filename") ){ |
| 1772 | rid = artifact_from_ci_and_filename(0, 0); |
| 1773 | } |
| 1774 | if( rid==0 ){ |
| 1775 | rid = name_to_rid_www("name"); |
| 1776 | } |
| 1777 | login_check_credentials(); |
| @@ -1945,59 +1945,53 @@ | |
| 1945 | |
| 1946 | /* |
| 1947 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1948 | ** use them to extract the record ID of an artifact for the file. |
| 1949 | ** |
| 1950 | ** Also look for "fn" as an alias for "filename". If either "filename" |
| 1951 | ** or "fn" is present but "ci" is missing, use "tip" as a default value |
| 1952 | ** for "ci". |
| 1953 | ** |
| 1954 | ** If zNameParam is not NULL, this use that parameter as the filename |
| 1955 | ** rather than "fn" or "filename". |
| 1956 | ** |
| 1957 | ** If pUrl is not NULL, then record the "ci" and "filename" values in |
| 1958 | ** pUrl. |
| 1959 | ** |
| 1960 | ** At least one of pUrl or zNameParam must be NULL. |
| 1961 | */ |
| 1962 | int artifact_from_ci_and_filename(HQuery *pUrl, const char *zNameParam){ |
| 1963 | const char *zFilename; |
| 1964 | const char *zCI; |
| 1965 | int cirid; |
| 1966 | Manifest *pManifest; |
| 1967 | ManifestFile *pFile; |
| 1968 | |
| 1969 | if( zNameParam ){ |
| 1970 | zFilename = P(zNameParam); |
| 1971 | }else{ |
| 1972 | zFilename = P("filename"); |
| 1973 | if( zFilename==0 ){ |
| 1974 | zFilename = P("fn"); |
| 1975 | } |
| 1976 | } |
| 1977 | if( zFilename==0 ) return 0; |
| 1978 | |
| 1979 | zCI = P("ci"); |
| 1980 | cirid = name_to_typed_rid(zCI ? zCI : "tip", "ci"); |
| 1981 | if( cirid<=0 ) return 0; |
| 1982 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1983 | if( pManifest==0 ) return 0; |
| 1984 | manifest_file_rewind(pManifest); |
| 1985 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1986 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1987 | int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 1988 | manifest_destroy(pManifest); |
| 1989 | if( pUrl ){ |
| 1990 | assert( zNameParam==0 ); |
| 1991 | url_add_parameter(pUrl, "fn", zFilename); |
| 1992 | if( zCI ) url_add_parameter(pUrl, "ci", zCI); |
| 1993 | } |
| 1994 | return rid; |
| 1995 | } |
| 1996 | } |
| 1997 | manifest_destroy(pManifest); |
| 1998 | return 0; |
| 1999 | } |
| 2000 | |
| 2001 | /* |
| 2002 | ** The "z" argument is a string that contains the text of a source code |
| 2003 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2099,21 +2093,31 @@ | |
| 2099 | ** ln=N - highlight line number N |
| 2100 | ** ln=M-N - highlight lines M through N inclusive |
| 2101 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2102 | ** verbose - show more detail in the description |
| 2103 | ** download - redirect to the download (artifact page only) |
| 2104 | ** name=SHA1HASH - Provide the SHA1HASH as a query parameter |
| 2105 | ** filename=NAME - Show information for content file NAME |
| 2106 | ** fn=NAME - "fn" is shorthand for "filename" |
| 2107 | ** ci=VERSION - The specific check-in to use for "filename=". |
| 2108 | ** |
| 2109 | ** The /artifact page show the complete content of a file |
| 2110 | ** identified by HASH as preformatted text. The |
| 2111 | ** /whatis page shows only a description of the file. The /file |
| 2112 | ** page shows the most recent version of the file or directory |
| 2113 | ** called NAME, or a list of the top-level directory if NAME is |
| 2114 | ** omitted. |
| 2115 | */ |
| 2116 | void artifact_page(void){ |
| 2117 | int rid = 0; |
| 2118 | Blob content; |
| 2119 | const char *zMime; |
| @@ -2128,139 +2132,138 @@ | |
| 2128 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2129 | const char *zLn = P("ln"); |
| 2130 | const char *zName = P("name"); |
| 2131 | const char *zCI = P("ci"); |
| 2132 | HQuery url; |
| 2133 | Blob dirname; |
| 2134 | char *zCIUuid = 0; |
| 2135 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2136 | char *zHeader = 0; |
| 2137 | |
| 2138 | login_check_credentials(); |
| 2139 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2140 | url_initialize(&url, g.zPath); |
| 2141 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 2142 | if( zCI ){ |
| 2143 | blob_zero(&dirname); |
| 2144 | hyperlinked_path(zName, &dirname, zCI, "dir", ""); |
| 2145 | blob_reset(&dirname); |
| 2146 | |
| 2147 | if( name_to_uuid2(zCI, "ci", &zCIUuid) ){ |
| 2148 | isSymbolicCI = (sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI)) != 0); |
| 2149 | } |
| 2150 | } |
| 2151 | if( isFile && zName ) { |
| 2152 | rid = artifact_from_ci_and_filename(0, "name"); |
| 2153 | }else{ |
| 2154 | rid = artifact_from_ci_and_filename(&url, 0); |
| 2155 | } |
| 2156 | if( rid==0 ){ |
| 2157 | url_add_parameter(&url, "name", zName); |
| 2158 | if( isFile ){ |
| 2159 | int isUnknownAtCI = 0; |
| 2160 | |
| 2161 | /* Do a top-level directory listing in /file mode if no argument |
| 2162 | ** specified */ |
| 2163 | if( zName==0 || zName[0]==0 ){ |
| 2164 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2165 | page_tree(); |
| 2166 | return; |
| 2167 | } |
| 2168 | /* Look for a single file with the given name */ |
| 2169 | rid = db_int(0, |
| 2170 | "SELECT fid FROM filename, mlink, event" |
| 2171 | " WHERE name=%Q" |
| 2172 | " AND mlink.fnid=filename.fnid" |
| 2173 | " AND event.objid=mlink.mid" |
| 2174 | " ORDER BY event.mtime DESC LIMIT 1", |
| 2175 | zName |
| 2176 | ); |
| 2177 | /* If found only by the name, then the file is unknown in the check-in. |
| 2178 | ** Possibly, the file was renamed/deleted. |
| 2179 | */ |
| 2180 | if( rid && zCIUuid ){ |
| 2181 | rid = 0; |
| 2182 | isUnknownAtCI = 1; |
| 2183 | } |
| 2184 | /* If no file called NAME exists, instead look for a directory |
| 2185 | ** with that name, and do a directory listing */ |
| 2186 | if( rid==0 ){ |
| 2187 | int nName = (int)strlen(zName); |
| 2188 | if( nName && zName[nName-1]=='/' ) nName--; |
| 2189 | if( db_exists( |
| 2190 | "SELECT 1 FROM filename" |
| 2191 | " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", |
| 2192 | nName, zName, nName+1, nName, zName |
| 2193 | ) ){ |
| 2194 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2195 | page_tree(); |
| 2196 | return; |
| 2197 | } |
| 2198 | } |
| 2199 | /* If no file or directory called NAME: issue an error */ |
| 2200 | if( rid==0 ){ |
| 2201 | if( isUnknownAtCI ){ |
| 2202 | if( isSymbolicCI ){ |
| 2203 | zHeader = mprintf("No such file at %s", zCI); |
| 2204 | }else{ |
| 2205 | zHeader = mprintf("No such file at [%S]", zCIUuid); |
| 2206 | } |
| 2207 | style_header("%s", zHeader); |
| 2208 | fossil_free(zHeader); |
| 2209 | @ File %z(href("%R/finfo?name=%T",zName))%h(zName)</a> is not known |
| 2210 | @ at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]. |
| 2211 | }else{ |
| 2212 | style_header("No such file"); |
| 2213 | @ File '%h(zName)' is not known in this repository. |
| 2214 | } |
| 2215 | style_footer(); |
| 2216 | return; |
| 2217 | } |
| 2218 | }else{ |
| 2219 | rid = name_to_rid_www("name"); |
| 2220 | } |
| 2221 | } |
| 2222 | |
| 2223 | if( rid==0 ){ |
| 2224 | style_header("No such artifact"); |
| 2225 | @ Artifact '%h(zName)' does not exist in this repository. |
| 2226 | style_footer(); |
| 2227 | return; |
| 2228 | } |
| 2229 | if( descOnly || P("verbose")!=0 ){ |
| 2230 | url_add_parameter(&url, "verbose", "1"); |
| 2231 | objdescFlags |= OBJDESC_DETAIL; |
| 2232 | } |
| 2233 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2234 | |
| 2235 | if( isFile ){ |
| 2236 | if( zCI ){ |
| 2237 | const char *zPath; |
| 2238 | Blob path; |
| 2239 | blob_zero(&path); |
| 2240 | hyperlinked_path(zName, &path, zCI, "dir", ""); |
| 2241 | zPath = blob_str(&path); |
| 2242 | @ <h2>File %s(zPath) at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>] |
| 2243 | @ </h2> |
| 2244 | blob_reset(&path); |
| 2245 | }else{ |
| 2246 | @ <h2>Latest version of file '%h(zName)':</h2> |
| 2247 | } |
| 2248 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2249 | }else{ |
| 2250 | @ <h2>Artifact |
| 2251 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2252 | if( g.perm.Setup ){ |
| 2253 | @ (%d(rid)):</h2> |
| 2254 | }else{ |
| 2255 | @ :</h2> |
| 2256 | } |
| 2257 | } |
| 2258 | blob_zero(&downloadName); |
| 2259 | asText = P("txt")!=0; |
| 2260 | if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2261 | objType = object_description(rid, objdescFlags, (isFile ? zName : 0),&downloadName); |
| 2262 | if( !descOnly && P("download")!=0 ){ |
| 2263 | cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName), |
| 2264 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2265 | /*NOTREACHED*/ |
| 2266 | } |
| @@ -2288,11 +2291,11 @@ | |
| 2288 | zHeader = mprintf("Artifact [%S]", zUuid); |
| 2289 | } |
| 2290 | style_header("%s", zHeader); |
| 2291 | fossil_free(zCIUuid); |
| 2292 | fossil_free(zHeader); |
| 2293 | if( g.perm.Admin ){ |
| 2294 | Stmt q; |
| 2295 | db_prepare(&q, |
| 2296 | "SELECT coalesce(user.login,rcvfrom.uid)," |
| 2297 | " datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr" |
| 2298 | " FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2299 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1664,12 +1664,12 @@ | |
| 1664 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1665 | cookie_link_parameter("diff","diff","2"); |
| 1666 | diffType = atoi(PD("diff","2")); |
| 1667 | cookie_render(); |
| 1668 | if( P("from") && P("to") ){ |
| 1669 | v1 = artifact_from_ci_and_filename("from"); |
| 1670 | v2 = artifact_from_ci_and_filename("to"); |
| 1671 | }else{ |
| 1672 | Stmt q; |
| 1673 | v1 = name_to_rid_www("v1"); |
| 1674 | v2 = name_to_rid_www("v2"); |
| 1675 | |
| @@ -1766,12 +1766,12 @@ | |
| 1766 | */ |
| 1767 | void rawartifact_page(void){ |
| 1768 | int rid = 0; |
| 1769 | char *zUuid; |
| 1770 | |
| 1771 | if( P("ci") ){ |
| 1772 | rid = artifact_from_ci_and_filename(0); |
| 1773 | } |
| 1774 | if( rid==0 ){ |
| 1775 | rid = name_to_rid_www("name"); |
| 1776 | } |
| 1777 | login_check_credentials(); |
| @@ -1945,59 +1945,53 @@ | |
| 1945 | |
| 1946 | /* |
| 1947 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1948 | ** use them to extract the record ID of an artifact for the file. |
| 1949 | ** |
| 1950 | ** Also look for "fn" and "name" as an aliases for "filename". If any |
| 1951 | ** "filename" or "fn" or "name" are present but "ci" is missing, then |
| 1952 | ** use "tip" as the default value for "ci". |
| 1953 | ** |
| 1954 | ** If zNameParam is not NULL, then use that parameter as the filename |
| 1955 | ** rather than "fn" or "filename" or "name". the zNameParam is used |
| 1956 | ** for the from= and to= query parameters of /fdiff. |
| 1957 | */ |
| 1958 | int artifact_from_ci_and_filename(const char *zNameParam){ |
| 1959 | const char *zFilename; |
| 1960 | const char *zCI; |
| 1961 | int cirid; |
| 1962 | Manifest *pManifest; |
| 1963 | ManifestFile *pFile; |
| 1964 | int rid = 0; |
| 1965 | |
| 1966 | if( zNameParam ){ |
| 1967 | zFilename = P(zNameParam); |
| 1968 | }else{ |
| 1969 | zFilename = P("filename"); |
| 1970 | if( zFilename==0 ){ |
| 1971 | zFilename = P("fn"); |
| 1972 | } |
| 1973 | if( zFilename==0 ){ |
| 1974 | zFilename = P("name"); |
| 1975 | } |
| 1976 | } |
| 1977 | if( zFilename==0 ) return 0; |
| 1978 | |
| 1979 | zCI = PD("ci", "tip"); |
| 1980 | cirid = name_to_typed_rid(zCI, "ci"); |
| 1981 | if( cirid<=0 ) return 0; |
| 1982 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1983 | if( pManifest==0 ) return 0; |
| 1984 | manifest_file_rewind(pManifest); |
| 1985 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1986 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1987 | rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 1988 | break; |
| 1989 | } |
| 1990 | } |
| 1991 | manifest_destroy(pManifest); |
| 1992 | return rid; |
| 1993 | } |
| 1994 | |
| 1995 | /* |
| 1996 | ** The "z" argument is a string that contains the text of a source code |
| 1997 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2099,21 +2093,31 @@ | |
| 2093 | ** ln=N - highlight line number N |
| 2094 | ** ln=M-N - highlight lines M through N inclusive |
| 2095 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2096 | ** verbose - show more detail in the description |
| 2097 | ** download - redirect to the download (artifact page only) |
| 2098 | ** name=NAME - filename or hash as a query parameter |
| 2099 | ** filename=NAME - alternative spelling for "name=" |
| 2100 | ** fn=NAME - alternative spelling for "name=" |
| 2101 | ** ci=VERSION - The specific check-in to use with "name=" to |
| 2102 | ** identify the file. |
| 2103 | ** |
| 2104 | ** The /artifact page show the complete content of a file |
| 2105 | ** identified by HASH. The /whatis page shows only a description |
| 2106 | ** of how the artifact is used. The /file page shows the most recent |
| 2107 | ** version of the file or directory called NAME, or a list of the |
| 2108 | ** top-level directory if NAME is omitted. |
| 2109 | ** |
| 2110 | ** For /artifact and /whatis, the name= query parameter can refer to |
| 2111 | ** either the name of a file, or an artifact hash. If the ci= query |
| 2112 | ** parameter is also present, then name= must refer to a file name. |
| 2113 | ** If ci= is omitted, then the hash interpretation is preferred but |
| 2114 | ** if name= cannot be understood as a hash, a default "tip" value is |
| 2115 | ** used for ci=. |
| 2116 | ** |
| 2117 | ** For /file, name= can only be interpreted as a filename. As before, |
| 2118 | ** a default value of "tip" is used for ci= if ci= is omitted. |
| 2119 | */ |
| 2120 | void artifact_page(void){ |
| 2121 | int rid = 0; |
| 2122 | Blob content; |
| 2123 | const char *zMime; |
| @@ -2128,139 +2132,138 @@ | |
| 2132 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2133 | const char *zLn = P("ln"); |
| 2134 | const char *zName = P("name"); |
| 2135 | const char *zCI = P("ci"); |
| 2136 | HQuery url; |
| 2137 | char *zCIUuid = 0; |
| 2138 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2139 | int isBranchCI = 0; /* ci= refers to a branch name */ |
| 2140 | char *zHeader = 0; |
| 2141 | |
| 2142 | login_check_credentials(); |
| 2143 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2144 | |
| 2145 | /* Capture and normalize the name= and ci= query parameters */ |
| 2146 | if( zName==0 ){ |
| 2147 | zName = P("filename"); |
| 2148 | if( zName==0 ){ |
| 2149 | zName = P("fn"); |
| 2150 | } |
| 2151 | } |
| 2152 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 2153 | if( zCI |
| 2154 | && name_to_uuid2(zCI, "ci", &zCIUuid) |
| 2155 | && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0 |
| 2156 | ){ |
| 2157 | isSymbolicCI = 1; |
| 2158 | isBranchCI = branch_includes_uuid(zCI, zCIUuid); |
| 2159 | } |
| 2160 | |
| 2161 | /* The name= query parameter (or at least one of its alternative |
| 2162 | ** spellings) is required. Except for /file, show a top-level |
| 2163 | ** directory listing if name= is omitted. |
| 2164 | */ |
| 2165 | if( zName==0 ){ |
| 2166 | if( isFile ){ |
| 2167 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2168 | page_tree(); |
| 2169 | return; |
| 2170 | } |
| 2171 | style_header("Missing name= query parameter"); |
| 2172 | @ The name= query parameter is missing |
| 2173 | style_footer(); |
| 2174 | return; |
| 2175 | } |
| 2176 | |
| 2177 | url_initialize(&url, g.zPath); |
| 2178 | url_add_parameter(&url, "name", zName); |
| 2179 | url_add_parameter(&url, "ci", zCI); |
| 2180 | |
| 2181 | if( zCI==0 && !isFile ){ |
| 2182 | /* If there is no ci= query parameter, then prefer to interpret |
| 2183 | ** name= as a hash for /artifact and /whatis. But for not for /file. |
| 2184 | ** For /file, a name= without a ci= while prefer to use the default |
| 2185 | ** "tip" value for ci=. */ |
| 2186 | rid = name_to_rid(zName); |
| 2187 | } |
| 2188 | if( rid==0 ){ |
| 2189 | rid = artifact_from_ci_and_filename(0); |
| 2190 | } |
| 2191 | |
| 2192 | if( rid==0 ){ /* Artifact not found */ |
| 2193 | if( isFile ){ |
| 2194 | /* For /file, also check to see if name= refers to a directory, |
| 2195 | ** and if so, do a listing for that directory */ |
| 2196 | int nName = (int)strlen(zName); |
| 2197 | if( nName && zName[nName-1]=='/' ) nName--; |
| 2198 | if( db_exists( |
| 2199 | "SELECT 1 FROM filename" |
| 2200 | " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", |
| 2201 | nName, zName, nName+1, nName, zName |
| 2202 | ) ){ |
| 2203 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2204 | page_tree(); |
| 2205 | return; |
| 2206 | } |
| 2207 | style_header("No such file"); |
| 2208 | @ File '%h(zName)' does not exist in this repository. |
| 2209 | }else{ |
| 2210 | style_header("No such artifact"); |
| 2211 | @ Artifact '%h(zName)' does not exist in this repository. |
| 2212 | } |
| 2213 | style_footer(); |
| 2214 | return; |
| 2215 | } |
| 2216 | |
| 2217 | if( descOnly || P("verbose")!=0 ){ |
| 2218 | url_add_parameter(&url, "verbose", "1"); |
| 2219 | objdescFlags |= OBJDESC_DETAIL; |
| 2220 | } |
| 2221 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2222 | |
| 2223 | asText = P("txt")!=0; |
| 2224 | if( isFile ){ |
| 2225 | if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2226 | zCI = "tip"; |
| 2227 | @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a> |
| 2228 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2229 | }else{ |
| 2230 | const char *zPath; |
| 2231 | Blob path; |
| 2232 | blob_zero(&path); |
| 2233 | hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); |
| 2234 | zPath = blob_str(&path); |
| 2235 | @ <h2>File %s(zPath) \ |
| 2236 | if( isBranchCI ){ |
| 2237 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2238 | }else if( isSymbolicCI ){ |
| 2239 | @ as of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2> |
| 2240 | }else{ |
| 2241 | @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2> |
| 2242 | } |
| 2243 | blob_reset(&path); |
| 2244 | } |
| 2245 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2246 | style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", |
| 2247 | zName, zCI); |
| 2248 | style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", |
| 2249 | zName, zCI); |
| 2250 | blob_init(&downloadName, zName, -1); |
| 2251 | objType = OBJTYPE_CONTENT; |
| 2252 | }else{ |
| 2253 | @ <h2>Artifact |
| 2254 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2255 | if( g.perm.Setup ){ |
| 2256 | @ (%d(rid)):</h2> |
| 2257 | }else{ |
| 2258 | @ :</h2> |
| 2259 | } |
| 2260 | blob_zero(&downloadName); |
| 2261 | if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2262 | objType = object_description(rid, objdescFlags, |
| 2263 | (isFile?zName:0), &downloadName); |
| 2264 | } |
| 2265 | if( !descOnly && P("download")!=0 ){ |
| 2266 | cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName), |
| 2267 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2268 | /*NOTREACHED*/ |
| 2269 | } |
| @@ -2288,11 +2291,11 @@ | |
| 2291 | zHeader = mprintf("Artifact [%S]", zUuid); |
| 2292 | } |
| 2293 | style_header("%s", zHeader); |
| 2294 | fossil_free(zCIUuid); |
| 2295 | fossil_free(zHeader); |
| 2296 | if( !isFile && g.perm.Admin ){ |
| 2297 | Stmt q; |
| 2298 | db_prepare(&q, |
| 2299 | "SELECT coalesce(user.login,rcvfrom.uid)," |
| 2300 | " datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr" |
| 2301 | " FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2302 |
+140
-137
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -1664,12 +1664,12 @@ | ||
| 1664 | 1664 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1665 | 1665 | cookie_link_parameter("diff","diff","2"); |
| 1666 | 1666 | diffType = atoi(PD("diff","2")); |
| 1667 | 1667 | cookie_render(); |
| 1668 | 1668 | if( P("from") && P("to") ){ |
| 1669 | - v1 = artifact_from_ci_and_filename(0, "from"); | |
| 1670 | - v2 = artifact_from_ci_and_filename(0, "to"); | |
| 1669 | + v1 = artifact_from_ci_and_filename("from"); | |
| 1670 | + v2 = artifact_from_ci_and_filename("to"); | |
| 1671 | 1671 | }else{ |
| 1672 | 1672 | Stmt q; |
| 1673 | 1673 | v1 = name_to_rid_www("v1"); |
| 1674 | 1674 | v2 = name_to_rid_www("v2"); |
| 1675 | 1675 | |
| @@ -1766,12 +1766,12 @@ | ||
| 1766 | 1766 | */ |
| 1767 | 1767 | void rawartifact_page(void){ |
| 1768 | 1768 | int rid = 0; |
| 1769 | 1769 | char *zUuid; |
| 1770 | 1770 | |
| 1771 | - if( P("ci") && P("filename") ){ | |
| 1772 | - rid = artifact_from_ci_and_filename(0, 0); | |
| 1771 | + if( P("ci") ){ | |
| 1772 | + rid = artifact_from_ci_and_filename(0); | |
| 1773 | 1773 | } |
| 1774 | 1774 | if( rid==0 ){ |
| 1775 | 1775 | rid = name_to_rid_www("name"); |
| 1776 | 1776 | } |
| 1777 | 1777 | login_check_credentials(); |
| @@ -1945,59 +1945,53 @@ | ||
| 1945 | 1945 | |
| 1946 | 1946 | /* |
| 1947 | 1947 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1948 | 1948 | ** use them to extract the record ID of an artifact for the file. |
| 1949 | 1949 | ** |
| 1950 | -** Also look for "fn" as an alias for "filename". If either "filename" | |
| 1951 | -** or "fn" is present but "ci" is missing, use "tip" as a default value | |
| 1952 | -** for "ci". | |
| 1953 | -** | |
| 1954 | -** If zNameParam is not NULL, this use that parameter as the filename | |
| 1955 | -** rather than "fn" or "filename". | |
| 1956 | -** | |
| 1957 | -** If pUrl is not NULL, then record the "ci" and "filename" values in | |
| 1958 | -** pUrl. | |
| 1959 | -** | |
| 1960 | -** At least one of pUrl or zNameParam must be NULL. | |
| 1950 | +** Also look for "fn" and "name" as an aliases for "filename". If any | |
| 1951 | +** "filename" or "fn" or "name" are present but "ci" is missing, then | |
| 1952 | +** use "tip" as the default value for "ci". | |
| 1953 | +** | |
| 1954 | +** If zNameParam is not NULL, then use that parameter as the filename | |
| 1955 | +** rather than "fn" or "filename" or "name". the zNameParam is used | |
| 1956 | +** for the from= and to= query parameters of /fdiff. | |
| 1961 | 1957 | */ |
| 1962 | -int artifact_from_ci_and_filename(HQuery *pUrl, const char *zNameParam){ | |
| 1958 | +int artifact_from_ci_and_filename(const char *zNameParam){ | |
| 1963 | 1959 | const char *zFilename; |
| 1964 | 1960 | const char *zCI; |
| 1965 | 1961 | int cirid; |
| 1966 | 1962 | Manifest *pManifest; |
| 1967 | 1963 | ManifestFile *pFile; |
| 1964 | + int rid = 0; | |
| 1968 | 1965 | |
| 1969 | 1966 | if( zNameParam ){ |
| 1970 | 1967 | zFilename = P(zNameParam); |
| 1971 | 1968 | }else{ |
| 1972 | 1969 | zFilename = P("filename"); |
| 1973 | 1970 | if( zFilename==0 ){ |
| 1974 | 1971 | zFilename = P("fn"); |
| 1975 | 1972 | } |
| 1973 | + if( zFilename==0 ){ | |
| 1974 | + zFilename = P("name"); | |
| 1975 | + } | |
| 1976 | 1976 | } |
| 1977 | 1977 | if( zFilename==0 ) return 0; |
| 1978 | 1978 | |
| 1979 | - zCI = P("ci"); | |
| 1980 | - cirid = name_to_typed_rid(zCI ? zCI : "tip", "ci"); | |
| 1979 | + zCI = PD("ci", "tip"); | |
| 1980 | + cirid = name_to_typed_rid(zCI, "ci"); | |
| 1981 | 1981 | if( cirid<=0 ) return 0; |
| 1982 | 1982 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1983 | 1983 | if( pManifest==0 ) return 0; |
| 1984 | 1984 | manifest_file_rewind(pManifest); |
| 1985 | 1985 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1986 | 1986 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1987 | - int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); | |
| 1988 | - manifest_destroy(pManifest); | |
| 1989 | - if( pUrl ){ | |
| 1990 | - assert( zNameParam==0 ); | |
| 1991 | - url_add_parameter(pUrl, "fn", zFilename); | |
| 1992 | - if( zCI ) url_add_parameter(pUrl, "ci", zCI); | |
| 1993 | - } | |
| 1994 | - return rid; | |
| 1987 | + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); | |
| 1988 | + break; | |
| 1995 | 1989 | } |
| 1996 | 1990 | } |
| 1997 | 1991 | manifest_destroy(pManifest); |
| 1998 | - return 0; | |
| 1992 | + return rid; | |
| 1999 | 1993 | } |
| 2000 | 1994 | |
| 2001 | 1995 | /* |
| 2002 | 1996 | ** The "z" argument is a string that contains the text of a source code |
| 2003 | 1997 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2099,21 +2093,31 @@ | ||
| 2099 | 2093 | ** ln=N - highlight line number N |
| 2100 | 2094 | ** ln=M-N - highlight lines M through N inclusive |
| 2101 | 2095 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2102 | 2096 | ** verbose - show more detail in the description |
| 2103 | 2097 | ** download - redirect to the download (artifact page only) |
| 2104 | -** name=SHA1HASH - Provide the SHA1HASH as a query parameter | |
| 2105 | -** filename=NAME - Show information for content file NAME | |
| 2106 | -** fn=NAME - "fn" is shorthand for "filename" | |
| 2107 | -** ci=VERSION - The specific check-in to use for "filename=". | |
| 2098 | +** name=NAME - filename or hash as a query parameter | |
| 2099 | +** filename=NAME - alternative spelling for "name=" | |
| 2100 | +** fn=NAME - alternative spelling for "name=" | |
| 2101 | +** ci=VERSION - The specific check-in to use with "name=" to | |
| 2102 | +** identify the file. | |
| 2108 | 2103 | ** |
| 2109 | 2104 | ** The /artifact page show the complete content of a file |
| 2110 | -** identified by HASH as preformatted text. The | |
| 2111 | -** /whatis page shows only a description of the file. The /file | |
| 2112 | -** page shows the most recent version of the file or directory | |
| 2113 | -** called NAME, or a list of the top-level directory if NAME is | |
| 2114 | -** omitted. | |
| 2105 | +** identified by HASH. The /whatis page shows only a description | |
| 2106 | +** of how the artifact is used. The /file page shows the most recent | |
| 2107 | +** version of the file or directory called NAME, or a list of the | |
| 2108 | +** top-level directory if NAME is omitted. | |
| 2109 | +** | |
| 2110 | +** For /artifact and /whatis, the name= query parameter can refer to | |
| 2111 | +** either the name of a file, or an artifact hash. If the ci= query | |
| 2112 | +** parameter is also present, then name= must refer to a file name. | |
| 2113 | +** If ci= is omitted, then the hash interpretation is preferred but | |
| 2114 | +** if name= cannot be understood as a hash, a default "tip" value is | |
| 2115 | +** used for ci=. | |
| 2116 | +** | |
| 2117 | +** For /file, name= can only be interpreted as a filename. As before, | |
| 2118 | +** a default value of "tip" is used for ci= if ci= is omitted. | |
| 2115 | 2119 | */ |
| 2116 | 2120 | void artifact_page(void){ |
| 2117 | 2121 | int rid = 0; |
| 2118 | 2122 | Blob content; |
| 2119 | 2123 | const char *zMime; |
| @@ -2128,139 +2132,138 @@ | ||
| 2128 | 2132 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2129 | 2133 | const char *zLn = P("ln"); |
| 2130 | 2134 | const char *zName = P("name"); |
| 2131 | 2135 | const char *zCI = P("ci"); |
| 2132 | 2136 | HQuery url; |
| 2133 | - Blob dirname; | |
| 2134 | 2137 | char *zCIUuid = 0; |
| 2135 | 2138 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2139 | + int isBranchCI = 0; /* ci= refers to a branch name */ | |
| 2136 | 2140 | char *zHeader = 0; |
| 2137 | 2141 | |
| 2138 | 2142 | login_check_credentials(); |
| 2139 | 2143 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2140 | - url_initialize(&url, g.zPath); | |
| 2141 | - if( zCI && strlen(zCI)==0 ){ zCI = 0; } | |
| 2142 | - if( zCI ){ | |
| 2143 | - blob_zero(&dirname); | |
| 2144 | - hyperlinked_path(zName, &dirname, zCI, "dir", ""); | |
| 2145 | - blob_reset(&dirname); | |
| 2146 | - | |
| 2147 | - if( name_to_uuid2(zCI, "ci", &zCIUuid) ){ | |
| 2148 | - isSymbolicCI = (sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI)) != 0); | |
| 2149 | - } | |
| 2150 | - } | |
| 2151 | - if( isFile && zName ) { | |
| 2152 | - rid = artifact_from_ci_and_filename(0, "name"); | |
| 2153 | - }else{ | |
| 2154 | - rid = artifact_from_ci_and_filename(&url, 0); | |
| 2155 | - } | |
| 2156 | - if( rid==0 ){ | |
| 2157 | - url_add_parameter(&url, "name", zName); | |
| 2158 | - if( isFile ){ | |
| 2159 | - int isUnknownAtCI = 0; | |
| 2160 | - | |
| 2161 | - /* Do a top-level directory listing in /file mode if no argument | |
| 2162 | - ** specified */ | |
| 2163 | - if( zName==0 || zName[0]==0 ){ | |
| 2164 | - if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2165 | - page_tree(); | |
| 2166 | - return; | |
| 2167 | - } | |
| 2168 | - /* Look for a single file with the given name */ | |
| 2169 | - rid = db_int(0, | |
| 2170 | - "SELECT fid FROM filename, mlink, event" | |
| 2171 | - " WHERE name=%Q" | |
| 2172 | - " AND mlink.fnid=filename.fnid" | |
| 2173 | - " AND event.objid=mlink.mid" | |
| 2174 | - " ORDER BY event.mtime DESC LIMIT 1", | |
| 2175 | - zName | |
| 2176 | - ); | |
| 2177 | - /* If found only by the name, then the file is unknown in the check-in. | |
| 2178 | - ** Possibly, the file was renamed/deleted. | |
| 2179 | - */ | |
| 2180 | - if( rid && zCIUuid ){ | |
| 2181 | - rid = 0; | |
| 2182 | - isUnknownAtCI = 1; | |
| 2183 | - } | |
| 2184 | - /* If no file called NAME exists, instead look for a directory | |
| 2185 | - ** with that name, and do a directory listing */ | |
| 2186 | - if( rid==0 ){ | |
| 2187 | - int nName = (int)strlen(zName); | |
| 2188 | - if( nName && zName[nName-1]=='/' ) nName--; | |
| 2189 | - if( db_exists( | |
| 2190 | - "SELECT 1 FROM filename" | |
| 2191 | - " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", | |
| 2192 | - nName, zName, nName+1, nName, zName | |
| 2193 | - ) ){ | |
| 2194 | - if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2195 | - page_tree(); | |
| 2196 | - return; | |
| 2197 | - } | |
| 2198 | - } | |
| 2199 | - /* If no file or directory called NAME: issue an error */ | |
| 2200 | - if( rid==0 ){ | |
| 2201 | - if( isUnknownAtCI ){ | |
| 2202 | - if( isSymbolicCI ){ | |
| 2203 | - zHeader = mprintf("No such file at %s", zCI); | |
| 2204 | - }else{ | |
| 2205 | - zHeader = mprintf("No such file at [%S]", zCIUuid); | |
| 2206 | - } | |
| 2207 | - style_header("%s", zHeader); | |
| 2208 | - fossil_free(zHeader); | |
| 2209 | - @ File %z(href("%R/finfo?name=%T",zName))%h(zName)</a> is not known | |
| 2210 | - @ at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]. | |
| 2211 | - }else{ | |
| 2212 | - style_header("No such file"); | |
| 2213 | - @ File '%h(zName)' is not known in this repository. | |
| 2214 | - } | |
| 2215 | - style_footer(); | |
| 2216 | - return; | |
| 2217 | - } | |
| 2218 | - }else{ | |
| 2219 | - rid = name_to_rid_www("name"); | |
| 2220 | - } | |
| 2221 | - } | |
| 2222 | - | |
| 2223 | - if( rid==0 ){ | |
| 2224 | - style_header("No such artifact"); | |
| 2225 | - @ Artifact '%h(zName)' does not exist in this repository. | |
| 2226 | - style_footer(); | |
| 2227 | - return; | |
| 2228 | - } | |
| 2144 | + | |
| 2145 | + /* Capture and normalize the name= and ci= query parameters */ | |
| 2146 | + if( zName==0 ){ | |
| 2147 | + zName = P("filename"); | |
| 2148 | + if( zName==0 ){ | |
| 2149 | + zName = P("fn"); | |
| 2150 | + } | |
| 2151 | + } | |
| 2152 | + if( zCI && strlen(zCI)==0 ){ zCI = 0; } | |
| 2153 | + if( zCI | |
| 2154 | + && name_to_uuid2(zCI, "ci", &zCIUuid) | |
| 2155 | + && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0 | |
| 2156 | + ){ | |
| 2157 | + isSymbolicCI = 1; | |
| 2158 | + isBranchCI = branch_includes_uuid(zCI, zCIUuid); | |
| 2159 | + } | |
| 2160 | + | |
| 2161 | + /* The name= query parameter (or at least one of its alternative | |
| 2162 | + ** spellings) is required. Except for /file, show a top-level | |
| 2163 | + ** directory listing if name= is omitted. | |
| 2164 | + */ | |
| 2165 | + if( zName==0 ){ | |
| 2166 | + if( isFile ){ | |
| 2167 | + if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2168 | + page_tree(); | |
| 2169 | + return; | |
| 2170 | + } | |
| 2171 | + style_header("Missing name= query parameter"); | |
| 2172 | + @ The name= query parameter is missing | |
| 2173 | + style_footer(); | |
| 2174 | + return; | |
| 2175 | + } | |
| 2176 | + | |
| 2177 | + url_initialize(&url, g.zPath); | |
| 2178 | + url_add_parameter(&url, "name", zName); | |
| 2179 | + url_add_parameter(&url, "ci", zCI); | |
| 2180 | + | |
| 2181 | + if( zCI==0 && !isFile ){ | |
| 2182 | + /* If there is no ci= query parameter, then prefer to interpret | |
| 2183 | + ** name= as a hash for /artifact and /whatis. But for not for /file. | |
| 2184 | + ** For /file, a name= without a ci= while prefer to use the default | |
| 2185 | + ** "tip" value for ci=. */ | |
| 2186 | + rid = name_to_rid(zName); | |
| 2187 | + } | |
| 2188 | + if( rid==0 ){ | |
| 2189 | + rid = artifact_from_ci_and_filename(0); | |
| 2190 | + } | |
| 2191 | + | |
| 2192 | + if( rid==0 ){ /* Artifact not found */ | |
| 2193 | + if( isFile ){ | |
| 2194 | + /* For /file, also check to see if name= refers to a directory, | |
| 2195 | + ** and if so, do a listing for that directory */ | |
| 2196 | + int nName = (int)strlen(zName); | |
| 2197 | + if( nName && zName[nName-1]=='/' ) nName--; | |
| 2198 | + if( db_exists( | |
| 2199 | + "SELECT 1 FROM filename" | |
| 2200 | + " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", | |
| 2201 | + nName, zName, nName+1, nName, zName | |
| 2202 | + ) ){ | |
| 2203 | + if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2204 | + page_tree(); | |
| 2205 | + return; | |
| 2206 | + } | |
| 2207 | + style_header("No such file"); | |
| 2208 | + @ File '%h(zName)' does not exist in this repository. | |
| 2209 | + }else{ | |
| 2210 | + style_header("No such artifact"); | |
| 2211 | + @ Artifact '%h(zName)' does not exist in this repository. | |
| 2212 | + } | |
| 2213 | + style_footer(); | |
| 2214 | + return; | |
| 2215 | + } | |
| 2216 | + | |
| 2229 | 2217 | if( descOnly || P("verbose")!=0 ){ |
| 2230 | 2218 | url_add_parameter(&url, "verbose", "1"); |
| 2231 | 2219 | objdescFlags |= OBJDESC_DETAIL; |
| 2232 | 2220 | } |
| 2233 | 2221 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2234 | 2222 | |
| 2223 | + asText = P("txt")!=0; | |
| 2235 | 2224 | if( isFile ){ |
| 2236 | - if( zCI ){ | |
| 2225 | + if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ | |
| 2226 | + zCI = "tip"; | |
| 2227 | + @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a> | |
| 2228 | + @ from the %z(href("%R/info/tip"))latest check-in</a></h2> | |
| 2229 | + }else{ | |
| 2237 | 2230 | const char *zPath; |
| 2238 | 2231 | Blob path; |
| 2239 | 2232 | blob_zero(&path); |
| 2240 | - hyperlinked_path(zName, &path, zCI, "dir", ""); | |
| 2233 | + hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); | |
| 2241 | 2234 | zPath = blob_str(&path); |
| 2242 | - @ <h2>File %s(zPath) at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>] | |
| 2243 | - @ </h2> | |
| 2235 | + @ <h2>File %s(zPath) \ | |
| 2236 | + if( isBranchCI ){ | |
| 2237 | + @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> | |
| 2238 | + }else if( isSymbolicCI ){ | |
| 2239 | + @ as of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2> | |
| 2240 | + }else{ | |
| 2241 | + @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2> | |
| 2242 | + } | |
| 2244 | 2243 | blob_reset(&path); |
| 2245 | - }else{ | |
| 2246 | - @ <h2>Latest version of file '%h(zName)':</h2> | |
| 2247 | 2244 | } |
| 2248 | 2245 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2246 | + style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", | |
| 2247 | + zName, zCI); | |
| 2248 | + style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", | |
| 2249 | + zName, zCI); | |
| 2250 | + blob_init(&downloadName, zName, -1); | |
| 2251 | + objType = OBJTYPE_CONTENT; | |
| 2249 | 2252 | }else{ |
| 2250 | 2253 | @ <h2>Artifact |
| 2251 | 2254 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2252 | 2255 | if( g.perm.Setup ){ |
| 2253 | 2256 | @ (%d(rid)):</h2> |
| 2254 | 2257 | }else{ |
| 2255 | 2258 | @ :</h2> |
| 2256 | 2259 | } |
| 2260 | + blob_zero(&downloadName); | |
| 2261 | + if( asText ) objdescFlags &= ~OBJDESC_BASE; | |
| 2262 | + objType = object_description(rid, objdescFlags, | |
| 2263 | + (isFile?zName:0), &downloadName); | |
| 2257 | 2264 | } |
| 2258 | - blob_zero(&downloadName); | |
| 2259 | - asText = P("txt")!=0; | |
| 2260 | - if( asText ) objdescFlags &= ~OBJDESC_BASE; | |
| 2261 | - objType = object_description(rid, objdescFlags, (isFile ? zName : 0),&downloadName); | |
| 2262 | 2265 | if( !descOnly && P("download")!=0 ){ |
| 2263 | 2266 | cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName), |
| 2264 | 2267 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2265 | 2268 | /*NOTREACHED*/ |
| 2266 | 2269 | } |
| @@ -2288,11 +2291,11 @@ | ||
| 2288 | 2291 | zHeader = mprintf("Artifact [%S]", zUuid); |
| 2289 | 2292 | } |
| 2290 | 2293 | style_header("%s", zHeader); |
| 2291 | 2294 | fossil_free(zCIUuid); |
| 2292 | 2295 | fossil_free(zHeader); |
| 2293 | - if( g.perm.Admin ){ | |
| 2296 | + if( !isFile && g.perm.Admin ){ | |
| 2294 | 2297 | Stmt q; |
| 2295 | 2298 | db_prepare(&q, |
| 2296 | 2299 | "SELECT coalesce(user.login,rcvfrom.uid)," |
| 2297 | 2300 | " datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr" |
| 2298 | 2301 | " FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2299 | 2302 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1664,12 +1664,12 @@ | |
| 1664 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1665 | cookie_link_parameter("diff","diff","2"); |
| 1666 | diffType = atoi(PD("diff","2")); |
| 1667 | cookie_render(); |
| 1668 | if( P("from") && P("to") ){ |
| 1669 | v1 = artifact_from_ci_and_filename(0, "from"); |
| 1670 | v2 = artifact_from_ci_and_filename(0, "to"); |
| 1671 | }else{ |
| 1672 | Stmt q; |
| 1673 | v1 = name_to_rid_www("v1"); |
| 1674 | v2 = name_to_rid_www("v2"); |
| 1675 | |
| @@ -1766,12 +1766,12 @@ | |
| 1766 | */ |
| 1767 | void rawartifact_page(void){ |
| 1768 | int rid = 0; |
| 1769 | char *zUuid; |
| 1770 | |
| 1771 | if( P("ci") && P("filename") ){ |
| 1772 | rid = artifact_from_ci_and_filename(0, 0); |
| 1773 | } |
| 1774 | if( rid==0 ){ |
| 1775 | rid = name_to_rid_www("name"); |
| 1776 | } |
| 1777 | login_check_credentials(); |
| @@ -1945,59 +1945,53 @@ | |
| 1945 | |
| 1946 | /* |
| 1947 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1948 | ** use them to extract the record ID of an artifact for the file. |
| 1949 | ** |
| 1950 | ** Also look for "fn" as an alias for "filename". If either "filename" |
| 1951 | ** or "fn" is present but "ci" is missing, use "tip" as a default value |
| 1952 | ** for "ci". |
| 1953 | ** |
| 1954 | ** If zNameParam is not NULL, this use that parameter as the filename |
| 1955 | ** rather than "fn" or "filename". |
| 1956 | ** |
| 1957 | ** If pUrl is not NULL, then record the "ci" and "filename" values in |
| 1958 | ** pUrl. |
| 1959 | ** |
| 1960 | ** At least one of pUrl or zNameParam must be NULL. |
| 1961 | */ |
| 1962 | int artifact_from_ci_and_filename(HQuery *pUrl, const char *zNameParam){ |
| 1963 | const char *zFilename; |
| 1964 | const char *zCI; |
| 1965 | int cirid; |
| 1966 | Manifest *pManifest; |
| 1967 | ManifestFile *pFile; |
| 1968 | |
| 1969 | if( zNameParam ){ |
| 1970 | zFilename = P(zNameParam); |
| 1971 | }else{ |
| 1972 | zFilename = P("filename"); |
| 1973 | if( zFilename==0 ){ |
| 1974 | zFilename = P("fn"); |
| 1975 | } |
| 1976 | } |
| 1977 | if( zFilename==0 ) return 0; |
| 1978 | |
| 1979 | zCI = P("ci"); |
| 1980 | cirid = name_to_typed_rid(zCI ? zCI : "tip", "ci"); |
| 1981 | if( cirid<=0 ) return 0; |
| 1982 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1983 | if( pManifest==0 ) return 0; |
| 1984 | manifest_file_rewind(pManifest); |
| 1985 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1986 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1987 | int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 1988 | manifest_destroy(pManifest); |
| 1989 | if( pUrl ){ |
| 1990 | assert( zNameParam==0 ); |
| 1991 | url_add_parameter(pUrl, "fn", zFilename); |
| 1992 | if( zCI ) url_add_parameter(pUrl, "ci", zCI); |
| 1993 | } |
| 1994 | return rid; |
| 1995 | } |
| 1996 | } |
| 1997 | manifest_destroy(pManifest); |
| 1998 | return 0; |
| 1999 | } |
| 2000 | |
| 2001 | /* |
| 2002 | ** The "z" argument is a string that contains the text of a source code |
| 2003 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2099,21 +2093,31 @@ | |
| 2099 | ** ln=N - highlight line number N |
| 2100 | ** ln=M-N - highlight lines M through N inclusive |
| 2101 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2102 | ** verbose - show more detail in the description |
| 2103 | ** download - redirect to the download (artifact page only) |
| 2104 | ** name=SHA1HASH - Provide the SHA1HASH as a query parameter |
| 2105 | ** filename=NAME - Show information for content file NAME |
| 2106 | ** fn=NAME - "fn" is shorthand for "filename" |
| 2107 | ** ci=VERSION - The specific check-in to use for "filename=". |
| 2108 | ** |
| 2109 | ** The /artifact page show the complete content of a file |
| 2110 | ** identified by HASH as preformatted text. The |
| 2111 | ** /whatis page shows only a description of the file. The /file |
| 2112 | ** page shows the most recent version of the file or directory |
| 2113 | ** called NAME, or a list of the top-level directory if NAME is |
| 2114 | ** omitted. |
| 2115 | */ |
| 2116 | void artifact_page(void){ |
| 2117 | int rid = 0; |
| 2118 | Blob content; |
| 2119 | const char *zMime; |
| @@ -2128,139 +2132,138 @@ | |
| 2128 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2129 | const char *zLn = P("ln"); |
| 2130 | const char *zName = P("name"); |
| 2131 | const char *zCI = P("ci"); |
| 2132 | HQuery url; |
| 2133 | Blob dirname; |
| 2134 | char *zCIUuid = 0; |
| 2135 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2136 | char *zHeader = 0; |
| 2137 | |
| 2138 | login_check_credentials(); |
| 2139 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2140 | url_initialize(&url, g.zPath); |
| 2141 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 2142 | if( zCI ){ |
| 2143 | blob_zero(&dirname); |
| 2144 | hyperlinked_path(zName, &dirname, zCI, "dir", ""); |
| 2145 | blob_reset(&dirname); |
| 2146 | |
| 2147 | if( name_to_uuid2(zCI, "ci", &zCIUuid) ){ |
| 2148 | isSymbolicCI = (sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI)) != 0); |
| 2149 | } |
| 2150 | } |
| 2151 | if( isFile && zName ) { |
| 2152 | rid = artifact_from_ci_and_filename(0, "name"); |
| 2153 | }else{ |
| 2154 | rid = artifact_from_ci_and_filename(&url, 0); |
| 2155 | } |
| 2156 | if( rid==0 ){ |
| 2157 | url_add_parameter(&url, "name", zName); |
| 2158 | if( isFile ){ |
| 2159 | int isUnknownAtCI = 0; |
| 2160 | |
| 2161 | /* Do a top-level directory listing in /file mode if no argument |
| 2162 | ** specified */ |
| 2163 | if( zName==0 || zName[0]==0 ){ |
| 2164 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2165 | page_tree(); |
| 2166 | return; |
| 2167 | } |
| 2168 | /* Look for a single file with the given name */ |
| 2169 | rid = db_int(0, |
| 2170 | "SELECT fid FROM filename, mlink, event" |
| 2171 | " WHERE name=%Q" |
| 2172 | " AND mlink.fnid=filename.fnid" |
| 2173 | " AND event.objid=mlink.mid" |
| 2174 | " ORDER BY event.mtime DESC LIMIT 1", |
| 2175 | zName |
| 2176 | ); |
| 2177 | /* If found only by the name, then the file is unknown in the check-in. |
| 2178 | ** Possibly, the file was renamed/deleted. |
| 2179 | */ |
| 2180 | if( rid && zCIUuid ){ |
| 2181 | rid = 0; |
| 2182 | isUnknownAtCI = 1; |
| 2183 | } |
| 2184 | /* If no file called NAME exists, instead look for a directory |
| 2185 | ** with that name, and do a directory listing */ |
| 2186 | if( rid==0 ){ |
| 2187 | int nName = (int)strlen(zName); |
| 2188 | if( nName && zName[nName-1]=='/' ) nName--; |
| 2189 | if( db_exists( |
| 2190 | "SELECT 1 FROM filename" |
| 2191 | " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", |
| 2192 | nName, zName, nName+1, nName, zName |
| 2193 | ) ){ |
| 2194 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2195 | page_tree(); |
| 2196 | return; |
| 2197 | } |
| 2198 | } |
| 2199 | /* If no file or directory called NAME: issue an error */ |
| 2200 | if( rid==0 ){ |
| 2201 | if( isUnknownAtCI ){ |
| 2202 | if( isSymbolicCI ){ |
| 2203 | zHeader = mprintf("No such file at %s", zCI); |
| 2204 | }else{ |
| 2205 | zHeader = mprintf("No such file at [%S]", zCIUuid); |
| 2206 | } |
| 2207 | style_header("%s", zHeader); |
| 2208 | fossil_free(zHeader); |
| 2209 | @ File %z(href("%R/finfo?name=%T",zName))%h(zName)</a> is not known |
| 2210 | @ at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]. |
| 2211 | }else{ |
| 2212 | style_header("No such file"); |
| 2213 | @ File '%h(zName)' is not known in this repository. |
| 2214 | } |
| 2215 | style_footer(); |
| 2216 | return; |
| 2217 | } |
| 2218 | }else{ |
| 2219 | rid = name_to_rid_www("name"); |
| 2220 | } |
| 2221 | } |
| 2222 | |
| 2223 | if( rid==0 ){ |
| 2224 | style_header("No such artifact"); |
| 2225 | @ Artifact '%h(zName)' does not exist in this repository. |
| 2226 | style_footer(); |
| 2227 | return; |
| 2228 | } |
| 2229 | if( descOnly || P("verbose")!=0 ){ |
| 2230 | url_add_parameter(&url, "verbose", "1"); |
| 2231 | objdescFlags |= OBJDESC_DETAIL; |
| 2232 | } |
| 2233 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2234 | |
| 2235 | if( isFile ){ |
| 2236 | if( zCI ){ |
| 2237 | const char *zPath; |
| 2238 | Blob path; |
| 2239 | blob_zero(&path); |
| 2240 | hyperlinked_path(zName, &path, zCI, "dir", ""); |
| 2241 | zPath = blob_str(&path); |
| 2242 | @ <h2>File %s(zPath) at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>] |
| 2243 | @ </h2> |
| 2244 | blob_reset(&path); |
| 2245 | }else{ |
| 2246 | @ <h2>Latest version of file '%h(zName)':</h2> |
| 2247 | } |
| 2248 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2249 | }else{ |
| 2250 | @ <h2>Artifact |
| 2251 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2252 | if( g.perm.Setup ){ |
| 2253 | @ (%d(rid)):</h2> |
| 2254 | }else{ |
| 2255 | @ :</h2> |
| 2256 | } |
| 2257 | } |
| 2258 | blob_zero(&downloadName); |
| 2259 | asText = P("txt")!=0; |
| 2260 | if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2261 | objType = object_description(rid, objdescFlags, (isFile ? zName : 0),&downloadName); |
| 2262 | if( !descOnly && P("download")!=0 ){ |
| 2263 | cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName), |
| 2264 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2265 | /*NOTREACHED*/ |
| 2266 | } |
| @@ -2288,11 +2291,11 @@ | |
| 2288 | zHeader = mprintf("Artifact [%S]", zUuid); |
| 2289 | } |
| 2290 | style_header("%s", zHeader); |
| 2291 | fossil_free(zCIUuid); |
| 2292 | fossil_free(zHeader); |
| 2293 | if( g.perm.Admin ){ |
| 2294 | Stmt q; |
| 2295 | db_prepare(&q, |
| 2296 | "SELECT coalesce(user.login,rcvfrom.uid)," |
| 2297 | " datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr" |
| 2298 | " FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2299 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1664,12 +1664,12 @@ | |
| 1664 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1665 | cookie_link_parameter("diff","diff","2"); |
| 1666 | diffType = atoi(PD("diff","2")); |
| 1667 | cookie_render(); |
| 1668 | if( P("from") && P("to") ){ |
| 1669 | v1 = artifact_from_ci_and_filename("from"); |
| 1670 | v2 = artifact_from_ci_and_filename("to"); |
| 1671 | }else{ |
| 1672 | Stmt q; |
| 1673 | v1 = name_to_rid_www("v1"); |
| 1674 | v2 = name_to_rid_www("v2"); |
| 1675 | |
| @@ -1766,12 +1766,12 @@ | |
| 1766 | */ |
| 1767 | void rawartifact_page(void){ |
| 1768 | int rid = 0; |
| 1769 | char *zUuid; |
| 1770 | |
| 1771 | if( P("ci") ){ |
| 1772 | rid = artifact_from_ci_and_filename(0); |
| 1773 | } |
| 1774 | if( rid==0 ){ |
| 1775 | rid = name_to_rid_www("name"); |
| 1776 | } |
| 1777 | login_check_credentials(); |
| @@ -1945,59 +1945,53 @@ | |
| 1945 | |
| 1946 | /* |
| 1947 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1948 | ** use them to extract the record ID of an artifact for the file. |
| 1949 | ** |
| 1950 | ** Also look for "fn" and "name" as an aliases for "filename". If any |
| 1951 | ** "filename" or "fn" or "name" are present but "ci" is missing, then |
| 1952 | ** use "tip" as the default value for "ci". |
| 1953 | ** |
| 1954 | ** If zNameParam is not NULL, then use that parameter as the filename |
| 1955 | ** rather than "fn" or "filename" or "name". the zNameParam is used |
| 1956 | ** for the from= and to= query parameters of /fdiff. |
| 1957 | */ |
| 1958 | int artifact_from_ci_and_filename(const char *zNameParam){ |
| 1959 | const char *zFilename; |
| 1960 | const char *zCI; |
| 1961 | int cirid; |
| 1962 | Manifest *pManifest; |
| 1963 | ManifestFile *pFile; |
| 1964 | int rid = 0; |
| 1965 | |
| 1966 | if( zNameParam ){ |
| 1967 | zFilename = P(zNameParam); |
| 1968 | }else{ |
| 1969 | zFilename = P("filename"); |
| 1970 | if( zFilename==0 ){ |
| 1971 | zFilename = P("fn"); |
| 1972 | } |
| 1973 | if( zFilename==0 ){ |
| 1974 | zFilename = P("name"); |
| 1975 | } |
| 1976 | } |
| 1977 | if( zFilename==0 ) return 0; |
| 1978 | |
| 1979 | zCI = PD("ci", "tip"); |
| 1980 | cirid = name_to_typed_rid(zCI, "ci"); |
| 1981 | if( cirid<=0 ) return 0; |
| 1982 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1983 | if( pManifest==0 ) return 0; |
| 1984 | manifest_file_rewind(pManifest); |
| 1985 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1986 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1987 | rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 1988 | break; |
| 1989 | } |
| 1990 | } |
| 1991 | manifest_destroy(pManifest); |
| 1992 | return rid; |
| 1993 | } |
| 1994 | |
| 1995 | /* |
| 1996 | ** The "z" argument is a string that contains the text of a source code |
| 1997 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2099,21 +2093,31 @@ | |
| 2093 | ** ln=N - highlight line number N |
| 2094 | ** ln=M-N - highlight lines M through N inclusive |
| 2095 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2096 | ** verbose - show more detail in the description |
| 2097 | ** download - redirect to the download (artifact page only) |
| 2098 | ** name=NAME - filename or hash as a query parameter |
| 2099 | ** filename=NAME - alternative spelling for "name=" |
| 2100 | ** fn=NAME - alternative spelling for "name=" |
| 2101 | ** ci=VERSION - The specific check-in to use with "name=" to |
| 2102 | ** identify the file. |
| 2103 | ** |
| 2104 | ** The /artifact page show the complete content of a file |
| 2105 | ** identified by HASH. The /whatis page shows only a description |
| 2106 | ** of how the artifact is used. The /file page shows the most recent |
| 2107 | ** version of the file or directory called NAME, or a list of the |
| 2108 | ** top-level directory if NAME is omitted. |
| 2109 | ** |
| 2110 | ** For /artifact and /whatis, the name= query parameter can refer to |
| 2111 | ** either the name of a file, or an artifact hash. If the ci= query |
| 2112 | ** parameter is also present, then name= must refer to a file name. |
| 2113 | ** If ci= is omitted, then the hash interpretation is preferred but |
| 2114 | ** if name= cannot be understood as a hash, a default "tip" value is |
| 2115 | ** used for ci=. |
| 2116 | ** |
| 2117 | ** For /file, name= can only be interpreted as a filename. As before, |
| 2118 | ** a default value of "tip" is used for ci= if ci= is omitted. |
| 2119 | */ |
| 2120 | void artifact_page(void){ |
| 2121 | int rid = 0; |
| 2122 | Blob content; |
| 2123 | const char *zMime; |
| @@ -2128,139 +2132,138 @@ | |
| 2132 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2133 | const char *zLn = P("ln"); |
| 2134 | const char *zName = P("name"); |
| 2135 | const char *zCI = P("ci"); |
| 2136 | HQuery url; |
| 2137 | char *zCIUuid = 0; |
| 2138 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2139 | int isBranchCI = 0; /* ci= refers to a branch name */ |
| 2140 | char *zHeader = 0; |
| 2141 | |
| 2142 | login_check_credentials(); |
| 2143 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2144 | |
| 2145 | /* Capture and normalize the name= and ci= query parameters */ |
| 2146 | if( zName==0 ){ |
| 2147 | zName = P("filename"); |
| 2148 | if( zName==0 ){ |
| 2149 | zName = P("fn"); |
| 2150 | } |
| 2151 | } |
| 2152 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 2153 | if( zCI |
| 2154 | && name_to_uuid2(zCI, "ci", &zCIUuid) |
| 2155 | && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0 |
| 2156 | ){ |
| 2157 | isSymbolicCI = 1; |
| 2158 | isBranchCI = branch_includes_uuid(zCI, zCIUuid); |
| 2159 | } |
| 2160 | |
| 2161 | /* The name= query parameter (or at least one of its alternative |
| 2162 | ** spellings) is required. Except for /file, show a top-level |
| 2163 | ** directory listing if name= is omitted. |
| 2164 | */ |
| 2165 | if( zName==0 ){ |
| 2166 | if( isFile ){ |
| 2167 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2168 | page_tree(); |
| 2169 | return; |
| 2170 | } |
| 2171 | style_header("Missing name= query parameter"); |
| 2172 | @ The name= query parameter is missing |
| 2173 | style_footer(); |
| 2174 | return; |
| 2175 | } |
| 2176 | |
| 2177 | url_initialize(&url, g.zPath); |
| 2178 | url_add_parameter(&url, "name", zName); |
| 2179 | url_add_parameter(&url, "ci", zCI); |
| 2180 | |
| 2181 | if( zCI==0 && !isFile ){ |
| 2182 | /* If there is no ci= query parameter, then prefer to interpret |
| 2183 | ** name= as a hash for /artifact and /whatis. But for not for /file. |
| 2184 | ** For /file, a name= without a ci= while prefer to use the default |
| 2185 | ** "tip" value for ci=. */ |
| 2186 | rid = name_to_rid(zName); |
| 2187 | } |
| 2188 | if( rid==0 ){ |
| 2189 | rid = artifact_from_ci_and_filename(0); |
| 2190 | } |
| 2191 | |
| 2192 | if( rid==0 ){ /* Artifact not found */ |
| 2193 | if( isFile ){ |
| 2194 | /* For /file, also check to see if name= refers to a directory, |
| 2195 | ** and if so, do a listing for that directory */ |
| 2196 | int nName = (int)strlen(zName); |
| 2197 | if( nName && zName[nName-1]=='/' ) nName--; |
| 2198 | if( db_exists( |
| 2199 | "SELECT 1 FROM filename" |
| 2200 | " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", |
| 2201 | nName, zName, nName+1, nName, zName |
| 2202 | ) ){ |
| 2203 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2204 | page_tree(); |
| 2205 | return; |
| 2206 | } |
| 2207 | style_header("No such file"); |
| 2208 | @ File '%h(zName)' does not exist in this repository. |
| 2209 | }else{ |
| 2210 | style_header("No such artifact"); |
| 2211 | @ Artifact '%h(zName)' does not exist in this repository. |
| 2212 | } |
| 2213 | style_footer(); |
| 2214 | return; |
| 2215 | } |
| 2216 | |
| 2217 | if( descOnly || P("verbose")!=0 ){ |
| 2218 | url_add_parameter(&url, "verbose", "1"); |
| 2219 | objdescFlags |= OBJDESC_DETAIL; |
| 2220 | } |
| 2221 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2222 | |
| 2223 | asText = P("txt")!=0; |
| 2224 | if( isFile ){ |
| 2225 | if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2226 | zCI = "tip"; |
| 2227 | @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a> |
| 2228 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2229 | }else{ |
| 2230 | const char *zPath; |
| 2231 | Blob path; |
| 2232 | blob_zero(&path); |
| 2233 | hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); |
| 2234 | zPath = blob_str(&path); |
| 2235 | @ <h2>File %s(zPath) \ |
| 2236 | if( isBranchCI ){ |
| 2237 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2238 | }else if( isSymbolicCI ){ |
| 2239 | @ as of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2> |
| 2240 | }else{ |
| 2241 | @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2> |
| 2242 | } |
| 2243 | blob_reset(&path); |
| 2244 | } |
| 2245 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2246 | style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", |
| 2247 | zName, zCI); |
| 2248 | style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", |
| 2249 | zName, zCI); |
| 2250 | blob_init(&downloadName, zName, -1); |
| 2251 | objType = OBJTYPE_CONTENT; |
| 2252 | }else{ |
| 2253 | @ <h2>Artifact |
| 2254 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2255 | if( g.perm.Setup ){ |
| 2256 | @ (%d(rid)):</h2> |
| 2257 | }else{ |
| 2258 | @ :</h2> |
| 2259 | } |
| 2260 | blob_zero(&downloadName); |
| 2261 | if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2262 | objType = object_description(rid, objdescFlags, |
| 2263 | (isFile?zName:0), &downloadName); |
| 2264 | } |
| 2265 | if( !descOnly && P("download")!=0 ){ |
| 2266 | cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName), |
| 2267 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2268 | /*NOTREACHED*/ |
| 2269 | } |
| @@ -2288,11 +2291,11 @@ | |
| 2291 | zHeader = mprintf("Artifact [%S]", zUuid); |
| 2292 | } |
| 2293 | style_header("%s", zHeader); |
| 2294 | fossil_free(zCIUuid); |
| 2295 | fossil_free(zHeader); |
| 2296 | if( !isFile && g.perm.Admin ){ |
| 2297 | Stmt q; |
| 2298 | db_prepare(&q, |
| 2299 | "SELECT coalesce(user.login,rcvfrom.uid)," |
| 2300 | " datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr" |
| 2301 | " FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2302 |
+38
| --- src/mkversion.c | ||
| +++ src/mkversion.c | ||
| @@ -9,27 +9,59 @@ | ||
| 9 | 9 | */ |
| 10 | 10 | #include <stdio.h> |
| 11 | 11 | #include <string.h> |
| 12 | 12 | #include <stdlib.h> |
| 13 | 13 | #include <ctype.h> |
| 14 | +#include <time.h> | |
| 14 | 15 | |
| 15 | 16 | static FILE *open_for_reading(const char *zFilename){ |
| 16 | 17 | FILE *f = fopen(zFilename, "r"); |
| 17 | 18 | if( f==0 ){ |
| 18 | 19 | fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); |
| 19 | 20 | exit(1); |
| 20 | 21 | } |
| 21 | 22 | return f; |
| 22 | 23 | } |
| 24 | + | |
| 25 | +/* | |
| 26 | +** Given an arbitrary-length input string key zIn, generate | |
| 27 | +** an N-byte hexadecimal hash of that string into zOut. | |
| 28 | +*/ | |
| 29 | +static void hash(const char *zIn, int N, char *zOut){ | |
| 30 | + unsigned char i, j, t; | |
| 31 | + int m, n; | |
| 32 | + unsigned char s[256]; | |
| 33 | + for(m=0; m<256; m++){ s[m] = m; } | |
| 34 | + for(j=0, m=n=0; m<256; m++, n++){ | |
| 35 | + j += s[m] + zIn[n]; | |
| 36 | + if( zIn[n]==0 ){ n = -1; } | |
| 37 | + t = s[j]; | |
| 38 | + s[j] = s[m]; | |
| 39 | + s[m] = t; | |
| 40 | + } | |
| 41 | + i = j = 0; | |
| 42 | + for(n=0; n<N-2; n+=2){ | |
| 43 | + i++; | |
| 44 | + t = s[i]; | |
| 45 | + j += t; | |
| 46 | + s[i] = s[j]; | |
| 47 | + s[j] = t; | |
| 48 | + t += s[i]; | |
| 49 | + zOut[n] = "0123456789abcdef"[(t>>4)&0xf]; | |
| 50 | + zOut[n+1] = "0123456789abcdef"[t&0xf]; | |
| 51 | + } | |
| 52 | + zOut[n] = 0; | |
| 53 | +} | |
| 23 | 54 | |
| 24 | 55 | int main(int argc, char *argv[]){ |
| 25 | 56 | FILE *m,*u,*v; |
| 26 | 57 | char *z; |
| 27 | 58 | #if defined(__DMC__) /* e.g. 0x857 */ |
| 28 | 59 | int i = 0; |
| 29 | 60 | #endif |
| 30 | 61 | int j = 0, x = 0, d = 0; |
| 62 | + size_t n; | |
| 31 | 63 | int vn[3]; |
| 32 | 64 | char b[1000]; |
| 33 | 65 | char vx[1000]; |
| 34 | 66 | if( argc!=4 ){ |
| 35 | 67 | fprintf(stderr, "Usage: %s manifest.uuid manifest VERSION\n", argv[0]); |
| @@ -45,10 +77,16 @@ | ||
| 45 | 77 | fclose(u); |
| 46 | 78 | for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){} |
| 47 | 79 | *z = 0; |
| 48 | 80 | printf("#define MANIFEST_UUID \"%s\"\n",b); |
| 49 | 81 | printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b); |
| 82 | + n = strlen(b); | |
| 83 | + if( n + 50 < sizeof(b) ){ | |
| 84 | + sprintf(b+n, "%d", (int)time(0)); | |
| 85 | + hash(b,33,vx); | |
| 86 | + printf("#define FOSSIL_BUILD_HASH \"%s\"\n", vx); | |
| 87 | + } | |
| 50 | 88 | m = open_for_reading(argv[2]); |
| 51 | 89 | while(b == fgets(b, sizeof(b)-1,m)){ |
| 52 | 90 | if(0 == strncmp("D ",b,2)){ |
| 53 | 91 | int k, n; |
| 54 | 92 | char zDateNum[30]; |
| 55 | 93 |
| --- src/mkversion.c | |
| +++ src/mkversion.c | |
| @@ -9,27 +9,59 @@ | |
| 9 | */ |
| 10 | #include <stdio.h> |
| 11 | #include <string.h> |
| 12 | #include <stdlib.h> |
| 13 | #include <ctype.h> |
| 14 | |
| 15 | static FILE *open_for_reading(const char *zFilename){ |
| 16 | FILE *f = fopen(zFilename, "r"); |
| 17 | if( f==0 ){ |
| 18 | fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); |
| 19 | exit(1); |
| 20 | } |
| 21 | return f; |
| 22 | } |
| 23 | |
| 24 | int main(int argc, char *argv[]){ |
| 25 | FILE *m,*u,*v; |
| 26 | char *z; |
| 27 | #if defined(__DMC__) /* e.g. 0x857 */ |
| 28 | int i = 0; |
| 29 | #endif |
| 30 | int j = 0, x = 0, d = 0; |
| 31 | int vn[3]; |
| 32 | char b[1000]; |
| 33 | char vx[1000]; |
| 34 | if( argc!=4 ){ |
| 35 | fprintf(stderr, "Usage: %s manifest.uuid manifest VERSION\n", argv[0]); |
| @@ -45,10 +77,16 @@ | |
| 45 | fclose(u); |
| 46 | for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){} |
| 47 | *z = 0; |
| 48 | printf("#define MANIFEST_UUID \"%s\"\n",b); |
| 49 | printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b); |
| 50 | m = open_for_reading(argv[2]); |
| 51 | while(b == fgets(b, sizeof(b)-1,m)){ |
| 52 | if(0 == strncmp("D ",b,2)){ |
| 53 | int k, n; |
| 54 | char zDateNum[30]; |
| 55 |
| --- src/mkversion.c | |
| +++ src/mkversion.c | |
| @@ -9,27 +9,59 @@ | |
| 9 | */ |
| 10 | #include <stdio.h> |
| 11 | #include <string.h> |
| 12 | #include <stdlib.h> |
| 13 | #include <ctype.h> |
| 14 | #include <time.h> |
| 15 | |
| 16 | static FILE *open_for_reading(const char *zFilename){ |
| 17 | FILE *f = fopen(zFilename, "r"); |
| 18 | if( f==0 ){ |
| 19 | fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); |
| 20 | exit(1); |
| 21 | } |
| 22 | return f; |
| 23 | } |
| 24 | |
| 25 | /* |
| 26 | ** Given an arbitrary-length input string key zIn, generate |
| 27 | ** an N-byte hexadecimal hash of that string into zOut. |
| 28 | */ |
| 29 | static void hash(const char *zIn, int N, char *zOut){ |
| 30 | unsigned char i, j, t; |
| 31 | int m, n; |
| 32 | unsigned char s[256]; |
| 33 | for(m=0; m<256; m++){ s[m] = m; } |
| 34 | for(j=0, m=n=0; m<256; m++, n++){ |
| 35 | j += s[m] + zIn[n]; |
| 36 | if( zIn[n]==0 ){ n = -1; } |
| 37 | t = s[j]; |
| 38 | s[j] = s[m]; |
| 39 | s[m] = t; |
| 40 | } |
| 41 | i = j = 0; |
| 42 | for(n=0; n<N-2; n+=2){ |
| 43 | i++; |
| 44 | t = s[i]; |
| 45 | j += t; |
| 46 | s[i] = s[j]; |
| 47 | s[j] = t; |
| 48 | t += s[i]; |
| 49 | zOut[n] = "0123456789abcdef"[(t>>4)&0xf]; |
| 50 | zOut[n+1] = "0123456789abcdef"[t&0xf]; |
| 51 | } |
| 52 | zOut[n] = 0; |
| 53 | } |
| 54 | |
| 55 | int main(int argc, char *argv[]){ |
| 56 | FILE *m,*u,*v; |
| 57 | char *z; |
| 58 | #if defined(__DMC__) /* e.g. 0x857 */ |
| 59 | int i = 0; |
| 60 | #endif |
| 61 | int j = 0, x = 0, d = 0; |
| 62 | size_t n; |
| 63 | int vn[3]; |
| 64 | char b[1000]; |
| 65 | char vx[1000]; |
| 66 | if( argc!=4 ){ |
| 67 | fprintf(stderr, "Usage: %s manifest.uuid manifest VERSION\n", argv[0]); |
| @@ -45,10 +77,16 @@ | |
| 77 | fclose(u); |
| 78 | for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){} |
| 79 | *z = 0; |
| 80 | printf("#define MANIFEST_UUID \"%s\"\n",b); |
| 81 | printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b); |
| 82 | n = strlen(b); |
| 83 | if( n + 50 < sizeof(b) ){ |
| 84 | sprintf(b+n, "%d", (int)time(0)); |
| 85 | hash(b,33,vx); |
| 86 | printf("#define FOSSIL_BUILD_HASH \"%s\"\n", vx); |
| 87 | } |
| 88 | m = open_for_reading(argv[2]); |
| 89 | while(b == fgets(b, sizeof(b)-1,m)){ |
| 90 | if(0 == strncmp("D ",b,2)){ |
| 91 | int k, n; |
| 92 | char zDateNum[30]; |
| 93 |
+2
-2
| --- src/shun.c | ||
| +++ src/shun.c | ||
| @@ -187,11 +187,11 @@ | ||
| 187 | 187 | @ sight - set the "hidden" tag on such artifacts instead.</p> |
| 188 | 188 | @ |
| 189 | 189 | @ <blockquote> |
| 190 | 190 | @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> |
| 191 | 191 | login_insert_csrf_secret(); |
| 192 | - @ <textarea class="fullsize-text" cols="50" rows="%d(numRows)" name="uuid"> | |
| 192 | + @ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid"> | |
| 193 | 193 | if( zShun ){ |
| 194 | 194 | if( strlen(zShun) ){ |
| 195 | 195 | @ %h(zShun) |
| 196 | 196 | }else if( nRcvid ){ |
| 197 | 197 | db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid); |
| @@ -214,11 +214,11 @@ | ||
| 214 | 214 | @ operations.</p> |
| 215 | 215 | @ |
| 216 | 216 | @ <blockquote> |
| 217 | 217 | @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> |
| 218 | 218 | login_insert_csrf_secret(); |
| 219 | - @ <textarea class="fullsize-text" cols="50" rows="%d(numRows)" name="uuid"> | |
| 219 | + @ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid"> | |
| 220 | 220 | if( zAccept ){ |
| 221 | 221 | if( strlen(zAccept) ){ |
| 222 | 222 | @ %h(zAccept) |
| 223 | 223 | }else if( nRcvid ){ |
| 224 | 224 | db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid); |
| 225 | 225 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -187,11 +187,11 @@ | |
| 187 | @ sight - set the "hidden" tag on such artifacts instead.</p> |
| 188 | @ |
| 189 | @ <blockquote> |
| 190 | @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> |
| 191 | login_insert_csrf_secret(); |
| 192 | @ <textarea class="fullsize-text" cols="50" rows="%d(numRows)" name="uuid"> |
| 193 | if( zShun ){ |
| 194 | if( strlen(zShun) ){ |
| 195 | @ %h(zShun) |
| 196 | }else if( nRcvid ){ |
| 197 | db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid); |
| @@ -214,11 +214,11 @@ | |
| 214 | @ operations.</p> |
| 215 | @ |
| 216 | @ <blockquote> |
| 217 | @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> |
| 218 | login_insert_csrf_secret(); |
| 219 | @ <textarea class="fullsize-text" cols="50" rows="%d(numRows)" name="uuid"> |
| 220 | if( zAccept ){ |
| 221 | if( strlen(zAccept) ){ |
| 222 | @ %h(zAccept) |
| 223 | }else if( nRcvid ){ |
| 224 | db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid); |
| 225 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -187,11 +187,11 @@ | |
| 187 | @ sight - set the "hidden" tag on such artifacts instead.</p> |
| 188 | @ |
| 189 | @ <blockquote> |
| 190 | @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> |
| 191 | login_insert_csrf_secret(); |
| 192 | @ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid"> |
| 193 | if( zShun ){ |
| 194 | if( strlen(zShun) ){ |
| 195 | @ %h(zShun) |
| 196 | }else if( nRcvid ){ |
| 197 | db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid); |
| @@ -214,11 +214,11 @@ | |
| 214 | @ operations.</p> |
| 215 | @ |
| 216 | @ <blockquote> |
| 217 | @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> |
| 218 | login_insert_csrf_secret(); |
| 219 | @ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid"> |
| 220 | if( zAccept ){ |
| 221 | if( strlen(zAccept) ){ |
| 222 | @ %h(zAccept) |
| 223 | }else if( nRcvid ){ |
| 224 | db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid); |
| 225 |
+10
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -2201,10 +2201,17 @@ | ||
| 2201 | 2201 | "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);" |
| 2202 | 2202 | "INSERT OR IGNORE INTO selected_nodes" |
| 2203 | 2203 | " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag" |
| 2204 | 2204 | " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/ |
| 2205 | 2205 | ); |
| 2206 | + if( zMark ){ | |
| 2207 | + /* If the t=release option is used with m=UUID, then also | |
| 2208 | + ** include the UUID check-in in the display list */ | |
| 2209 | + int ridMark = name_to_rid(zMark); | |
| 2210 | + db_multi_exec( | |
| 2211 | + "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark); | |
| 2212 | + } | |
| 2206 | 2213 | if( !related ){ |
| 2207 | 2214 | blob_append_sql(&cond, " AND blob.rid IN selected_nodes"); |
| 2208 | 2215 | }else{ |
| 2209 | 2216 | db_multi_exec( |
| 2210 | 2217 | "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);" |
| @@ -2427,10 +2434,13 @@ | ||
| 2427 | 2434 | blob_appendf(&desc, " related to tags matching %h", zMatchDesc); |
| 2428 | 2435 | }else{ |
| 2429 | 2436 | blob_appendf(&desc, " with tags matching %h", zMatchDesc); |
| 2430 | 2437 | } |
| 2431 | 2438 | } |
| 2439 | + if( zMark ){ | |
| 2440 | + blob_appendf(&desc," plus check-in \"%h\"", zMark); | |
| 2441 | + } | |
| 2432 | 2442 | tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS; |
| 2433 | 2443 | } |
| 2434 | 2444 | addFileGlobDescription(zChng, &desc); |
| 2435 | 2445 | if( rAfter>0.0 ){ |
| 2436 | 2446 | if( rBefore>0.0 ){ |
| 2437 | 2447 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -2201,10 +2201,17 @@ | |
| 2201 | "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);" |
| 2202 | "INSERT OR IGNORE INTO selected_nodes" |
| 2203 | " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag" |
| 2204 | " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/ |
| 2205 | ); |
| 2206 | if( !related ){ |
| 2207 | blob_append_sql(&cond, " AND blob.rid IN selected_nodes"); |
| 2208 | }else{ |
| 2209 | db_multi_exec( |
| 2210 | "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);" |
| @@ -2427,10 +2434,13 @@ | |
| 2427 | blob_appendf(&desc, " related to tags matching %h", zMatchDesc); |
| 2428 | }else{ |
| 2429 | blob_appendf(&desc, " with tags matching %h", zMatchDesc); |
| 2430 | } |
| 2431 | } |
| 2432 | tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS; |
| 2433 | } |
| 2434 | addFileGlobDescription(zChng, &desc); |
| 2435 | if( rAfter>0.0 ){ |
| 2436 | if( rBefore>0.0 ){ |
| 2437 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -2201,10 +2201,17 @@ | |
| 2201 | "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);" |
| 2202 | "INSERT OR IGNORE INTO selected_nodes" |
| 2203 | " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag" |
| 2204 | " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/ |
| 2205 | ); |
| 2206 | if( zMark ){ |
| 2207 | /* If the t=release option is used with m=UUID, then also |
| 2208 | ** include the UUID check-in in the display list */ |
| 2209 | int ridMark = name_to_rid(zMark); |
| 2210 | db_multi_exec( |
| 2211 | "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark); |
| 2212 | } |
| 2213 | if( !related ){ |
| 2214 | blob_append_sql(&cond, " AND blob.rid IN selected_nodes"); |
| 2215 | }else{ |
| 2216 | db_multi_exec( |
| 2217 | "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);" |
| @@ -2427,10 +2434,13 @@ | |
| 2434 | blob_appendf(&desc, " related to tags matching %h", zMatchDesc); |
| 2435 | }else{ |
| 2436 | blob_appendf(&desc, " with tags matching %h", zMatchDesc); |
| 2437 | } |
| 2438 | } |
| 2439 | if( zMark ){ |
| 2440 | blob_appendf(&desc," plus check-in \"%h\"", zMark); |
| 2441 | } |
| 2442 | tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS; |
| 2443 | } |
| 2444 | addFileGlobDescription(zChng, &desc); |
| 2445 | if( rAfter>0.0 ){ |
| 2446 | if( rBefore>0.0 ){ |
| 2447 |