Fossil SCM
Continuing UX improvements on the /file, /artifact, and /whatis pages. More needs to be done.
Commit
1b5d0b0e18d6372afa5e848a053c79cc78601a63cf4b49ec38bf3111e54ba3db
Parent
26011322cd34934…
1 file changed
+117
-120
+117
-120
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -1661,12 +1661,12 @@ | ||
| 1661 | 1661 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1662 | 1662 | cookie_link_parameter("diff","diff","2"); |
| 1663 | 1663 | diffType = atoi(PD("diff","2")); |
| 1664 | 1664 | cookie_render(); |
| 1665 | 1665 | if( P("from") && P("to") ){ |
| 1666 | - v1 = artifact_from_ci_and_filename(0, "from"); | |
| 1667 | - v2 = artifact_from_ci_and_filename(0, "to"); | |
| 1666 | + v1 = artifact_from_ci_and_filename("from"); | |
| 1667 | + v2 = artifact_from_ci_and_filename("to"); | |
| 1668 | 1668 | }else{ |
| 1669 | 1669 | Stmt q; |
| 1670 | 1670 | v1 = name_to_rid_www("v1"); |
| 1671 | 1671 | v2 = name_to_rid_www("v2"); |
| 1672 | 1672 | |
| @@ -1763,12 +1763,12 @@ | ||
| 1763 | 1763 | */ |
| 1764 | 1764 | void rawartifact_page(void){ |
| 1765 | 1765 | int rid = 0; |
| 1766 | 1766 | char *zUuid; |
| 1767 | 1767 | |
| 1768 | - if( P("ci") && P("filename") ){ | |
| 1769 | - rid = artifact_from_ci_and_filename(0, 0); | |
| 1768 | + if( P("ci") ){ | |
| 1769 | + rid = artifact_from_ci_and_filename(0); | |
| 1770 | 1770 | } |
| 1771 | 1771 | if( rid==0 ){ |
| 1772 | 1772 | rid = name_to_rid_www("name"); |
| 1773 | 1773 | } |
| 1774 | 1774 | login_check_credentials(); |
| @@ -1942,59 +1942,53 @@ | ||
| 1942 | 1942 | |
| 1943 | 1943 | /* |
| 1944 | 1944 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1945 | 1945 | ** use them to extract the record ID of an artifact for the file. |
| 1946 | 1946 | ** |
| 1947 | -** Also look for "fn" as an alias for "filename". If either "filename" | |
| 1948 | -** or "fn" is present but "ci" is missing, use "tip" as a default value | |
| 1949 | -** for "ci". | |
| 1950 | -** | |
| 1951 | -** If zNameParam is not NULL, this use that parameter as the filename | |
| 1952 | -** rather than "fn" or "filename". | |
| 1953 | -** | |
| 1954 | -** If pUrl is not NULL, then record the "ci" and "filename" values in | |
| 1955 | -** pUrl. | |
| 1956 | -** | |
| 1957 | -** At least one of pUrl or zNameParam must be NULL. | |
| 1947 | +** Also look for "fn" and "name" as an aliases for "filename". If any | |
| 1948 | +** "filename" or "fn" or "name" are present but "ci" is missing, then | |
| 1949 | +** use "tip" as the default value for "ci". | |
| 1950 | +** | |
| 1951 | +** If zNameParam is not NULL, then use that parameter as the filename | |
| 1952 | +** rather than "fn" or "filename" or "name". the zNameParam is used | |
| 1953 | +** for the from= and to= query parameters of /fdiff. | |
| 1958 | 1954 | */ |
| 1959 | -int artifact_from_ci_and_filename(HQuery *pUrl, const char *zNameParam){ | |
| 1955 | +int artifact_from_ci_and_filename(const char *zNameParam){ | |
| 1960 | 1956 | const char *zFilename; |
| 1961 | 1957 | const char *zCI; |
| 1962 | 1958 | int cirid; |
| 1963 | 1959 | Manifest *pManifest; |
| 1964 | 1960 | ManifestFile *pFile; |
| 1961 | + int rid = 0; | |
| 1965 | 1962 | |
| 1966 | 1963 | if( zNameParam ){ |
| 1967 | 1964 | zFilename = P(zNameParam); |
| 1968 | 1965 | }else{ |
| 1969 | 1966 | zFilename = P("filename"); |
| 1970 | 1967 | if( zFilename==0 ){ |
| 1971 | 1968 | zFilename = P("fn"); |
| 1972 | 1969 | } |
| 1970 | + if( zFilename==0 ){ | |
| 1971 | + zFilename = P("name"); | |
| 1972 | + } | |
| 1973 | 1973 | } |
| 1974 | 1974 | if( zFilename==0 ) return 0; |
| 1975 | 1975 | |
| 1976 | - zCI = P("ci"); | |
| 1977 | - cirid = name_to_typed_rid(zCI ? zCI : "tip", "ci"); | |
| 1976 | + zCI = PD("ci", "tip"); | |
| 1977 | + cirid = name_to_typed_rid(zCI, "ci"); | |
| 1978 | 1978 | if( cirid<=0 ) return 0; |
| 1979 | 1979 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1980 | 1980 | if( pManifest==0 ) return 0; |
| 1981 | 1981 | manifest_file_rewind(pManifest); |
| 1982 | 1982 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1983 | 1983 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1984 | - int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); | |
| 1985 | - manifest_destroy(pManifest); | |
| 1986 | - if( pUrl ){ | |
| 1987 | - assert( zNameParam==0 ); | |
| 1988 | - url_add_parameter(pUrl, "fn", zFilename); | |
| 1989 | - if( zCI ) url_add_parameter(pUrl, "ci", zCI); | |
| 1990 | - } | |
| 1991 | - return rid; | |
| 1984 | + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); | |
| 1985 | + break; | |
| 1992 | 1986 | } |
| 1993 | 1987 | } |
| 1994 | 1988 | manifest_destroy(pManifest); |
| 1995 | - return 0; | |
| 1989 | + return rid; | |
| 1996 | 1990 | } |
| 1997 | 1991 | |
| 1998 | 1992 | /* |
| 1999 | 1993 | ** The "z" argument is a string that contains the text of a source code |
| 2000 | 1994 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2096,21 +2090,31 @@ | ||
| 2096 | 2090 | ** ln=N - highlight line number N |
| 2097 | 2091 | ** ln=M-N - highlight lines M through N inclusive |
| 2098 | 2092 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2099 | 2093 | ** verbose - show more detail in the description |
| 2100 | 2094 | ** download - redirect to the download (artifact page only) |
| 2101 | -** name=NAME - Provide filename or hash as a query parameter | |
| 2095 | +** name=NAME - filename or hash as a query parameter | |
| 2102 | 2096 | ** filename=NAME - alternative spelling for "name=" |
| 2103 | 2097 | ** fn=NAME - alternative spelling for "name=" |
| 2104 | -** ci=VERSION - The specific check-in to use for "filename=". | |
| 2098 | +** ci=VERSION - The specific check-in to use with "name=" to | |
| 2099 | +** identify the file. | |
| 2105 | 2100 | ** |
| 2106 | 2101 | ** The /artifact page show the complete content of a file |
| 2107 | -** identified by HASH as preformatted text. The | |
| 2108 | -** /whatis page shows only a description of the file. The /file | |
| 2109 | -** page shows the most recent version of the file or directory | |
| 2110 | -** called NAME, or a list of the top-level directory if NAME is | |
| 2111 | -** omitted. | |
| 2102 | +** identified by HASH. The /whatis page shows only a description | |
| 2103 | +** of how the artifact is used. The /file page shows the most recent | |
| 2104 | +** version of the file or directory called NAME, or a list of the | |
| 2105 | +** top-level directory if NAME is omitted. | |
| 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=. | |
| 2112 | +** | |
| 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. | |
| 2112 | 2116 | */ |
| 2113 | 2117 | void artifact_page(void){ |
| 2114 | 2118 | int rid = 0; |
| 2115 | 2119 | Blob content; |
| 2116 | 2120 | const char *zMime; |
| @@ -2125,101 +2129,88 @@ | ||
| 2125 | 2129 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2126 | 2130 | const char *zLn = P("ln"); |
| 2127 | 2131 | const char *zName = P("name"); |
| 2128 | 2132 | const char *zCI = P("ci"); |
| 2129 | 2133 | HQuery url; |
| 2130 | - Blob dirname; | |
| 2131 | 2134 | char *zCIUuid = 0; |
| 2132 | 2135 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2136 | + int isBranchCI = 0; /* ci= refers to a branch name */ | |
| 2133 | 2137 | char *zHeader = 0; |
| 2134 | 2138 | |
| 2135 | 2139 | login_check_credentials(); |
| 2136 | 2140 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2137 | - url_initialize(&url, g.zPath); | |
| 2141 | + | |
| 2142 | + /* Capture and normalize the name= and ci= query parameters */ | |
| 2138 | 2143 | if( zName==0 ){ |
| 2139 | 2144 | zName = P("filename"); |
| 2140 | - if( zName==0 ) zName = P("fn"); | |
| 2145 | + if( zName==0 ){ | |
| 2146 | + zName = P("fn"); | |
| 2147 | + } | |
| 2141 | 2148 | } |
| 2142 | 2149 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 2143 | - if( zCI && zName ){ | |
| 2144 | - blob_zero(&dirname); | |
| 2145 | - hyperlinked_path(zName, &dirname, zCI, "dir", ""); | |
| 2146 | - blob_reset(&dirname); | |
| 2147 | - | |
| 2148 | - if( name_to_uuid2(zCI, "ci", &zCIUuid) ){ | |
| 2149 | - isSymbolicCI = (sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI)) != 0); | |
| 2150 | - } | |
| 2151 | - } | |
| 2152 | - if( isFile && zName ) { | |
| 2153 | - rid = artifact_from_ci_and_filename(0, "name"); | |
| 2154 | - }else{ | |
| 2155 | - rid = artifact_from_ci_and_filename(&url, 0); | |
| 2156 | - } | |
| 2157 | - if( rid==0 ){ | |
| 2158 | - url_add_parameter(&url, "name", zName); | |
| 2159 | - if( isFile ){ | |
| 2160 | - int isUnknownAtCI = 0; | |
| 2161 | - | |
| 2162 | - /* Do a top-level directory listing in /file mode if no argument | |
| 2163 | - ** specified */ | |
| 2164 | - if( zName==0 || zName[0]==0 ){ | |
| 2165 | - if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2166 | - page_tree(); | |
| 2167 | - return; | |
| 2168 | - } | |
| 2169 | - /* Look for a single file with the given name */ | |
| 2170 | - rid = db_int(0, | |
| 2171 | - "SELECT fid FROM filename, mlink, event" | |
| 2172 | - " WHERE name=%Q" | |
| 2173 | - " AND mlink.fnid=filename.fnid" | |
| 2174 | - " AND event.objid=mlink.mid" | |
| 2175 | - " ORDER BY event.mtime DESC LIMIT 1", | |
| 2176 | - zName | |
| 2177 | - ); | |
| 2178 | - /* If found only by the name, then the file is unknown in the check-in. | |
| 2179 | - ** Possibly, the file was renamed/deleted. | |
| 2180 | - */ | |
| 2181 | - if( rid && zCIUuid ){ | |
| 2182 | - rid = 0; | |
| 2183 | - isUnknownAtCI = 1; | |
| 2184 | - } | |
| 2185 | - /* If no file called NAME exists, instead look for a directory | |
| 2186 | - ** with that name, and do a directory listing */ | |
| 2187 | - if( rid==0 ){ | |
| 2188 | - int nName = (int)strlen(zName); | |
| 2189 | - if( nName && zName[nName-1]=='/' ) nName--; | |
| 2190 | - if( db_exists( | |
| 2191 | - "SELECT 1 FROM filename" | |
| 2192 | - " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", | |
| 2193 | - nName, zName, nName+1, nName, zName | |
| 2194 | - ) ){ | |
| 2195 | - if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2196 | - page_tree(); | |
| 2197 | - return; | |
| 2198 | - } | |
| 2199 | - } | |
| 2200 | - /* If no file or directory called NAME: issue an error */ | |
| 2201 | - if( rid==0 ){ | |
| 2202 | - if( isUnknownAtCI ){ | |
| 2203 | - if( isSymbolicCI ){ | |
| 2204 | - zHeader = mprintf("No such file at %s", zCI); | |
| 2205 | - }else{ | |
| 2206 | - zHeader = mprintf("No such file at [%S]", zCIUuid); | |
| 2207 | - } | |
| 2208 | - style_header("%s", zHeader); | |
| 2209 | - fossil_free(zHeader); | |
| 2210 | - @ File %z(href("%R/finfo?name=%T",zName))%h(zName)</a> is not known | |
| 2211 | - @ at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]. | |
| 2212 | - }else{ | |
| 2213 | - style_header("No such file"); | |
| 2214 | - @ File '%h(zName)' is not known in this repository. | |
| 2215 | - } | |
| 2216 | - style_footer(); | |
| 2217 | - return; | |
| 2218 | - } | |
| 2219 | - }else{ | |
| 2220 | - rid = name_to_rid_www("name"); | |
| 2150 | + if( zCI | |
| 2151 | + && name_to_uuid2(zCI, "ci", &zCIUuid) | |
| 2152 | + && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0 | |
| 2153 | + ){ | |
| 2154 | + isSymbolicCI = 1; | |
| 2155 | + isBranchCI = db_exists( | |
| 2156 | + "SELECT 1 FROM tagxref, blob" | |
| 2157 | + " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid" | |
| 2158 | + " AND tagxref.value=%Q AND tagxref.tagtype>0" | |
| 2159 | + " AND tagxref.tagid=%d", | |
| 2160 | + zCIUuid, zCI, TAG_BRANCH | |
| 2161 | + ); | |
| 2162 | + } | |
| 2163 | + | |
| 2164 | + /* The name= query parameter (or at least one of its alternative | |
| 2165 | + ** spellings) is required. Except for /file, show a top-level | |
| 2166 | + ** directory listing if name= is omitted. | |
| 2167 | + */ | |
| 2168 | + if( zName==0 ){ | |
| 2169 | + if( isFile ){ | |
| 2170 | + if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); | |
| 2171 | + page_tree(); | |
| 2172 | + return; | |
| 2173 | + } | |
| 2174 | + style_header("Missing name= query parameter"); | |
| 2175 | + @ The name= query parameter is missing | |
| 2176 | + style_footer(); | |
| 2177 | + return; | |
| 2178 | + } | |
| 2179 | + | |
| 2180 | + url_initialize(&url, g.zPath); | |
| 2181 | + url_add_parameter(&url, "name", zName); | |
| 2182 | + url_add_parameter(&url, "ci", zCI); | |
| 2183 | + | |
| 2184 | + if( zCI==0 && !isFile ){ | |
| 2185 | + /* If there is no ci= query parameter, then prefer to interpret | |
| 2186 | + ** name= as a hash for /artifact and /whatis. But for not for /file. | |
| 2187 | + ** For /file, a name= without a ci= while prefer to use the default | |
| 2188 | + ** "tip" value for ci=. */ | |
| 2189 | + rid = name_to_rid(zName); | |
| 2190 | + } | |
| 2191 | + if( rid==0 ){ | |
| 2192 | + rid = artifact_from_ci_and_filename(0); | |
| 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; | |
| 2221 | 2212 | } |
| 2222 | 2213 | } |
| 2223 | 2214 | |
| 2224 | 2215 | if( rid==0 ){ |
| 2225 | 2216 | style_header("No such artifact"); |
| @@ -2232,21 +2223,27 @@ | ||
| 2232 | 2223 | objdescFlags |= OBJDESC_DETAIL; |
| 2233 | 2224 | } |
| 2234 | 2225 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2235 | 2226 | |
| 2236 | 2227 | if( isFile ){ |
| 2237 | - if( zCI ){ | |
| 2228 | + if( zCI==0 ){ | |
| 2229 | + @ <h2>Latest version of file '%h(zName)':</h2> | |
| 2230 | + }else{ | |
| 2238 | 2231 | const char *zPath; |
| 2239 | 2232 | Blob path; |
| 2240 | 2233 | blob_zero(&path); |
| 2241 | 2234 | hyperlinked_path(zName, &path, zCI, "dir", ""); |
| 2242 | 2235 | zPath = blob_str(&path); |
| 2243 | - @ <h2>File %s(zPath) at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>] | |
| 2244 | - @ </h2> | |
| 2236 | + @ <h2>File %s(zPath) \ | |
| 2237 | + if( isBranchCI ){ | |
| 2238 | + @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> | |
| 2239 | + }else if( isSymbolicCI ){ | |
| 2240 | + @ as of check-in %z(href("/info/%!S",zCIUuid))%s(zCI)</a></h2> | |
| 2241 | + }else{ | |
| 2242 | + @ as of check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2> | |
| 2243 | + } | |
| 2245 | 2244 | blob_reset(&path); |
| 2246 | - }else{ | |
| 2247 | - @ <h2>Latest version of file '%h(zName)':</h2> | |
| 2248 | 2245 | } |
| 2249 | 2246 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2250 | 2247 | }else{ |
| 2251 | 2248 | @ <h2>Artifact |
| 2252 | 2249 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2253 | 2250 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1661,12 +1661,12 @@ | |
| 1661 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1662 | cookie_link_parameter("diff","diff","2"); |
| 1663 | diffType = atoi(PD("diff","2")); |
| 1664 | cookie_render(); |
| 1665 | if( P("from") && P("to") ){ |
| 1666 | v1 = artifact_from_ci_and_filename(0, "from"); |
| 1667 | v2 = artifact_from_ci_and_filename(0, "to"); |
| 1668 | }else{ |
| 1669 | Stmt q; |
| 1670 | v1 = name_to_rid_www("v1"); |
| 1671 | v2 = name_to_rid_www("v2"); |
| 1672 | |
| @@ -1763,12 +1763,12 @@ | |
| 1763 | */ |
| 1764 | void rawartifact_page(void){ |
| 1765 | int rid = 0; |
| 1766 | char *zUuid; |
| 1767 | |
| 1768 | if( P("ci") && P("filename") ){ |
| 1769 | rid = artifact_from_ci_and_filename(0, 0); |
| 1770 | } |
| 1771 | if( rid==0 ){ |
| 1772 | rid = name_to_rid_www("name"); |
| 1773 | } |
| 1774 | login_check_credentials(); |
| @@ -1942,59 +1942,53 @@ | |
| 1942 | |
| 1943 | /* |
| 1944 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1945 | ** use them to extract the record ID of an artifact for the file. |
| 1946 | ** |
| 1947 | ** Also look for "fn" as an alias for "filename". If either "filename" |
| 1948 | ** or "fn" is present but "ci" is missing, use "tip" as a default value |
| 1949 | ** for "ci". |
| 1950 | ** |
| 1951 | ** If zNameParam is not NULL, this use that parameter as the filename |
| 1952 | ** rather than "fn" or "filename". |
| 1953 | ** |
| 1954 | ** If pUrl is not NULL, then record the "ci" and "filename" values in |
| 1955 | ** pUrl. |
| 1956 | ** |
| 1957 | ** At least one of pUrl or zNameParam must be NULL. |
| 1958 | */ |
| 1959 | int artifact_from_ci_and_filename(HQuery *pUrl, const char *zNameParam){ |
| 1960 | const char *zFilename; |
| 1961 | const char *zCI; |
| 1962 | int cirid; |
| 1963 | Manifest *pManifest; |
| 1964 | ManifestFile *pFile; |
| 1965 | |
| 1966 | if( zNameParam ){ |
| 1967 | zFilename = P(zNameParam); |
| 1968 | }else{ |
| 1969 | zFilename = P("filename"); |
| 1970 | if( zFilename==0 ){ |
| 1971 | zFilename = P("fn"); |
| 1972 | } |
| 1973 | } |
| 1974 | if( zFilename==0 ) return 0; |
| 1975 | |
| 1976 | zCI = P("ci"); |
| 1977 | cirid = name_to_typed_rid(zCI ? zCI : "tip", "ci"); |
| 1978 | if( cirid<=0 ) return 0; |
| 1979 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1980 | if( pManifest==0 ) return 0; |
| 1981 | manifest_file_rewind(pManifest); |
| 1982 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1983 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1984 | int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 1985 | manifest_destroy(pManifest); |
| 1986 | if( pUrl ){ |
| 1987 | assert( zNameParam==0 ); |
| 1988 | url_add_parameter(pUrl, "fn", zFilename); |
| 1989 | if( zCI ) url_add_parameter(pUrl, "ci", zCI); |
| 1990 | } |
| 1991 | return rid; |
| 1992 | } |
| 1993 | } |
| 1994 | manifest_destroy(pManifest); |
| 1995 | return 0; |
| 1996 | } |
| 1997 | |
| 1998 | /* |
| 1999 | ** The "z" argument is a string that contains the text of a source code |
| 2000 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2096,21 +2090,31 @@ | |
| 2096 | ** ln=N - highlight line number N |
| 2097 | ** ln=M-N - highlight lines M through N inclusive |
| 2098 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2099 | ** verbose - show more detail in the description |
| 2100 | ** download - redirect to the download (artifact page only) |
| 2101 | ** name=NAME - Provide filename or hash as a query parameter |
| 2102 | ** filename=NAME - alternative spelling for "name=" |
| 2103 | ** fn=NAME - alternative spelling for "name=" |
| 2104 | ** ci=VERSION - The specific check-in to use for "filename=". |
| 2105 | ** |
| 2106 | ** The /artifact page show the complete content of a file |
| 2107 | ** identified by HASH as preformatted text. The |
| 2108 | ** /whatis page shows only a description of the file. The /file |
| 2109 | ** page shows the most recent version of the file or directory |
| 2110 | ** called NAME, or a list of the top-level directory if NAME is |
| 2111 | ** omitted. |
| 2112 | */ |
| 2113 | void artifact_page(void){ |
| 2114 | int rid = 0; |
| 2115 | Blob content; |
| 2116 | const char *zMime; |
| @@ -2125,101 +2129,88 @@ | |
| 2125 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2126 | const char *zLn = P("ln"); |
| 2127 | const char *zName = P("name"); |
| 2128 | const char *zCI = P("ci"); |
| 2129 | HQuery url; |
| 2130 | Blob dirname; |
| 2131 | char *zCIUuid = 0; |
| 2132 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2133 | char *zHeader = 0; |
| 2134 | |
| 2135 | login_check_credentials(); |
| 2136 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2137 | url_initialize(&url, g.zPath); |
| 2138 | if( zName==0 ){ |
| 2139 | zName = P("filename"); |
| 2140 | if( zName==0 ) zName = P("fn"); |
| 2141 | } |
| 2142 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 2143 | if( zCI && zName ){ |
| 2144 | blob_zero(&dirname); |
| 2145 | hyperlinked_path(zName, &dirname, zCI, "dir", ""); |
| 2146 | blob_reset(&dirname); |
| 2147 | |
| 2148 | if( name_to_uuid2(zCI, "ci", &zCIUuid) ){ |
| 2149 | isSymbolicCI = (sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI)) != 0); |
| 2150 | } |
| 2151 | } |
| 2152 | if( isFile && zName ) { |
| 2153 | rid = artifact_from_ci_and_filename(0, "name"); |
| 2154 | }else{ |
| 2155 | rid = artifact_from_ci_and_filename(&url, 0); |
| 2156 | } |
| 2157 | if( rid==0 ){ |
| 2158 | url_add_parameter(&url, "name", zName); |
| 2159 | if( isFile ){ |
| 2160 | int isUnknownAtCI = 0; |
| 2161 | |
| 2162 | /* Do a top-level directory listing in /file mode if no argument |
| 2163 | ** specified */ |
| 2164 | if( zName==0 || zName[0]==0 ){ |
| 2165 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2166 | page_tree(); |
| 2167 | return; |
| 2168 | } |
| 2169 | /* Look for a single file with the given name */ |
| 2170 | rid = db_int(0, |
| 2171 | "SELECT fid FROM filename, mlink, event" |
| 2172 | " WHERE name=%Q" |
| 2173 | " AND mlink.fnid=filename.fnid" |
| 2174 | " AND event.objid=mlink.mid" |
| 2175 | " ORDER BY event.mtime DESC LIMIT 1", |
| 2176 | zName |
| 2177 | ); |
| 2178 | /* If found only by the name, then the file is unknown in the check-in. |
| 2179 | ** Possibly, the file was renamed/deleted. |
| 2180 | */ |
| 2181 | if( rid && zCIUuid ){ |
| 2182 | rid = 0; |
| 2183 | isUnknownAtCI = 1; |
| 2184 | } |
| 2185 | /* If no file called NAME exists, instead look for a directory |
| 2186 | ** with that name, and do a directory listing */ |
| 2187 | if( rid==0 ){ |
| 2188 | int nName = (int)strlen(zName); |
| 2189 | if( nName && zName[nName-1]=='/' ) nName--; |
| 2190 | if( db_exists( |
| 2191 | "SELECT 1 FROM filename" |
| 2192 | " WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';", |
| 2193 | nName, zName, nName+1, nName, zName |
| 2194 | ) ){ |
| 2195 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2196 | page_tree(); |
| 2197 | return; |
| 2198 | } |
| 2199 | } |
| 2200 | /* If no file or directory called NAME: issue an error */ |
| 2201 | if( rid==0 ){ |
| 2202 | if( isUnknownAtCI ){ |
| 2203 | if( isSymbolicCI ){ |
| 2204 | zHeader = mprintf("No such file at %s", zCI); |
| 2205 | }else{ |
| 2206 | zHeader = mprintf("No such file at [%S]", zCIUuid); |
| 2207 | } |
| 2208 | style_header("%s", zHeader); |
| 2209 | fossil_free(zHeader); |
| 2210 | @ File %z(href("%R/finfo?name=%T",zName))%h(zName)</a> is not known |
| 2211 | @ at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]. |
| 2212 | }else{ |
| 2213 | style_header("No such file"); |
| 2214 | @ File '%h(zName)' is not known in this repository. |
| 2215 | } |
| 2216 | style_footer(); |
| 2217 | return; |
| 2218 | } |
| 2219 | }else{ |
| 2220 | rid = name_to_rid_www("name"); |
| 2221 | } |
| 2222 | } |
| 2223 | |
| 2224 | if( rid==0 ){ |
| 2225 | style_header("No such artifact"); |
| @@ -2232,21 +2223,27 @@ | |
| 2232 | objdescFlags |= OBJDESC_DETAIL; |
| 2233 | } |
| 2234 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2235 | |
| 2236 | if( isFile ){ |
| 2237 | if( zCI ){ |
| 2238 | const char *zPath; |
| 2239 | Blob path; |
| 2240 | blob_zero(&path); |
| 2241 | hyperlinked_path(zName, &path, zCI, "dir", ""); |
| 2242 | zPath = blob_str(&path); |
| 2243 | @ <h2>File %s(zPath) at check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>] |
| 2244 | @ </h2> |
| 2245 | blob_reset(&path); |
| 2246 | }else{ |
| 2247 | @ <h2>Latest version of file '%h(zName)':</h2> |
| 2248 | } |
| 2249 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2250 | }else{ |
| 2251 | @ <h2>Artifact |
| 2252 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2253 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1661,12 +1661,12 @@ | |
| 1661 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1662 | cookie_link_parameter("diff","diff","2"); |
| 1663 | diffType = atoi(PD("diff","2")); |
| 1664 | cookie_render(); |
| 1665 | if( P("from") && P("to") ){ |
| 1666 | v1 = artifact_from_ci_and_filename("from"); |
| 1667 | v2 = artifact_from_ci_and_filename("to"); |
| 1668 | }else{ |
| 1669 | Stmt q; |
| 1670 | v1 = name_to_rid_www("v1"); |
| 1671 | v2 = name_to_rid_www("v2"); |
| 1672 | |
| @@ -1763,12 +1763,12 @@ | |
| 1763 | */ |
| 1764 | void rawartifact_page(void){ |
| 1765 | int rid = 0; |
| 1766 | char *zUuid; |
| 1767 | |
| 1768 | if( P("ci") ){ |
| 1769 | rid = artifact_from_ci_and_filename(0); |
| 1770 | } |
| 1771 | if( rid==0 ){ |
| 1772 | rid = name_to_rid_www("name"); |
| 1773 | } |
| 1774 | login_check_credentials(); |
| @@ -1942,59 +1942,53 @@ | |
| 1942 | |
| 1943 | /* |
| 1944 | ** Look for "ci" and "filename" query parameters. If found, try to |
| 1945 | ** use them to extract the record ID of an artifact for the file. |
| 1946 | ** |
| 1947 | ** Also look for "fn" and "name" as an aliases for "filename". If any |
| 1948 | ** "filename" or "fn" or "name" are present but "ci" is missing, then |
| 1949 | ** use "tip" as the default value for "ci". |
| 1950 | ** |
| 1951 | ** If zNameParam is not NULL, then use that parameter as the filename |
| 1952 | ** rather than "fn" or "filename" or "name". the zNameParam is used |
| 1953 | ** for the from= and to= query parameters of /fdiff. |
| 1954 | */ |
| 1955 | int artifact_from_ci_and_filename(const char *zNameParam){ |
| 1956 | const char *zFilename; |
| 1957 | const char *zCI; |
| 1958 | int cirid; |
| 1959 | Manifest *pManifest; |
| 1960 | ManifestFile *pFile; |
| 1961 | int rid = 0; |
| 1962 | |
| 1963 | if( zNameParam ){ |
| 1964 | zFilename = P(zNameParam); |
| 1965 | }else{ |
| 1966 | zFilename = P("filename"); |
| 1967 | if( zFilename==0 ){ |
| 1968 | zFilename = P("fn"); |
| 1969 | } |
| 1970 | if( zFilename==0 ){ |
| 1971 | zFilename = P("name"); |
| 1972 | } |
| 1973 | } |
| 1974 | if( zFilename==0 ) return 0; |
| 1975 | |
| 1976 | zCI = PD("ci", "tip"); |
| 1977 | cirid = name_to_typed_rid(zCI, "ci"); |
| 1978 | if( cirid<=0 ) return 0; |
| 1979 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); |
| 1980 | if( pManifest==0 ) return 0; |
| 1981 | manifest_file_rewind(pManifest); |
| 1982 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1983 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 1984 | rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 1985 | break; |
| 1986 | } |
| 1987 | } |
| 1988 | manifest_destroy(pManifest); |
| 1989 | return rid; |
| 1990 | } |
| 1991 | |
| 1992 | /* |
| 1993 | ** The "z" argument is a string that contains the text of a source code |
| 1994 | ** file. This routine appends that text to the HTTP reply with line numbering. |
| @@ -2096,21 +2090,31 @@ | |
| 2090 | ** ln=N - highlight line number N |
| 2091 | ** ln=M-N - highlight lines M through N inclusive |
| 2092 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2093 | ** verbose - show more detail in the description |
| 2094 | ** download - redirect to the download (artifact page only) |
| 2095 | ** name=NAME - filename or hash as a query parameter |
| 2096 | ** filename=NAME - alternative spelling for "name=" |
| 2097 | ** fn=NAME - alternative spelling for "name=" |
| 2098 | ** ci=VERSION - The specific check-in to use with "name=" to |
| 2099 | ** identify the file. |
| 2100 | ** |
| 2101 | ** The /artifact page show the complete content of a file |
| 2102 | ** identified by HASH. The /whatis page shows only a description |
| 2103 | ** of how the artifact is used. The /file page shows the most recent |
| 2104 | ** version of the file or directory called NAME, or a list of the |
| 2105 | ** top-level directory if NAME is omitted. |
| 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=. |
| 2112 | ** |
| 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. |
| 2116 | */ |
| 2117 | void artifact_page(void){ |
| 2118 | int rid = 0; |
| 2119 | Blob content; |
| 2120 | const char *zMime; |
| @@ -2125,101 +2129,88 @@ | |
| 2129 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2130 | const char *zLn = P("ln"); |
| 2131 | const char *zName = P("name"); |
| 2132 | const char *zCI = P("ci"); |
| 2133 | HQuery url; |
| 2134 | char *zCIUuid = 0; |
| 2135 | int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */ |
| 2136 | int isBranchCI = 0; /* ci= refers to a branch name */ |
| 2137 | char *zHeader = 0; |
| 2138 | |
| 2139 | login_check_credentials(); |
| 2140 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2141 | |
| 2142 | /* Capture and normalize the name= and ci= query parameters */ |
| 2143 | if( zName==0 ){ |
| 2144 | zName = P("filename"); |
| 2145 | if( zName==0 ){ |
| 2146 | zName = P("fn"); |
| 2147 | } |
| 2148 | } |
| 2149 | if( zCI && strlen(zCI)==0 ){ zCI = 0; } |
| 2150 | if( zCI |
| 2151 | && name_to_uuid2(zCI, "ci", &zCIUuid) |
| 2152 | && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0 |
| 2153 | ){ |
| 2154 | isSymbolicCI = 1; |
| 2155 | isBranchCI = db_exists( |
| 2156 | "SELECT 1 FROM tagxref, blob" |
| 2157 | " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid" |
| 2158 | " AND tagxref.value=%Q AND tagxref.tagtype>0" |
| 2159 | " AND tagxref.tagid=%d", |
| 2160 | zCIUuid, zCI, TAG_BRANCH |
| 2161 | ); |
| 2162 | } |
| 2163 | |
| 2164 | /* The name= query parameter (or at least one of its alternative |
| 2165 | ** spellings) is required. Except for /file, show a top-level |
| 2166 | ** directory listing if name= is omitted. |
| 2167 | */ |
| 2168 | if( zName==0 ){ |
| 2169 | if( isFile ){ |
| 2170 | if( P("ci")==0 ) cgi_set_query_parameter("ci","tip"); |
| 2171 | page_tree(); |
| 2172 | return; |
| 2173 | } |
| 2174 | style_header("Missing name= query parameter"); |
| 2175 | @ The name= query parameter is missing |
| 2176 | style_footer(); |
| 2177 | return; |
| 2178 | } |
| 2179 | |
| 2180 | url_initialize(&url, g.zPath); |
| 2181 | url_add_parameter(&url, "name", zName); |
| 2182 | url_add_parameter(&url, "ci", zCI); |
| 2183 | |
| 2184 | if( zCI==0 && !isFile ){ |
| 2185 | /* If there is no ci= query parameter, then prefer to interpret |
| 2186 | ** name= as a hash for /artifact and /whatis. But for not for /file. |
| 2187 | ** For /file, a name= without a ci= while prefer to use the default |
| 2188 | ** "tip" value for ci=. */ |
| 2189 | rid = name_to_rid(zName); |
| 2190 | } |
| 2191 | if( rid==0 ){ |
| 2192 | rid = artifact_from_ci_and_filename(0); |
| 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"); |
| @@ -2232,21 +2223,27 @@ | |
| 2223 | objdescFlags |= OBJDESC_DETAIL; |
| 2224 | } |
| 2225 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2226 | |
| 2227 | if( isFile ){ |
| 2228 | if( zCI==0 ){ |
| 2229 | @ <h2>Latest version of file '%h(zName)':</h2> |
| 2230 | }else{ |
| 2231 | const char *zPath; |
| 2232 | Blob path; |
| 2233 | blob_zero(&path); |
| 2234 | hyperlinked_path(zName, &path, zCI, "dir", ""); |
| 2235 | zPath = blob_str(&path); |
| 2236 | @ <h2>File %s(zPath) \ |
| 2237 | if( isBranchCI ){ |
| 2238 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2239 | }else if( isSymbolicCI ){ |
| 2240 | @ as of check-in %z(href("/info/%!S",zCIUuid))%s(zCI)</a></h2> |
| 2241 | }else{ |
| 2242 | @ as of check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2> |
| 2243 | } |
| 2244 | blob_reset(&path); |
| 2245 | } |
| 2246 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2247 | }else{ |
| 2248 | @ <h2>Artifact |
| 2249 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| 2250 |