| | @@ -2102,19 +2102,19 @@ |
| 2102 | 2102 | ** identified by HASH. The /whatis page shows only a description |
| 2103 | 2103 | ** of how the artifact is used. The /file page shows the most recent |
| 2104 | 2104 | ** version of the file or directory called NAME, or a list of the |
| 2105 | 2105 | ** top-level directory if NAME is omitted. |
| 2106 | 2106 | ** |
| 2107 | | -** The name= query parameter can refer to either the name of a file, |
| 2108 | | -** or an artifact hash. If the ci= query parameter is also present, |
| 2109 | | -** then name= must refer to a file name. If ci= is omitted, either |
| 2110 | | -** interpretation may be used. When name= is a filename and ci= |
| 2111 | | -** is omitted, a default value of "tip" is used for ci=. |
| 2107 | +** For /artifact and /whatis, the name= query parameter can refer to |
| 2108 | +** either the name of a file, or an artifact hash. If the ci= query |
| 2109 | +** parameter is also present, then name= must refer to a file name. |
| 2110 | +** If ci= is omitted, then the hash interpretation is preferred but |
| 2111 | +** if name= cannot be understood as a hash, a default "tip" value is |
| 2112 | +** used for ci=. |
| 2112 | 2113 | ** |
| 2113 | | -** If name= is ambiguous in that it might be either a filename or |
| 2114 | | -** a hash, then the hash interpretation is preferred for /artifact |
| 2115 | | -** and /whatis and the filename interpretation is preferred for /file. |
| 2114 | +** For /file, name= can only be interpreted as a filename. As before, |
| 2115 | +** a default value of "tip" is used for ci= if ci= is omitted. |
| 2116 | 2116 | */ |
| 2117 | 2117 | void artifact_page(void){ |
| 2118 | 2118 | int rid = 0; |
| 2119 | 2119 | Blob content; |
| 2120 | 2120 | const char *zMime; |
| | @@ -2189,51 +2189,52 @@ |
| 2189 | 2189 | rid = name_to_rid(zName); |
| 2190 | 2190 | } |
| 2191 | 2191 | if( rid==0 ){ |
| 2192 | 2192 | rid = artifact_from_ci_and_filename(0); |
| 2193 | 2193 | } |
| 2194 | | - if( rid==0 && zCI==0 && isFile ){ |
| 2195 | | - /* For /file, only try to interpret name= as a hash if it did not |
| 2196 | | - ** match any known filename. */ |
| 2197 | | - rid = name_to_rid(zName); |
| 2198 | | - } |
| 2199 | | - if( rid==0 && isFile ){ |
| 2200 | | - /* If no file called NAME exists, instead look for a directory |
| 2201 | | - ** with that name, and do a directory listing */ |
| 2202 | | - int nName = (int)strlen(zName); |
| 2203 | | - if( nName && zName[nName-1]=='/' ) nName--; |
| 2204 | | - if( db_exists( |
| 2205 | | - "SELECT 1 FROM filename" |
| 2206 | | - " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", |
| 2207 | | - nName, zName, nName+1, nName, zName |
| 2208 | | - ) ){ |
| 2209 | | - if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2210 | | - page_tree(); |
| 2211 | | - return; |
| 2212 | | - } |
| 2213 | | - } |
| 2214 | | - |
| 2215 | | - if( rid==0 ){ |
| 2216 | | - style_header("No such artifact"); |
| 2217 | | - @ Artifact '%h(zName)' does not exist in this repository. |
| 2194 | + |
| 2195 | + if( rid==0 ){ /* Artifact not found */ |
| 2196 | + if( isFile ){ |
| 2197 | + /* For /file, also check to see if name= refers to a directory, |
| 2198 | + ** and if so, do a listing for that directory */ |
| 2199 | + int nName = (int)strlen(zName); |
| 2200 | + if( nName && zName[nName-1]=='/' ) nName--; |
| 2201 | + if( db_exists( |
| 2202 | + "SELECT 1 FROM filename" |
| 2203 | + " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", |
| 2204 | + nName, zName, nName+1, nName, zName |
| 2205 | + ) ){ |
| 2206 | + if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2207 | + page_tree(); |
| 2208 | + return; |
| 2209 | + } |
| 2210 | + style_header("No such file"); |
| 2211 | + @ File '%h(zName)' does not exist in this repository. |
| 2212 | + }else{ |
| 2213 | + style_header("No such artifact"); |
| 2214 | + @ Artifact '%h(zName)' does not exist in this repository. |
| 2215 | + } |
| 2218 | 2216 | style_footer(); |
| 2219 | 2217 | return; |
| 2220 | 2218 | } |
| 2219 | + |
| 2221 | 2220 | if( descOnly || P("verbose")!=0 ){ |
| 2222 | 2221 | url_add_parameter(&url, "verbose", "1"); |
| 2223 | 2222 | objdescFlags |= OBJDESC_DETAIL; |
| 2224 | 2223 | } |
| 2225 | 2224 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2226 | 2225 | |
| 2227 | 2226 | if( isFile ){ |
| 2228 | | - if( zCI==0 ){ |
| 2229 | | - @ <h2>Latest version of file '%h(zName)':</h2> |
| 2227 | + if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2228 | + zCI = "tip"; |
| 2229 | + @ <h2>Latest version of file \ |
| 2230 | + @ '%z(href("/finfo?name=%T&m=%T",zName,zCI))%h(zName)</a>':</h2> |
| 2230 | 2231 | }else{ |
| 2231 | 2232 | const char *zPath; |
| 2232 | 2233 | Blob path; |
| 2233 | 2234 | blob_zero(&path); |
| 2234 | | - hyperlinked_path(zName, &path, zCI, "dir", ""); |
| 2235 | + hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); |
| 2235 | 2236 | zPath = blob_str(&path); |
| 2236 | 2237 | @ <h2>File %s(zPath) \ |
| 2237 | 2238 | if( isBranchCI ){ |
| 2238 | 2239 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2239 | 2240 | }else if( isSymbolicCI ){ |
| | @@ -2242,23 +2243,30 @@ |
| 2242 | 2243 | @ as of check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2> |
| 2243 | 2244 | } |
| 2244 | 2245 | blob_reset(&path); |
| 2245 | 2246 | } |
| 2246 | 2247 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2248 | + style_submenu_element("Annotate", "%R/annotate?filename=%T&ci=%T", |
| 2249 | + zName, zCI); |
| 2250 | + style_submenu_element("Blame", "%R/blame?filename=%T&ci=%T", |
| 2251 | + zName, zCI); |
| 2252 | + blob_init(&downloadName, zName, -1); |
| 2253 | + objType = OBJTYPE_CONTENT; |
| 2247 | 2254 | }else{ |
| 2248 | 2255 | @ <h2>Artifact |
| 2249 | 2256 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2250 | 2257 | if( g.perm.Setup ){ |
| 2251 | 2258 | @ (%d(rid)):</h2> |
| 2252 | 2259 | }else{ |
| 2253 | 2260 | @ :</h2> |
| 2254 | 2261 | } |
| 2262 | + blob_zero(&downloadName); |
| 2263 | + asText = P("txt")!=0; |
| 2264 | + if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2265 | + objType = object_description(rid, objdescFlags, |
| 2266 | + (isFile?zName:0), &downloadName); |
| 2255 | 2267 | } |
| 2256 | | - blob_zero(&downloadName); |
| 2257 | | - asText = P("txt")!=0; |
| 2258 | | - if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2259 | | - objType = object_description(rid, objdescFlags, (isFile ? zName : 0),&downloadName); |
| 2260 | 2268 | if( !descOnly && P("download")!=0 ){ |
| 2261 | 2269 | cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName), |
| 2262 | 2270 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2263 | 2271 | /*NOTREACHED*/ |
| 2264 | 2272 | } |
| | @@ -2286,11 +2294,11 @@ |
| 2286 | 2294 | zHeader = mprintf("Artifact [%S]", zUuid); |
| 2287 | 2295 | } |
| 2288 | 2296 | style_header("%s", zHeader); |
| 2289 | 2297 | fossil_free(zCIUuid); |
| 2290 | 2298 | fossil_free(zHeader); |
| 2291 | | - if( g.perm.Admin ){ |
| 2299 | + if( !isFile && g.perm.Admin ){ |
| 2292 | 2300 | Stmt q; |
| 2293 | 2301 | db_prepare(&q, |
| 2294 | 2302 | "SELECT coalesce(user.login,rcvfrom.uid)," |
| 2295 | 2303 | " datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr" |
| 2296 | 2304 | " FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2297 | 2305 | |