Fossil SCM

Show file sizes the the treeview. Other file browser enhancements.

drh 2023-07-23 20:28 trunk merge
Commit 73fe442a258179c427b5f521f1963c773ab86e238a230ce870a5f072034f59c2
2 files changed +98 -64 +8 -1
+98 -64
--- src/browse.c
+++ src/browse.c
@@ -271,54 +271,42 @@
271271
*/
272272
db_multi_exec(
273273
"CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);"
274274
);
275275
if( zCI ){
276
- Stmt ins;
277
- ManifestFile *pFile;
278
- ManifestFile *pPrev = 0;
279
- int nPrev = 0;
280
- int c;
281
-
282
- db_prepare(&ins,
283
- "INSERT OR IGNORE INTO localfiles VALUES(pathelement(:x,0), :u)"
284
- );
285
- manifest_file_rewind(pM);
286
- while( (pFile = manifest_file_next(pM,0))!=0 ){
287
- if( nD>0
288
- && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
289
- || pFile->zName[nD-1]!='/')
290
- ){
291
- continue;
292
- }
293
- if( pPrev
294
- && fossil_strncmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0
295
- && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/')
296
- ){
297
- continue;
298
- }
299
- db_bind_text(&ins, ":x", &pFile->zName[nD]);
300
- db_bind_text(&ins, ":u", pFile->zUuid);
301
- db_step(&ins);
302
- db_reset(&ins);
303
- pPrev = pFile;
304
- for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){}
305
- if( c=='/' ) nPrev++;
306
- }
307
- db_finalize(&ins);
308
- }else if( zD ){
309
- db_multi_exec(
310
- "INSERT OR IGNORE INTO localfiles"
311
- " SELECT pathelement(name,%d), NULL FROM filename"
312
- " WHERE name GLOB '%q/*'",
313
- nD, zD
314
- );
276
+ /* Files in the specific checked given by zCI */
277
+ if( zD ){
278
+ db_multi_exec(
279
+ "INSERT OR IGNORE INTO localfiles"
280
+ " SELECT pathelement(filename,%d), uuid"
281
+ " FROM files_of_checkin(%Q)"
282
+ " WHERE filename GLOB '%q/*'",
283
+ nD, zCI, zD
284
+ );
285
+ }else{
286
+ db_multi_exec(
287
+ "INSERT OR IGNORE INTO localfiles"
288
+ " SELECT pathelement(filename,%d), uuid"
289
+ " FROM files_of_checkin(%Q)",
290
+ nD, zCI
291
+ );
292
+ }
315293
}else{
316
- db_multi_exec(
317
- "INSERT OR IGNORE INTO localfiles"
318
- " SELECT pathelement(name,0), NULL FROM filename"
319
- );
294
+ /* All files across all check-ins */
295
+ if( zD ){
296
+ db_multi_exec(
297
+ "INSERT OR IGNORE INTO localfiles"
298
+ " SELECT pathelement(name,%d), NULL FROM filename"
299
+ " WHERE name GLOB '%q/*'",
300
+ nD, zD
301
+ );
302
+ }else{
303
+ db_multi_exec(
304
+ "INSERT OR IGNORE INTO localfiles"
305
+ " SELECT pathelement(name,0), NULL FROM filename"
306
+ );
307
+ }
320308
}
321309
322310
/* If the re=REGEXP query parameter is present, filter out names that
323311
** do not match the pattern */
324312
if( zRegexp ){
@@ -442,10 +430,12 @@
442430
FileTreeNode *pLastChild; /* Last child on the pChild list */
443431
char *zName; /* Name of this entry. The "tail" */
444432
char *zFullName; /* Full pathname of this entry */
445433
char *zUuid; /* Artifact hash of this file. May be NULL. */
446434
double mtime; /* Modification time for this entry */
435
+ double sortBy; /* Either mtime or size, depending on desired sort order */
436
+ int iSize; /* Size for this entry */
447437
unsigned nFullName; /* Length of zFullName */
448438
unsigned iLevel; /* Levels of parent directories */
449439
};
450440
451441
/*
@@ -471,15 +461,17 @@
471461
*/
472462
static void tree_add_node(
473463
FileTree *pTree, /* Tree into which nodes are added */
474464
const char *zPath, /* The full pathname of file to add */
475465
const char *zUuid, /* Hash of the file. Might be NULL. */
476
- double mtime /* Modification time for this entry */
466
+ double mtime, /* Modification time for this entry */
467
+ int size, /* Size for this entry */
468
+ int sortOrder /* 0: filename, 1: mtime, 2: size */
477469
){
478470
int i;
479471
FileTreeNode *pParent; /* Parent (directory) of the next node to insert */
480
-
472
+//fossil_print("<pre>zPath %s zUuid %s mtime %f size %d</pre>\n",zPath,zUuid,mtime,size);
481473
/* Make pParent point to the most recent ancestor of zPath, or
482474
** NULL if there are no prior entires that are a container for zPath.
483475
*/
484476
pParent = pTree->pLast;
485477
while( pParent!=0 &&
@@ -525,10 +517,16 @@
525517
}else{
526518
if( pTree->pLastTop ) pTree->pLastTop->pSibling = pNew;
527519
pTree->pLastTop = pNew;
528520
}
529521
pNew->mtime = mtime;
522
+ pNew->iSize = size;
523
+ if( sortOrder ){
524
+ pNew->sortBy = sortOrder==1 ? mtime : (double)size;
525
+ }else{
526
+ pNew->sortBy = 0.0;
527
+ }
530528
while( zPath[i]=='/' ){ i++; }
531529
pParent = pNew;
532530
}
533531
while( pParent && pParent->pParent ){
534532
if( pParent->pParent->mtime < pParent->mtime ){
@@ -537,19 +535,22 @@
537535
pParent = pParent->pParent;
538536
}
539537
}
540538
541539
/* Comparison function for two FileTreeNode objects. Sort first by
542
-** mtime (larger numbers first) and then by zName (smaller names first).
540
+** sortBy (larger numbers first) and then by zName (smaller names first).
541
+**
542
+** The sortBy field will be the same as mtime in order to sort by time,
543
+** or the same as iSize to sort by file size.
543544
**
544545
** Return negative if pLeft<pRight.
545546
** Return positive if pLeft>pRight.
546547
** Return zero if pLeft==pRight.
547548
*/
548549
static int compareNodes(FileTreeNode *pLeft, FileTreeNode *pRight){
549
- if( pLeft->mtime>pRight->mtime ) return -1;
550
- if( pLeft->mtime<pRight->mtime ) return +1;
550
+ if( pLeft->sortBy>pRight->sortBy ) return -1;
551
+ if( pLeft->sortBy<pRight->sortBy ) return +1;
551552
return fossil_stricmp(pLeft->zName, pRight->zName);
552553
}
553554
554555
/* Merge together two sorted lists of FileTreeNode objects */
555556
static FileTreeNode *mergeNodes(FileTreeNode *pLeft, FileTreeNode *pRight){
@@ -571,12 +572,12 @@
571572
pEnd->pSibling = pRight;
572573
}
573574
return base.pSibling;
574575
}
575576
576
-/* Sort a list of FileTreeNode objects in mtime order. */
577
-static FileTreeNode *sortNodesByMtime(FileTreeNode *p){
577
+/* Sort a list of FileTreeNode objects in sortmtime order. */
578
+static FileTreeNode *sortNodes(FileTreeNode *p){
578579
FileTreeNode *a[30];
579580
FileTreeNode *pX;
580581
int i;
581582
582583
memset(a, 0, sizeof(a));
@@ -604,16 +605,16 @@
604605
** FileTreeNode.pLastChild
605606
** FileTreeNode.pNext
606607
**
607608
** Use relinkTree to reconnect the pNext pointers.
608609
*/
609
-static FileTreeNode *sortTreeByMtime(FileTreeNode *p){
610
+static FileTreeNode *sortTree(FileTreeNode *p){
610611
FileTreeNode *pX;
611612
for(pX=p; pX; pX=pX->pSibling){
612
- if( pX->pChild ) pX->pChild = sortTreeByMtime(pX->pChild);
613
+ if( pX->pChild ) pX->pChild = sortTree(pX->pChild);
613614
}
614
- return sortNodesByMtime(p);
615
+ return sortNodes(p);
615616
}
616617
617618
/* Reconstruct the FileTree by reconnecting the FileTreeNode.pNext
618619
** fields in sequential order.
619620
*/
@@ -648,11 +649,11 @@
648649
** name=PATH Directory to display. Optional
649650
** ci=LABEL Show only files in this check-in. Optional.
650651
** re=REGEXP Show only files matching REGEXP. Optional.
651652
** expand Begin with the tree fully expanded.
652653
** nofiles Show directories (folders) only. Omit files.
653
-** mtime Order directory elements by decreasing mtime
654
+** sort 0: by filename, 1: by mtime, 2: by size
654655
*/
655656
void page_tree(void){
656657
char *zD = fossil_strdup(P("name"));
657658
int nD = zD ? strlen(zD)+1 : 0;
658659
const char *zCI = P("ci");
@@ -661,10 +662,11 @@
661662
Blob dirname;
662663
Manifest *pM = 0;
663664
double rNow = 0;
664665
char *zNow = 0;
665666
int useMtime = atoi(PD("mtime","0"));
667
+ int sortOrder = atoi(PD("sort",useMtime?"1":"0"));
666668
int linkTrunk = 1; /* include link to "trunk" */
667669
int linkTip = 1; /* include link to "tip" */
668670
const char *zRE; /* the value for the re=REGEXP query parameter */
669671
const char *zObjType; /* "files" by default or "folders" for "nofiles" */
670672
char *zREx = ""; /* Extra parameters for path hyperlinks */
@@ -765,11 +767,18 @@
765767
style_submenu_element("Top-Level", "%s",
766768
url_render(&sURI, "name", 0, 0, 0));
767769
}else if( zRE ){
768770
blob_appendf(&dirname, "matching \"%s\"", zRE);
769771
}
770
- style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
772
+ {
773
+ static const char *const sort_orders[] = {
774
+ "0", "Sort By Filename",
775
+ "1", "Sort By Age",
776
+ "2", "Sort By Size"
777
+ };
778
+ style_submenu_multichoice("sort", 3, sort_orders, 0);
779
+ }
771780
if( zCI ){
772781
style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
773782
if( nD==0 && !showDirOnly ){
774783
style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
775784
}
@@ -788,46 +797,49 @@
788797
*/
789798
if( zCI ){
790799
Stmt q;
791800
compute_fileage(rid, 0);
792801
db_prepare(&q,
793
- "SELECT filename.name, blob.uuid, fileage.mtime\n"
802
+ "SELECT filename.name, blob.uuid, blob.size, fileage.mtime\n"
794803
" FROM fileage, filename, blob\n"
795804
" WHERE filename.fnid=fileage.fnid\n"
796805
" AND blob.rid=fileage.fid\n"
797806
" ORDER BY filename.name COLLATE uintnocase;"
798807
);
799808
while( db_step(&q)==SQLITE_ROW ){
800809
const char *zFile = db_column_text(&q,0);
801810
const char *zUuid = db_column_text(&q,1);
802
- double mtime = db_column_double(&q,2);
811
+ int size = db_column_int(&q,2);
812
+ double mtime = db_column_double(&q,3);
803813
if( nD>0 && (fossil_strncmp(zFile, zD, nD-1)!=0 || zFile[nD-1]!='/') ){
804814
continue;
805815
}
806816
if( pRE && re_match(pRE, (const unsigned char*)zFile, -1)==0 ) continue;
807
- tree_add_node(&sTree, zFile, zUuid, mtime);
817
+ tree_add_node(&sTree, zFile, zUuid, mtime, size, sortOrder);
808818
}
809819
db_finalize(&q);
810820
}else{
811821
Stmt q;
812822
db_prepare(&q,
813823
"SELECT\n"
814824
" (SELECT name FROM filename WHERE filename.fnid=mlink.fnid),\n"
815825
" (SELECT uuid FROM blob WHERE blob.rid=mlink.fid),\n"
826
+ " (SELECT size FROM blob WHERE blob.rid=mlink.fid),\n"
816827
" max(event.mtime)\n"
817828
" FROM mlink JOIN event ON event.objid=mlink.mid\n"
818829
" GROUP BY mlink.fnid\n"
819830
" ORDER BY 1 COLLATE uintnocase;");
820831
while( db_step(&q)==SQLITE_ROW ){
821832
const char *zName = db_column_text(&q, 0);
822833
const char *zUuid = db_column_text(&q,1);
823
- double mtime = db_column_double(&q,2);
834
+ int size = db_column_int(&q,2);
835
+ double mtime = db_column_double(&q,3);
824836
if( nD>0 && (fossil_strncmp(zName, zD, nD-1)!=0 || zName[nD-1]!='/') ){
825837
continue;
826838
}
827839
if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue;
828
- tree_add_node(&sTree, zName, zUuid, mtime);
840
+ tree_add_node(&sTree, zName, zUuid, mtime, size, sortOrder);
829841
}
830842
db_finalize(&q);
831843
}
832844
style_submenu_checkbox("nofiles", "Folders Only", 0, 0);
833845
@@ -853,12 +865,14 @@
853865
}
854866
}else{
855867
int n = db_int(0, "SELECT count(*) FROM plink");
856868
@ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
857869
}
858
- if( useMtime ){
870
+ if( sortOrder==1 ){
859871
@ sorted by modification time</h2>
872
+ }else if( sortOrder==2 ){
873
+ @ sorted by size</h2>
860874
}else{
861875
@ sorted by filename</h2>
862876
}
863877
864878
if( zNow ){
@@ -884,16 +898,17 @@
884898
@ <li class="dir subdir last">
885899
}
886900
@ <div class="filetreeline">
887901
@ %z(href("%s",url_render(&sURI,"name",0,0,0)))%h(zProjectName)</a>
888902
if( zNow ){
889
- @ <div class="filetreeage">%s(zNow)</div>
903
+ @ <div class="filetreeage">Last Change</div>
904
+ @ <div class="filetreesize">Size</div>
890905
}
891906
@ </div>
892907
@ <ul>
893
- if( useMtime ){
894
- p = sortTreeByMtime(sTree.pFirst);
908
+ if( sortOrder ){
909
+ p = sortTree(sTree.pFirst);
895910
memset(&sTree, 0, sizeof(sTree));
896911
relinkTree(&sTree, p);
897912
}
898913
for(p=sTree.pFirst, nDir=0; p; p=p->pNext){
899914
const char *zLastClass = p->pSibling==0 ? " last" : "";
@@ -902,10 +917,11 @@
902917
@ <li class="dir%s(zSubdirClass)%s(zLastClass)"><div class="filetreeline">
903918
@ %z(href("%s",url_render(&sURI,"name",p->zFullName,0,0)))%h(p->zName)</a>
904919
if( p->mtime>0.0 ){
905920
char *zAge = human_readable_age(rNow - p->mtime);
906921
@ <div class="filetreeage">%s(zAge)</div>
922
+ @ <div class="filetreesize"></div>
907923
}
908924
@ </div>
909925
if( startExpanded || (int)(p->nFullName)<=nD ){
910926
@ <ul id="dir%d(nDir)">
911927
}else{
@@ -923,10 +939,11 @@
923939
@ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
924940
@ %z(zLink)%h(p->zName)</a>
925941
if( p->mtime>0 ){
926942
char *zAge = human_readable_age(rNow - p->mtime);
927943
@ <div class="filetreeage">%s(zAge)</div>
944
+ @ <div class="filetreesize">%s(p->iSize ? mprintf("%,d",p->iSize) : "-")</div>
928945
}
929946
@ </div>
930947
}
931948
if( p->pSibling==0 ){
932949
int nClose = p->iLevel - (p->pNext ? p->pNext->iLevel : 0);
@@ -1192,5 +1209,22 @@
11921209
@ </table></div>
11931210
db_finalize(&q1);
11941211
db_finalize(&q2);
11951212
style_finish_page();
11961213
}
1214
+
1215
+/*
1216
+** WEBPAGE: files
1217
+**
1218
+** Show files as a flat table. If the ci=LABEL query parameter is provided,
1219
+** then show all the files in the specified check-in. Without the ci= query
1220
+** parameter show all files across all check-ins.
1221
+**
1222
+** Query parameters:
1223
+**
1224
+** name=PATH Directory to display. Optional
1225
+** ci=LABEL Show only files in this check-in. Optional.
1226
+** re=REGEXP Show only files matching REGEXP. Optional.
1227
+*/
1228
+void files_page(void){
1229
+ return;
1230
+}
11971231
--- src/browse.c
+++ src/browse.c
@@ -271,54 +271,42 @@
271 */
272 db_multi_exec(
273 "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);"
274 );
275 if( zCI ){
276 Stmt ins;
277 ManifestFile *pFile;
278 ManifestFile *pPrev = 0;
279 int nPrev = 0;
280 int c;
281
282 db_prepare(&ins,
283 "INSERT OR IGNORE INTO localfiles VALUES(pathelement(:x,0), :u)"
284 );
285 manifest_file_rewind(pM);
286 while( (pFile = manifest_file_next(pM,0))!=0 ){
287 if( nD>0
288 && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
289 || pFile->zName[nD-1]!='/')
290 ){
291 continue;
292 }
293 if( pPrev
294 && fossil_strncmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0
295 && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/')
296 ){
297 continue;
298 }
299 db_bind_text(&ins, ":x", &pFile->zName[nD]);
300 db_bind_text(&ins, ":u", pFile->zUuid);
301 db_step(&ins);
302 db_reset(&ins);
303 pPrev = pFile;
304 for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){}
305 if( c=='/' ) nPrev++;
306 }
307 db_finalize(&ins);
308 }else if( zD ){
309 db_multi_exec(
310 "INSERT OR IGNORE INTO localfiles"
311 " SELECT pathelement(name,%d), NULL FROM filename"
312 " WHERE name GLOB '%q/*'",
313 nD, zD
314 );
315 }else{
316 db_multi_exec(
317 "INSERT OR IGNORE INTO localfiles"
318 " SELECT pathelement(name,0), NULL FROM filename"
319 );
 
 
 
 
 
 
 
 
 
 
320 }
321
322 /* If the re=REGEXP query parameter is present, filter out names that
323 ** do not match the pattern */
324 if( zRegexp ){
@@ -442,10 +430,12 @@
442 FileTreeNode *pLastChild; /* Last child on the pChild list */
443 char *zName; /* Name of this entry. The "tail" */
444 char *zFullName; /* Full pathname of this entry */
445 char *zUuid; /* Artifact hash of this file. May be NULL. */
446 double mtime; /* Modification time for this entry */
 
 
447 unsigned nFullName; /* Length of zFullName */
448 unsigned iLevel; /* Levels of parent directories */
449 };
450
451 /*
@@ -471,15 +461,17 @@
471 */
472 static void tree_add_node(
473 FileTree *pTree, /* Tree into which nodes are added */
474 const char *zPath, /* The full pathname of file to add */
475 const char *zUuid, /* Hash of the file. Might be NULL. */
476 double mtime /* Modification time for this entry */
 
 
477 ){
478 int i;
479 FileTreeNode *pParent; /* Parent (directory) of the next node to insert */
480
481 /* Make pParent point to the most recent ancestor of zPath, or
482 ** NULL if there are no prior entires that are a container for zPath.
483 */
484 pParent = pTree->pLast;
485 while( pParent!=0 &&
@@ -525,10 +517,16 @@
525 }else{
526 if( pTree->pLastTop ) pTree->pLastTop->pSibling = pNew;
527 pTree->pLastTop = pNew;
528 }
529 pNew->mtime = mtime;
 
 
 
 
 
 
530 while( zPath[i]=='/' ){ i++; }
531 pParent = pNew;
532 }
533 while( pParent && pParent->pParent ){
534 if( pParent->pParent->mtime < pParent->mtime ){
@@ -537,19 +535,22 @@
537 pParent = pParent->pParent;
538 }
539 }
540
541 /* Comparison function for two FileTreeNode objects. Sort first by
542 ** mtime (larger numbers first) and then by zName (smaller names first).
 
 
 
543 **
544 ** Return negative if pLeft<pRight.
545 ** Return positive if pLeft>pRight.
546 ** Return zero if pLeft==pRight.
547 */
548 static int compareNodes(FileTreeNode *pLeft, FileTreeNode *pRight){
549 if( pLeft->mtime>pRight->mtime ) return -1;
550 if( pLeft->mtime<pRight->mtime ) return +1;
551 return fossil_stricmp(pLeft->zName, pRight->zName);
552 }
553
554 /* Merge together two sorted lists of FileTreeNode objects */
555 static FileTreeNode *mergeNodes(FileTreeNode *pLeft, FileTreeNode *pRight){
@@ -571,12 +572,12 @@
571 pEnd->pSibling = pRight;
572 }
573 return base.pSibling;
574 }
575
576 /* Sort a list of FileTreeNode objects in mtime order. */
577 static FileTreeNode *sortNodesByMtime(FileTreeNode *p){
578 FileTreeNode *a[30];
579 FileTreeNode *pX;
580 int i;
581
582 memset(a, 0, sizeof(a));
@@ -604,16 +605,16 @@
604 ** FileTreeNode.pLastChild
605 ** FileTreeNode.pNext
606 **
607 ** Use relinkTree to reconnect the pNext pointers.
608 */
609 static FileTreeNode *sortTreeByMtime(FileTreeNode *p){
610 FileTreeNode *pX;
611 for(pX=p; pX; pX=pX->pSibling){
612 if( pX->pChild ) pX->pChild = sortTreeByMtime(pX->pChild);
613 }
614 return sortNodesByMtime(p);
615 }
616
617 /* Reconstruct the FileTree by reconnecting the FileTreeNode.pNext
618 ** fields in sequential order.
619 */
@@ -648,11 +649,11 @@
648 ** name=PATH Directory to display. Optional
649 ** ci=LABEL Show only files in this check-in. Optional.
650 ** re=REGEXP Show only files matching REGEXP. Optional.
651 ** expand Begin with the tree fully expanded.
652 ** nofiles Show directories (folders) only. Omit files.
653 ** mtime Order directory elements by decreasing mtime
654 */
655 void page_tree(void){
656 char *zD = fossil_strdup(P("name"));
657 int nD = zD ? strlen(zD)+1 : 0;
658 const char *zCI = P("ci");
@@ -661,10 +662,11 @@
661 Blob dirname;
662 Manifest *pM = 0;
663 double rNow = 0;
664 char *zNow = 0;
665 int useMtime = atoi(PD("mtime","0"));
 
666 int linkTrunk = 1; /* include link to "trunk" */
667 int linkTip = 1; /* include link to "tip" */
668 const char *zRE; /* the value for the re=REGEXP query parameter */
669 const char *zObjType; /* "files" by default or "folders" for "nofiles" */
670 char *zREx = ""; /* Extra parameters for path hyperlinks */
@@ -765,11 +767,18 @@
765 style_submenu_element("Top-Level", "%s",
766 url_render(&sURI, "name", 0, 0, 0));
767 }else if( zRE ){
768 blob_appendf(&dirname, "matching \"%s\"", zRE);
769 }
770 style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
 
 
 
 
 
 
 
771 if( zCI ){
772 style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
773 if( nD==0 && !showDirOnly ){
774 style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
775 }
@@ -788,46 +797,49 @@
788 */
789 if( zCI ){
790 Stmt q;
791 compute_fileage(rid, 0);
792 db_prepare(&q,
793 "SELECT filename.name, blob.uuid, fileage.mtime\n"
794 " FROM fileage, filename, blob\n"
795 " WHERE filename.fnid=fileage.fnid\n"
796 " AND blob.rid=fileage.fid\n"
797 " ORDER BY filename.name COLLATE uintnocase;"
798 );
799 while( db_step(&q)==SQLITE_ROW ){
800 const char *zFile = db_column_text(&q,0);
801 const char *zUuid = db_column_text(&q,1);
802 double mtime = db_column_double(&q,2);
 
803 if( nD>0 && (fossil_strncmp(zFile, zD, nD-1)!=0 || zFile[nD-1]!='/') ){
804 continue;
805 }
806 if( pRE && re_match(pRE, (const unsigned char*)zFile, -1)==0 ) continue;
807 tree_add_node(&sTree, zFile, zUuid, mtime);
808 }
809 db_finalize(&q);
810 }else{
811 Stmt q;
812 db_prepare(&q,
813 "SELECT\n"
814 " (SELECT name FROM filename WHERE filename.fnid=mlink.fnid),\n"
815 " (SELECT uuid FROM blob WHERE blob.rid=mlink.fid),\n"
 
816 " max(event.mtime)\n"
817 " FROM mlink JOIN event ON event.objid=mlink.mid\n"
818 " GROUP BY mlink.fnid\n"
819 " ORDER BY 1 COLLATE uintnocase;");
820 while( db_step(&q)==SQLITE_ROW ){
821 const char *zName = db_column_text(&q, 0);
822 const char *zUuid = db_column_text(&q,1);
823 double mtime = db_column_double(&q,2);
 
824 if( nD>0 && (fossil_strncmp(zName, zD, nD-1)!=0 || zName[nD-1]!='/') ){
825 continue;
826 }
827 if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue;
828 tree_add_node(&sTree, zName, zUuid, mtime);
829 }
830 db_finalize(&q);
831 }
832 style_submenu_checkbox("nofiles", "Folders Only", 0, 0);
833
@@ -853,12 +865,14 @@
853 }
854 }else{
855 int n = db_int(0, "SELECT count(*) FROM plink");
856 @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
857 }
858 if( useMtime ){
859 @ sorted by modification time</h2>
 
 
860 }else{
861 @ sorted by filename</h2>
862 }
863
864 if( zNow ){
@@ -884,16 +898,17 @@
884 @ <li class="dir subdir last">
885 }
886 @ <div class="filetreeline">
887 @ %z(href("%s",url_render(&sURI,"name",0,0,0)))%h(zProjectName)</a>
888 if( zNow ){
889 @ <div class="filetreeage">%s(zNow)</div>
 
890 }
891 @ </div>
892 @ <ul>
893 if( useMtime ){
894 p = sortTreeByMtime(sTree.pFirst);
895 memset(&sTree, 0, sizeof(sTree));
896 relinkTree(&sTree, p);
897 }
898 for(p=sTree.pFirst, nDir=0; p; p=p->pNext){
899 const char *zLastClass = p->pSibling==0 ? " last" : "";
@@ -902,10 +917,11 @@
902 @ <li class="dir%s(zSubdirClass)%s(zLastClass)"><div class="filetreeline">
903 @ %z(href("%s",url_render(&sURI,"name",p->zFullName,0,0)))%h(p->zName)</a>
904 if( p->mtime>0.0 ){
905 char *zAge = human_readable_age(rNow - p->mtime);
906 @ <div class="filetreeage">%s(zAge)</div>
 
907 }
908 @ </div>
909 if( startExpanded || (int)(p->nFullName)<=nD ){
910 @ <ul id="dir%d(nDir)">
911 }else{
@@ -923,10 +939,11 @@
923 @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
924 @ %z(zLink)%h(p->zName)</a>
925 if( p->mtime>0 ){
926 char *zAge = human_readable_age(rNow - p->mtime);
927 @ <div class="filetreeage">%s(zAge)</div>
 
928 }
929 @ </div>
930 }
931 if( p->pSibling==0 ){
932 int nClose = p->iLevel - (p->pNext ? p->pNext->iLevel : 0);
@@ -1192,5 +1209,22 @@
1192 @ </table></div>
1193 db_finalize(&q1);
1194 db_finalize(&q2);
1195 style_finish_page();
1196 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1197
--- src/browse.c
+++ src/browse.c
@@ -271,54 +271,42 @@
271 */
272 db_multi_exec(
273 "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);"
274 );
275 if( zCI ){
276 /* Files in the specific checked given by zCI */
277 if( zD ){
278 db_multi_exec(
279 "INSERT OR IGNORE INTO localfiles"
280 " SELECT pathelement(filename,%d), uuid"
281 " FROM files_of_checkin(%Q)"
282 " WHERE filename GLOB '%q/*'",
283 nD, zCI, zD
284 );
285 }else{
286 db_multi_exec(
287 "INSERT OR IGNORE INTO localfiles"
288 " SELECT pathelement(filename,%d), uuid"
289 " FROM files_of_checkin(%Q)",
290 nD, zCI
291 );
292 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293 }else{
294 /* All files across all check-ins */
295 if( zD ){
296 db_multi_exec(
297 "INSERT OR IGNORE INTO localfiles"
298 " SELECT pathelement(name,%d), NULL FROM filename"
299 " WHERE name GLOB '%q/*'",
300 nD, zD
301 );
302 }else{
303 db_multi_exec(
304 "INSERT OR IGNORE INTO localfiles"
305 " SELECT pathelement(name,0), NULL FROM filename"
306 );
307 }
308 }
309
310 /* If the re=REGEXP query parameter is present, filter out names that
311 ** do not match the pattern */
312 if( zRegexp ){
@@ -442,10 +430,12 @@
430 FileTreeNode *pLastChild; /* Last child on the pChild list */
431 char *zName; /* Name of this entry. The "tail" */
432 char *zFullName; /* Full pathname of this entry */
433 char *zUuid; /* Artifact hash of this file. May be NULL. */
434 double mtime; /* Modification time for this entry */
435 double sortBy; /* Either mtime or size, depending on desired sort order */
436 int iSize; /* Size for this entry */
437 unsigned nFullName; /* Length of zFullName */
438 unsigned iLevel; /* Levels of parent directories */
439 };
440
441 /*
@@ -471,15 +461,17 @@
461 */
462 static void tree_add_node(
463 FileTree *pTree, /* Tree into which nodes are added */
464 const char *zPath, /* The full pathname of file to add */
465 const char *zUuid, /* Hash of the file. Might be NULL. */
466 double mtime, /* Modification time for this entry */
467 int size, /* Size for this entry */
468 int sortOrder /* 0: filename, 1: mtime, 2: size */
469 ){
470 int i;
471 FileTreeNode *pParent; /* Parent (directory) of the next node to insert */
472 //fossil_print("<pre>zPath %s zUuid %s mtime %f size %d</pre>\n",zPath,zUuid,mtime,size);
473 /* Make pParent point to the most recent ancestor of zPath, or
474 ** NULL if there are no prior entires that are a container for zPath.
475 */
476 pParent = pTree->pLast;
477 while( pParent!=0 &&
@@ -525,10 +517,16 @@
517 }else{
518 if( pTree->pLastTop ) pTree->pLastTop->pSibling = pNew;
519 pTree->pLastTop = pNew;
520 }
521 pNew->mtime = mtime;
522 pNew->iSize = size;
523 if( sortOrder ){
524 pNew->sortBy = sortOrder==1 ? mtime : (double)size;
525 }else{
526 pNew->sortBy = 0.0;
527 }
528 while( zPath[i]=='/' ){ i++; }
529 pParent = pNew;
530 }
531 while( pParent && pParent->pParent ){
532 if( pParent->pParent->mtime < pParent->mtime ){
@@ -537,19 +535,22 @@
535 pParent = pParent->pParent;
536 }
537 }
538
539 /* Comparison function for two FileTreeNode objects. Sort first by
540 ** sortBy (larger numbers first) and then by zName (smaller names first).
541 **
542 ** The sortBy field will be the same as mtime in order to sort by time,
543 ** or the same as iSize to sort by file size.
544 **
545 ** Return negative if pLeft<pRight.
546 ** Return positive if pLeft>pRight.
547 ** Return zero if pLeft==pRight.
548 */
549 static int compareNodes(FileTreeNode *pLeft, FileTreeNode *pRight){
550 if( pLeft->sortBy>pRight->sortBy ) return -1;
551 if( pLeft->sortBy<pRight->sortBy ) return +1;
552 return fossil_stricmp(pLeft->zName, pRight->zName);
553 }
554
555 /* Merge together two sorted lists of FileTreeNode objects */
556 static FileTreeNode *mergeNodes(FileTreeNode *pLeft, FileTreeNode *pRight){
@@ -571,12 +572,12 @@
572 pEnd->pSibling = pRight;
573 }
574 return base.pSibling;
575 }
576
577 /* Sort a list of FileTreeNode objects in sortmtime order. */
578 static FileTreeNode *sortNodes(FileTreeNode *p){
579 FileTreeNode *a[30];
580 FileTreeNode *pX;
581 int i;
582
583 memset(a, 0, sizeof(a));
@@ -604,16 +605,16 @@
605 ** FileTreeNode.pLastChild
606 ** FileTreeNode.pNext
607 **
608 ** Use relinkTree to reconnect the pNext pointers.
609 */
610 static FileTreeNode *sortTree(FileTreeNode *p){
611 FileTreeNode *pX;
612 for(pX=p; pX; pX=pX->pSibling){
613 if( pX->pChild ) pX->pChild = sortTree(pX->pChild);
614 }
615 return sortNodes(p);
616 }
617
618 /* Reconstruct the FileTree by reconnecting the FileTreeNode.pNext
619 ** fields in sequential order.
620 */
@@ -648,11 +649,11 @@
649 ** name=PATH Directory to display. Optional
650 ** ci=LABEL Show only files in this check-in. Optional.
651 ** re=REGEXP Show only files matching REGEXP. Optional.
652 ** expand Begin with the tree fully expanded.
653 ** nofiles Show directories (folders) only. Omit files.
654 ** sort 0: by filename, 1: by mtime, 2: by size
655 */
656 void page_tree(void){
657 char *zD = fossil_strdup(P("name"));
658 int nD = zD ? strlen(zD)+1 : 0;
659 const char *zCI = P("ci");
@@ -661,10 +662,11 @@
662 Blob dirname;
663 Manifest *pM = 0;
664 double rNow = 0;
665 char *zNow = 0;
666 int useMtime = atoi(PD("mtime","0"));
667 int sortOrder = atoi(PD("sort",useMtime?"1":"0"));
668 int linkTrunk = 1; /* include link to "trunk" */
669 int linkTip = 1; /* include link to "tip" */
670 const char *zRE; /* the value for the re=REGEXP query parameter */
671 const char *zObjType; /* "files" by default or "folders" for "nofiles" */
672 char *zREx = ""; /* Extra parameters for path hyperlinks */
@@ -765,11 +767,18 @@
767 style_submenu_element("Top-Level", "%s",
768 url_render(&sURI, "name", 0, 0, 0));
769 }else if( zRE ){
770 blob_appendf(&dirname, "matching \"%s\"", zRE);
771 }
772 {
773 static const char *const sort_orders[] = {
774 "0", "Sort By Filename",
775 "1", "Sort By Age",
776 "2", "Sort By Size"
777 };
778 style_submenu_multichoice("sort", 3, sort_orders, 0);
779 }
780 if( zCI ){
781 style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
782 if( nD==0 && !showDirOnly ){
783 style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
784 }
@@ -788,46 +797,49 @@
797 */
798 if( zCI ){
799 Stmt q;
800 compute_fileage(rid, 0);
801 db_prepare(&q,
802 "SELECT filename.name, blob.uuid, blob.size, fileage.mtime\n"
803 " FROM fileage, filename, blob\n"
804 " WHERE filename.fnid=fileage.fnid\n"
805 " AND blob.rid=fileage.fid\n"
806 " ORDER BY filename.name COLLATE uintnocase;"
807 );
808 while( db_step(&q)==SQLITE_ROW ){
809 const char *zFile = db_column_text(&q,0);
810 const char *zUuid = db_column_text(&q,1);
811 int size = db_column_int(&q,2);
812 double mtime = db_column_double(&q,3);
813 if( nD>0 && (fossil_strncmp(zFile, zD, nD-1)!=0 || zFile[nD-1]!='/') ){
814 continue;
815 }
816 if( pRE && re_match(pRE, (const unsigned char*)zFile, -1)==0 ) continue;
817 tree_add_node(&sTree, zFile, zUuid, mtime, size, sortOrder);
818 }
819 db_finalize(&q);
820 }else{
821 Stmt q;
822 db_prepare(&q,
823 "SELECT\n"
824 " (SELECT name FROM filename WHERE filename.fnid=mlink.fnid),\n"
825 " (SELECT uuid FROM blob WHERE blob.rid=mlink.fid),\n"
826 " (SELECT size FROM blob WHERE blob.rid=mlink.fid),\n"
827 " max(event.mtime)\n"
828 " FROM mlink JOIN event ON event.objid=mlink.mid\n"
829 " GROUP BY mlink.fnid\n"
830 " ORDER BY 1 COLLATE uintnocase;");
831 while( db_step(&q)==SQLITE_ROW ){
832 const char *zName = db_column_text(&q, 0);
833 const char *zUuid = db_column_text(&q,1);
834 int size = db_column_int(&q,2);
835 double mtime = db_column_double(&q,3);
836 if( nD>0 && (fossil_strncmp(zName, zD, nD-1)!=0 || zName[nD-1]!='/') ){
837 continue;
838 }
839 if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue;
840 tree_add_node(&sTree, zName, zUuid, mtime, size, sortOrder);
841 }
842 db_finalize(&q);
843 }
844 style_submenu_checkbox("nofiles", "Folders Only", 0, 0);
845
@@ -853,12 +865,14 @@
865 }
866 }else{
867 int n = db_int(0, "SELECT count(*) FROM plink");
868 @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
869 }
870 if( sortOrder==1 ){
871 @ sorted by modification time</h2>
872 }else if( sortOrder==2 ){
873 @ sorted by size</h2>
874 }else{
875 @ sorted by filename</h2>
876 }
877
878 if( zNow ){
@@ -884,16 +898,17 @@
898 @ <li class="dir subdir last">
899 }
900 @ <div class="filetreeline">
901 @ %z(href("%s",url_render(&sURI,"name",0,0,0)))%h(zProjectName)</a>
902 if( zNow ){
903 @ <div class="filetreeage">Last Change</div>
904 @ <div class="filetreesize">Size</div>
905 }
906 @ </div>
907 @ <ul>
908 if( sortOrder ){
909 p = sortTree(sTree.pFirst);
910 memset(&sTree, 0, sizeof(sTree));
911 relinkTree(&sTree, p);
912 }
913 for(p=sTree.pFirst, nDir=0; p; p=p->pNext){
914 const char *zLastClass = p->pSibling==0 ? " last" : "";
@@ -902,10 +917,11 @@
917 @ <li class="dir%s(zSubdirClass)%s(zLastClass)"><div class="filetreeline">
918 @ %z(href("%s",url_render(&sURI,"name",p->zFullName,0,0)))%h(p->zName)</a>
919 if( p->mtime>0.0 ){
920 char *zAge = human_readable_age(rNow - p->mtime);
921 @ <div class="filetreeage">%s(zAge)</div>
922 @ <div class="filetreesize"></div>
923 }
924 @ </div>
925 if( startExpanded || (int)(p->nFullName)<=nD ){
926 @ <ul id="dir%d(nDir)">
927 }else{
@@ -923,10 +939,11 @@
939 @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
940 @ %z(zLink)%h(p->zName)</a>
941 if( p->mtime>0 ){
942 char *zAge = human_readable_age(rNow - p->mtime);
943 @ <div class="filetreeage">%s(zAge)</div>
944 @ <div class="filetreesize">%s(p->iSize ? mprintf("%,d",p->iSize) : "-")</div>
945 }
946 @ </div>
947 }
948 if( p->pSibling==0 ){
949 int nClose = p->iLevel - (p->pNext ? p->pNext->iLevel : 0);
@@ -1192,5 +1209,22 @@
1209 @ </table></div>
1210 db_finalize(&q1);
1211 db_finalize(&q2);
1212 style_finish_page();
1213 }
1214
1215 /*
1216 ** WEBPAGE: files
1217 **
1218 ** Show files as a flat table. If the ci=LABEL query parameter is provided,
1219 ** then show all the files in the specified check-in. Without the ci= query
1220 ** parameter show all files across all check-ins.
1221 **
1222 ** Query parameters:
1223 **
1224 ** name=PATH Directory to display. Optional
1225 ** ci=LABEL Show only files in this check-in. Optional.
1226 ** re=REGEXP Show only files matching REGEXP. Optional.
1227 */
1228 void files_page(void){
1229 return;
1230 }
1231
+8 -1
--- src/default.css
+++ src/default.css
@@ -332,12 +332,19 @@
332332
v\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo\
333333
+jUs6b5Z/K4siDu5RPUFADs=");
334334
}
335335
div.filetreeage {
336336
display: table-cell;
337
- padding-left: 3em;
337
+ padding-left: 1.5em;
338
+ text-align: right;
339
+ width: 8em;
340
+}
341
+div.filetreesize {
342
+ display: table-cell;
343
+ padding-left: 1em;
338344
text-align: right;
345
+ width: 7em;
339346
}
340347
div.filetreeline:hover {
341348
background-color: #eee;
342349
}
343350
table.login_out {
344351
--- src/default.css
+++ src/default.css
@@ -332,12 +332,19 @@
332 v\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo\
333 +jUs6b5Z/K4siDu5RPUFADs=");
334 }
335 div.filetreeage {
336 display: table-cell;
337 padding-left: 3em;
 
 
 
 
 
 
338 text-align: right;
 
339 }
340 div.filetreeline:hover {
341 background-color: #eee;
342 }
343 table.login_out {
344
--- src/default.css
+++ src/default.css
@@ -332,12 +332,19 @@
332 v\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo\
333 +jUs6b5Z/K4siDu5RPUFADs=");
334 }
335 div.filetreeage {
336 display: table-cell;
337 padding-left: 1.5em;
338 text-align: right;
339 width: 8em;
340 }
341 div.filetreesize {
342 display: table-cell;
343 padding-left: 1em;
344 text-align: right;
345 width: 7em;
346 }
347 div.filetreeline:hover {
348 background-color: #eee;
349 }
350 table.login_out {
351

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button