Fossil SCM
Enhance the repository list page so that it shows the name of project for each repository, and so that the "Last Modified" time is based on the most recent event in the repository, not the repository file mtime.
Commit
cdea59dc5c832837e9012c6a29aef8b36f0b2dff4f4bc44701f448e4f2ec4515
Parent
45a713495689ca2…
1 file changed
+73
-13
+73
-13
| --- src/repolist.c | ||
| +++ src/repolist.c | ||
| @@ -18,16 +18,61 @@ | ||
| 18 | 18 | ** This module contains code to implement the repository list page when |
| 19 | 19 | ** "fossil server" or "fossil ui" is serving a directory full of repositories. |
| 20 | 20 | */ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "repolist.h" |
| 23 | + | |
| 24 | +#if INTERFACE | |
| 25 | +/* | |
| 26 | +** Return value from the remote_repo_info() command. zRepoName is the | |
| 27 | +** input. All other fields are outputs. | |
| 28 | +*/ | |
| 29 | +struct RepoInfo { | |
| 30 | + char *zRepoName; /* Name of the repository file */ | |
| 31 | + int isValid; /* True if zRepoName is a valid Fossil repository */ | |
| 32 | + char *zProjName; /* Project Name. Memory from fossil_malloc() */ | |
| 33 | + double rMTime; /* Last update. Julian day number */ | |
| 34 | +}; | |
| 35 | +#endif | |
| 36 | + | |
| 37 | +/* | |
| 38 | +** Discover information about the repository given by | |
| 39 | +** pRepo->zRepoName. | |
| 40 | +*/ | |
| 41 | +void remote_repo_info(RepoInfo *pRepo){ | |
| 42 | + sqlite3 *db; | |
| 43 | + sqlite3_stmt *pStmt; | |
| 44 | + int rc; | |
| 45 | + | |
| 46 | + pRepo->isValid = 0; | |
| 47 | + pRepo->zProjName = 0; | |
| 48 | + pRepo->rMTime = 0.0; | |
| 49 | + | |
| 50 | + rc = sqlite3_open(pRepo->zRepoName, &db); | |
| 51 | + if( rc ) return; | |
| 52 | + rc = sqlite3_prepare_v2(db, "SELECT value FROM config" | |
| 53 | + " WHERE name='project-name'", | |
| 54 | + -1, &pStmt, 0); | |
| 55 | + if( rc ) return; | |
| 56 | + if( sqlite3_step(pStmt)==SQLITE_ROW ){ | |
| 57 | + pRepo->zProjName = fossil_strdup((char*)sqlite3_column_text(pStmt,0)); | |
| 58 | + } | |
| 59 | + sqlite3_finalize(pStmt); | |
| 60 | + rc = sqlite3_prepare_v2(db, "SELECT max(mtime) FROM event", -1, &pStmt, 0); | |
| 61 | + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ | |
| 62 | + pRepo->rMTime = sqlite3_column_double(pStmt,0); | |
| 63 | + } | |
| 64 | + sqlite3_finalize(pStmt); | |
| 65 | + sqlite3_close(db); | |
| 66 | + pRepo->isValid = 1; | |
| 67 | +} | |
| 23 | 68 | |
| 24 | 69 | /* |
| 25 | 70 | ** Generate a web-page that lists all repositories located under the |
| 26 | 71 | ** g.zRepositoryName directory and return non-zero. |
| 27 | 72 | ** |
| 28 | -** For the special case when g.zRepositoryName a non-chroot-jail "/", | |
| 73 | +** For the special case when g.zRepositoryName is a non-chroot-jail "/", | |
| 29 | 74 | ** compose the list using the "repo:" entries in the global_config |
| 30 | 75 | ** table of the configuration database. These entries comprise all |
| 31 | 76 | ** of the repositories known to the "all" command. The special case |
| 32 | 77 | ** processing is disallowed for chroot jails because g.zRepositoryName |
| 33 | 78 | ** is always "/" inside a chroot jail and so it cannot be used as a flag |
| @@ -80,25 +125,29 @@ | ||
| 80 | 125 | @ </head> |
| 81 | 126 | @ <body> |
| 82 | 127 | n = db_int(0, "SELECT count(*) FROM sfile"); |
| 83 | 128 | if( n>0 ){ |
| 84 | 129 | Stmt q; |
| 85 | - sqlite3_int64 iNow, iMTime; | |
| 130 | + double rNow; | |
| 86 | 131 | @ <h1 align="center">Fossil Repositories</h1> |
| 87 | 132 | @ <table border="0" class="sortable" data-init-sort="1" \ |
| 88 | - @ data-column-types="tnk"><thead> | |
| 89 | - @ <tr><th>Filename<th width="20"><th>Last Modified</tr> | |
| 133 | + @ data-column-types="tntnk"><thead> | |
| 134 | + @ <tr><th>Filename<th width="20">\ | |
| 135 | + @ <th>Project Name<th width="20">\ | |
| 136 | + @ <th>Last Modified</tr> | |
| 90 | 137 | @ </thead><tbody> |
| 91 | 138 | db_prepare(&q, "SELECT pathname" |
| 92 | 139 | " FROM sfile ORDER BY pathname COLLATE nocase;"); |
| 93 | - iNow = db_int64(0, "SELECT strftime('%%s','now')"); | |
| 140 | + rNow = db_double(0, "SELECT julianday('now')"); | |
| 94 | 141 | while( db_step(&q)==SQLITE_ROW ){ |
| 95 | 142 | const char *zName = db_column_text(&q, 0); |
| 96 | 143 | int nName = (int)strlen(zName); |
| 97 | 144 | char *zUrl; |
| 98 | 145 | char *zAge; |
| 99 | 146 | char *zFull; |
| 147 | + RepoInfo x; | |
| 148 | + int iAge; | |
| 100 | 149 | if( nName<7 ) continue; |
| 101 | 150 | zUrl = sqlite3_mprintf("%.*s", nName-7, zName); |
| 102 | 151 | if( zName[0]=='/' |
| 103 | 152 | #ifdef _WIN32 |
| 104 | 153 | || sqlite3_strglob("[a-zA-Z]:/*", zName)==0 |
| @@ -108,31 +157,42 @@ | ||
| 108 | 157 | }else if ( allRepo ){ |
| 109 | 158 | zFull = mprintf("/%s", zName); |
| 110 | 159 | }else{ |
| 111 | 160 | zFull = mprintf("%s/%s", g.zRepositoryName, zName); |
| 112 | 161 | } |
| 113 | - iMTime = file_mtime(zFull, ExtFILE); | |
| 162 | + x.zRepoName = zFull; | |
| 163 | + remote_repo_info(&x); | |
| 114 | 164 | fossil_free(zFull); |
| 115 | - if( iMTime<=0 ){ | |
| 165 | + if( !x.isValid ){ | |
| 116 | 166 | zAge = mprintf("..."); |
| 167 | + iAge = 0; | |
| 117 | 168 | }else{ |
| 118 | - zAge = human_readable_age((iNow - iMTime)/86400.0); | |
| 169 | + iAge = (rNow - x.rMTime)*86400; | |
| 170 | + if( iAge<0 ) x.rMTime = rNow; | |
| 171 | + zAge = human_readable_age(rNow - x.rMTime); | |
| 119 | 172 | } |
| 173 | + @ <tr><td valign="top">\ | |
| 120 | 174 | if( sqlite3_strglob("*.fossil", zName)!=0 ){ |
| 121 | 175 | /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands |
| 122 | 176 | ** do not work for repositories whose names do not end in ".fossil". |
| 123 | 177 | ** So do not hyperlink those cases. */ |
| 124 | - @ <tr><td>%h(zName) | |
| 178 | + @ %h(zName) | |
| 125 | 179 | } else if( sqlite3_strglob("*/.*", zName)==0 ){ |
| 126 | 180 | /* Do not show hidden repos */ |
| 127 | - @ <tr><td>%h(zName) (hidden) | |
| 181 | + @ %h(zName) (hidden) | |
| 128 | 182 | } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){ |
| 129 | - @ <tr><td><a href="%R/%T(zUrl)/home" target="_blank">/%h(zName)</a> | |
| 183 | + @ <a href="%R/%T(zUrl)/home" target="_blank">/%h(zName)</a> | |
| 184 | + }else{ | |
| 185 | + @ <a href="%R/%T(zUrl)/home" target="_blank">%h(zName)</a> | |
| 186 | + } | |
| 187 | + if( x.zProjName ){ | |
| 188 | + @ <td></td><td>%h(x.zProjName)</td> | |
| 189 | + fossil_free(x.zProjName); | |
| 130 | 190 | }else{ |
| 131 | - @ <tr><td><a href="%R/%T(zUrl)/home" target="_blank">%h(zName)</a> | |
| 191 | + @ <td></td><td></td> | |
| 132 | 192 | } |
| 133 | - @ <td></td><td data-sortkey='%010llx(iNow - iMTime)'>%h(zAge)</tr> | |
| 193 | + @ <td></td><td data-sortkey='%08x(iAge)'>%h(zAge)</tr> | |
| 134 | 194 | fossil_free(zAge); |
| 135 | 195 | sqlite3_free(zUrl); |
| 136 | 196 | } |
| 137 | 197 | @ </tbody></table> |
| 138 | 198 | }else{ |
| 139 | 199 |
| --- src/repolist.c | |
| +++ src/repolist.c | |
| @@ -18,16 +18,61 @@ | |
| 18 | ** This module contains code to implement the repository list page when |
| 19 | ** "fossil server" or "fossil ui" is serving a directory full of repositories. |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "repolist.h" |
| 23 | |
| 24 | /* |
| 25 | ** Generate a web-page that lists all repositories located under the |
| 26 | ** g.zRepositoryName directory and return non-zero. |
| 27 | ** |
| 28 | ** For the special case when g.zRepositoryName a non-chroot-jail "/", |
| 29 | ** compose the list using the "repo:" entries in the global_config |
| 30 | ** table of the configuration database. These entries comprise all |
| 31 | ** of the repositories known to the "all" command. The special case |
| 32 | ** processing is disallowed for chroot jails because g.zRepositoryName |
| 33 | ** is always "/" inside a chroot jail and so it cannot be used as a flag |
| @@ -80,25 +125,29 @@ | |
| 80 | @ </head> |
| 81 | @ <body> |
| 82 | n = db_int(0, "SELECT count(*) FROM sfile"); |
| 83 | if( n>0 ){ |
| 84 | Stmt q; |
| 85 | sqlite3_int64 iNow, iMTime; |
| 86 | @ <h1 align="center">Fossil Repositories</h1> |
| 87 | @ <table border="0" class="sortable" data-init-sort="1" \ |
| 88 | @ data-column-types="tnk"><thead> |
| 89 | @ <tr><th>Filename<th width="20"><th>Last Modified</tr> |
| 90 | @ </thead><tbody> |
| 91 | db_prepare(&q, "SELECT pathname" |
| 92 | " FROM sfile ORDER BY pathname COLLATE nocase;"); |
| 93 | iNow = db_int64(0, "SELECT strftime('%%s','now')"); |
| 94 | while( db_step(&q)==SQLITE_ROW ){ |
| 95 | const char *zName = db_column_text(&q, 0); |
| 96 | int nName = (int)strlen(zName); |
| 97 | char *zUrl; |
| 98 | char *zAge; |
| 99 | char *zFull; |
| 100 | if( nName<7 ) continue; |
| 101 | zUrl = sqlite3_mprintf("%.*s", nName-7, zName); |
| 102 | if( zName[0]=='/' |
| 103 | #ifdef _WIN32 |
| 104 | || sqlite3_strglob("[a-zA-Z]:/*", zName)==0 |
| @@ -108,31 +157,42 @@ | |
| 108 | }else if ( allRepo ){ |
| 109 | zFull = mprintf("/%s", zName); |
| 110 | }else{ |
| 111 | zFull = mprintf("%s/%s", g.zRepositoryName, zName); |
| 112 | } |
| 113 | iMTime = file_mtime(zFull, ExtFILE); |
| 114 | fossil_free(zFull); |
| 115 | if( iMTime<=0 ){ |
| 116 | zAge = mprintf("..."); |
| 117 | }else{ |
| 118 | zAge = human_readable_age((iNow - iMTime)/86400.0); |
| 119 | } |
| 120 | if( sqlite3_strglob("*.fossil", zName)!=0 ){ |
| 121 | /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands |
| 122 | ** do not work for repositories whose names do not end in ".fossil". |
| 123 | ** So do not hyperlink those cases. */ |
| 124 | @ <tr><td>%h(zName) |
| 125 | } else if( sqlite3_strglob("*/.*", zName)==0 ){ |
| 126 | /* Do not show hidden repos */ |
| 127 | @ <tr><td>%h(zName) (hidden) |
| 128 | } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){ |
| 129 | @ <tr><td><a href="%R/%T(zUrl)/home" target="_blank">/%h(zName)</a> |
| 130 | }else{ |
| 131 | @ <tr><td><a href="%R/%T(zUrl)/home" target="_blank">%h(zName)</a> |
| 132 | } |
| 133 | @ <td></td><td data-sortkey='%010llx(iNow - iMTime)'>%h(zAge)</tr> |
| 134 | fossil_free(zAge); |
| 135 | sqlite3_free(zUrl); |
| 136 | } |
| 137 | @ </tbody></table> |
| 138 | }else{ |
| 139 |
| --- src/repolist.c | |
| +++ src/repolist.c | |
| @@ -18,16 +18,61 @@ | |
| 18 | ** This module contains code to implement the repository list page when |
| 19 | ** "fossil server" or "fossil ui" is serving a directory full of repositories. |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "repolist.h" |
| 23 | |
| 24 | #if INTERFACE |
| 25 | /* |
| 26 | ** Return value from the remote_repo_info() command. zRepoName is the |
| 27 | ** input. All other fields are outputs. |
| 28 | */ |
| 29 | struct RepoInfo { |
| 30 | char *zRepoName; /* Name of the repository file */ |
| 31 | int isValid; /* True if zRepoName is a valid Fossil repository */ |
| 32 | char *zProjName; /* Project Name. Memory from fossil_malloc() */ |
| 33 | double rMTime; /* Last update. Julian day number */ |
| 34 | }; |
| 35 | #endif |
| 36 | |
| 37 | /* |
| 38 | ** Discover information about the repository given by |
| 39 | ** pRepo->zRepoName. |
| 40 | */ |
| 41 | void remote_repo_info(RepoInfo *pRepo){ |
| 42 | sqlite3 *db; |
| 43 | sqlite3_stmt *pStmt; |
| 44 | int rc; |
| 45 | |
| 46 | pRepo->isValid = 0; |
| 47 | pRepo->zProjName = 0; |
| 48 | pRepo->rMTime = 0.0; |
| 49 | |
| 50 | rc = sqlite3_open(pRepo->zRepoName, &db); |
| 51 | if( rc ) return; |
| 52 | rc = sqlite3_prepare_v2(db, "SELECT value FROM config" |
| 53 | " WHERE name='project-name'", |
| 54 | -1, &pStmt, 0); |
| 55 | if( rc ) return; |
| 56 | if( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 57 | pRepo->zProjName = fossil_strdup((char*)sqlite3_column_text(pStmt,0)); |
| 58 | } |
| 59 | sqlite3_finalize(pStmt); |
| 60 | rc = sqlite3_prepare_v2(db, "SELECT max(mtime) FROM event", -1, &pStmt, 0); |
| 61 | if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 62 | pRepo->rMTime = sqlite3_column_double(pStmt,0); |
| 63 | } |
| 64 | sqlite3_finalize(pStmt); |
| 65 | sqlite3_close(db); |
| 66 | pRepo->isValid = 1; |
| 67 | } |
| 68 | |
| 69 | /* |
| 70 | ** Generate a web-page that lists all repositories located under the |
| 71 | ** g.zRepositoryName directory and return non-zero. |
| 72 | ** |
| 73 | ** For the special case when g.zRepositoryName is a non-chroot-jail "/", |
| 74 | ** compose the list using the "repo:" entries in the global_config |
| 75 | ** table of the configuration database. These entries comprise all |
| 76 | ** of the repositories known to the "all" command. The special case |
| 77 | ** processing is disallowed for chroot jails because g.zRepositoryName |
| 78 | ** is always "/" inside a chroot jail and so it cannot be used as a flag |
| @@ -80,25 +125,29 @@ | |
| 125 | @ </head> |
| 126 | @ <body> |
| 127 | n = db_int(0, "SELECT count(*) FROM sfile"); |
| 128 | if( n>0 ){ |
| 129 | Stmt q; |
| 130 | double rNow; |
| 131 | @ <h1 align="center">Fossil Repositories</h1> |
| 132 | @ <table border="0" class="sortable" data-init-sort="1" \ |
| 133 | @ data-column-types="tntnk"><thead> |
| 134 | @ <tr><th>Filename<th width="20">\ |
| 135 | @ <th>Project Name<th width="20">\ |
| 136 | @ <th>Last Modified</tr> |
| 137 | @ </thead><tbody> |
| 138 | db_prepare(&q, "SELECT pathname" |
| 139 | " FROM sfile ORDER BY pathname COLLATE nocase;"); |
| 140 | rNow = db_double(0, "SELECT julianday('now')"); |
| 141 | while( db_step(&q)==SQLITE_ROW ){ |
| 142 | const char *zName = db_column_text(&q, 0); |
| 143 | int nName = (int)strlen(zName); |
| 144 | char *zUrl; |
| 145 | char *zAge; |
| 146 | char *zFull; |
| 147 | RepoInfo x; |
| 148 | int iAge; |
| 149 | if( nName<7 ) continue; |
| 150 | zUrl = sqlite3_mprintf("%.*s", nName-7, zName); |
| 151 | if( zName[0]=='/' |
| 152 | #ifdef _WIN32 |
| 153 | || sqlite3_strglob("[a-zA-Z]:/*", zName)==0 |
| @@ -108,31 +157,42 @@ | |
| 157 | }else if ( allRepo ){ |
| 158 | zFull = mprintf("/%s", zName); |
| 159 | }else{ |
| 160 | zFull = mprintf("%s/%s", g.zRepositoryName, zName); |
| 161 | } |
| 162 | x.zRepoName = zFull; |
| 163 | remote_repo_info(&x); |
| 164 | fossil_free(zFull); |
| 165 | if( !x.isValid ){ |
| 166 | zAge = mprintf("..."); |
| 167 | iAge = 0; |
| 168 | }else{ |
| 169 | iAge = (rNow - x.rMTime)*86400; |
| 170 | if( iAge<0 ) x.rMTime = rNow; |
| 171 | zAge = human_readable_age(rNow - x.rMTime); |
| 172 | } |
| 173 | @ <tr><td valign="top">\ |
| 174 | if( sqlite3_strglob("*.fossil", zName)!=0 ){ |
| 175 | /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands |
| 176 | ** do not work for repositories whose names do not end in ".fossil". |
| 177 | ** So do not hyperlink those cases. */ |
| 178 | @ %h(zName) |
| 179 | } else if( sqlite3_strglob("*/.*", zName)==0 ){ |
| 180 | /* Do not show hidden repos */ |
| 181 | @ %h(zName) (hidden) |
| 182 | } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){ |
| 183 | @ <a href="%R/%T(zUrl)/home" target="_blank">/%h(zName)</a> |
| 184 | }else{ |
| 185 | @ <a href="%R/%T(zUrl)/home" target="_blank">%h(zName)</a> |
| 186 | } |
| 187 | if( x.zProjName ){ |
| 188 | @ <td></td><td>%h(x.zProjName)</td> |
| 189 | fossil_free(x.zProjName); |
| 190 | }else{ |
| 191 | @ <td></td><td></td> |
| 192 | } |
| 193 | @ <td></td><td data-sortkey='%08x(iAge)'>%h(zAge)</tr> |
| 194 | fossil_free(zAge); |
| 195 | sqlite3_free(zUrl); |
| 196 | } |
| 197 | @ </tbody></table> |
| 198 | }else{ |
| 199 |