Fossil SCM

Add links to tarball and ZIP downloads from /dir and /tree pages. Standardize ZIP and tarball basenames using the archive_base_name() routine.

drh 2025-10-18 01:20 timeline-enhance-2025
Commit 983331a13a9062e23f103246c52f8ce980c6c35df21452cc841ba0b6c2e2835b
4 files changed +16 +2 -2 +40 -9 +1 -9
+16
--- src/browse.c
+++ src/browse.c
@@ -229,10 +229,18 @@
229229
}else{
230230
zMatch = "";
231231
}
232232
style_header("%s", zHeader);
233233
fossil_free(zHeader);
234
+ if( rid && zD==0 && zMatch[0]==0 && g.perm.Zip ){
235
+ char *zBase = archive_base_name(rid);
236
+ style_submenu_element("Tarball","%R/tarball/%!S/%s.tar.gz",
237
+ zUuid, zBase);
238
+ style_submenu_element("ZIP","%R/zip/%!S/%s.zip",
239
+ zUuid, zBase);
240
+ fossil_free(zBase);
241
+ }
234242
style_adunit_config(ADUNIT_RIGHT_OK);
235243
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
236244
pathelementFunc, 0, 0);
237245
url_initialize(&sURI, "dir");
238246
cgi_check_for_malice();
@@ -814,10 +822,18 @@
814822
style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
815823
}
816824
}
817825
style_submenu_element("Flat-View", "%s",
818826
url_render(&sURI, "type", "flat", 0, 0));
827
+ if( rid && zD==0 && zRE==0 && !showDirOnly && g.perm.Zip ){
828
+ char *zBase = archive_base_name(rid);
829
+ style_submenu_element("Tarball","%R/tarball/%!S/%s.tar.gz",
830
+ zUuid, zBase);
831
+ style_submenu_element("ZIP","%R/zip/%!S/%s.zip",
832
+ zUuid, zBase);
833
+ fossil_free(zBase);
834
+ }
819835
820836
/* Compute the file hierarchy.
821837
*/
822838
if( zCI ){
823839
Stmt q;
824840
--- src/browse.c
+++ src/browse.c
@@ -229,10 +229,18 @@
229 }else{
230 zMatch = "";
231 }
232 style_header("%s", zHeader);
233 fossil_free(zHeader);
 
 
 
 
 
 
 
 
234 style_adunit_config(ADUNIT_RIGHT_OK);
235 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
236 pathelementFunc, 0, 0);
237 url_initialize(&sURI, "dir");
238 cgi_check_for_malice();
@@ -814,10 +822,18 @@
814 style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
815 }
816 }
817 style_submenu_element("Flat-View", "%s",
818 url_render(&sURI, "type", "flat", 0, 0));
 
 
 
 
 
 
 
 
819
820 /* Compute the file hierarchy.
821 */
822 if( zCI ){
823 Stmt q;
824
--- src/browse.c
+++ src/browse.c
@@ -229,10 +229,18 @@
229 }else{
230 zMatch = "";
231 }
232 style_header("%s", zHeader);
233 fossil_free(zHeader);
234 if( rid && zD==0 && zMatch[0]==0 && g.perm.Zip ){
235 char *zBase = archive_base_name(rid);
236 style_submenu_element("Tarball","%R/tarball/%!S/%s.tar.gz",
237 zUuid, zBase);
238 style_submenu_element("ZIP","%R/zip/%!S/%s.zip",
239 zUuid, zBase);
240 fossil_free(zBase);
241 }
242 style_adunit_config(ADUNIT_RIGHT_OK);
243 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
244 pathelementFunc, 0, 0);
245 url_initialize(&sURI, "dir");
246 cgi_check_for_malice();
@@ -814,10 +822,18 @@
822 style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
823 }
824 }
825 style_submenu_element("Flat-View", "%s",
826 url_render(&sURI, "type", "flat", 0, 0));
827 if( rid && zD==0 && zRE==0 && !showDirOnly && g.perm.Zip ){
828 char *zBase = archive_base_name(rid);
829 style_submenu_element("Tarball","%R/tarball/%!S/%s.tar.gz",
830 zUuid, zBase);
831 style_submenu_element("ZIP","%R/zip/%!S/%s.zip",
832 zUuid, zBase);
833 fossil_free(zBase);
834 }
835
836 /* Compute the file hierarchy.
837 */
838 if( zCI ){
839 Stmt q;
840
+2 -2
--- src/db.c
+++ src/db.c
@@ -1228,12 +1228,12 @@
12281228
}
12291229
12301230
/*
12311231
** Execute a query. Return the first column of the first row
12321232
** of the result set as a string. Space to hold the string is
1233
-** obtained from malloc(). If the result set is empty, return
1234
-** zDefault instead.
1233
+** obtained from fossil_strdup() and should be freed using fossil_free().
1234
+** If the result set is empty, return a copy of zDefault instead.
12351235
*/
12361236
char *db_text(const char *zDefault, const char *zSql, ...){
12371237
va_list ap;
12381238
Stmt s;
12391239
char *z;
12401240
--- src/db.c
+++ src/db.c
@@ -1228,12 +1228,12 @@
1228 }
1229
1230 /*
1231 ** Execute a query. Return the first column of the first row
1232 ** of the result set as a string. Space to hold the string is
1233 ** obtained from malloc(). If the result set is empty, return
1234 ** zDefault instead.
1235 */
1236 char *db_text(const char *zDefault, const char *zSql, ...){
1237 va_list ap;
1238 Stmt s;
1239 char *z;
1240
--- src/db.c
+++ src/db.c
@@ -1228,12 +1228,12 @@
1228 }
1229
1230 /*
1231 ** Execute a query. Return the first column of the first row
1232 ** of the result set as a string. Space to hold the string is
1233 ** obtained from fossil_strdup() and should be freed using fossil_free().
1234 ** If the result set is empty, return a copy of zDefault instead.
1235 */
1236 char *db_text(const char *zDefault, const char *zSql, ...){
1237 va_list ap;
1238 Stmt s;
1239 char *z;
1240
+40 -9
--- src/tar.c
+++ src/tar.c
@@ -31,10 +31,49 @@
3131
char *zPrevDir; /* Name of directory for previous entry */
3232
int nPrevDirAlloc; /* size of zPrevDir */
3333
Blob pax; /* PAX data */
3434
} tball;
3535
36
+/*
37
+** Compute a sensible base-name for an archive file (tarball, ZIP, or SQLAR)
38
+** based on the rid of the check-in contained in that file.
39
+**
40
+** PROJECTNAME-DATETIME-HASHPREFIX
41
+**
42
+** So that the name will be safe to use as a URL or a filename on any system,
43
+** the name is only allowed to contain lower-case ASCII alphabetics,
44
+** digits, '_' and '-'. Upper-case ASCII is converted to lower-case. All
45
+** other bytes are mapped into a lower-case alphabetic.
46
+**
47
+** The value returned is obtained from mprintf() or fossil_strdup() and should
48
+** be released by the caller using fossil_free().
49
+*/
50
+char *archive_base_name(int rid){
51
+ char *zName;
52
+ int i;
53
+ char c;
54
+ zName = db_text(0,
55
+ "SELECT coalesce(config.value,'unnamed')||"
56
+ " strftime('-%%Y%%m%%d%%H%%M%%S-',event.mtime)||"
57
+ " substr(blob.uuid,1,10)"
58
+ " FROM blob, event LEFT JOIN config"
59
+ " WHERE blob.rid=%d"
60
+ " AND event.objid=%d"
61
+ " AND config.name='project-name'",
62
+ rid, rid);
63
+ for(i=0; (c = zName[i])!=0; i++){
64
+ if( fossil_isupper(c) ){
65
+ zName[i] = fossil_tolower(c);
66
+ }else if( !fossil_isalnum(c) && c!='_' && c!='-' ){
67
+ /* 123456789 123456789 123456 */
68
+ zName[i] = "abcdefghijklmnopqrstuvwxyz"[(unsigned)c%26];
69
+ }
70
+ }
71
+ return zName;
72
+}
73
+
74
+
3675
3776
/*
3877
** field lengths of 'ustar' name and prefix fields.
3978
*/
4079
#define USTAR_NAME_LEN 100
@@ -653,19 +692,11 @@
653692
if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){
654693
zOut = 0;
655694
}
656695
657696
if( zName==0 ){
658
- zName = db_text("default-name",
659
- "SELECT replace(%Q,' ','_') "
660
- " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
661
- " || substr(blob.uuid, 1, 10)"
662
- " FROM event, blob"
663
- " WHERE event.objid=%d"
664
- " AND blob.rid=%d",
665
- db_get("project-name", "unnamed"), rid, rid
666
- );
697
+ zName = archive_base_name(rid);
667698
}
668699
tarball_of_checkin(rid, zOut ? &tarball : 0,
669700
zName, pInclude, pExclude, listFlag);
670701
glob_free(pInclude);
671702
glob_free(pExclude);
672703
--- src/tar.c
+++ src/tar.c
@@ -31,10 +31,49 @@
31 char *zPrevDir; /* Name of directory for previous entry */
32 int nPrevDirAlloc; /* size of zPrevDir */
33 Blob pax; /* PAX data */
34 } tball;
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
37 /*
38 ** field lengths of 'ustar' name and prefix fields.
39 */
40 #define USTAR_NAME_LEN 100
@@ -653,19 +692,11 @@
653 if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){
654 zOut = 0;
655 }
656
657 if( zName==0 ){
658 zName = db_text("default-name",
659 "SELECT replace(%Q,' ','_') "
660 " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
661 " || substr(blob.uuid, 1, 10)"
662 " FROM event, blob"
663 " WHERE event.objid=%d"
664 " AND blob.rid=%d",
665 db_get("project-name", "unnamed"), rid, rid
666 );
667 }
668 tarball_of_checkin(rid, zOut ? &tarball : 0,
669 zName, pInclude, pExclude, listFlag);
670 glob_free(pInclude);
671 glob_free(pExclude);
672
--- src/tar.c
+++ src/tar.c
@@ -31,10 +31,49 @@
31 char *zPrevDir; /* Name of directory for previous entry */
32 int nPrevDirAlloc; /* size of zPrevDir */
33 Blob pax; /* PAX data */
34 } tball;
35
36 /*
37 ** Compute a sensible base-name for an archive file (tarball, ZIP, or SQLAR)
38 ** based on the rid of the check-in contained in that file.
39 **
40 ** PROJECTNAME-DATETIME-HASHPREFIX
41 **
42 ** So that the name will be safe to use as a URL or a filename on any system,
43 ** the name is only allowed to contain lower-case ASCII alphabetics,
44 ** digits, '_' and '-'. Upper-case ASCII is converted to lower-case. All
45 ** other bytes are mapped into a lower-case alphabetic.
46 **
47 ** The value returned is obtained from mprintf() or fossil_strdup() and should
48 ** be released by the caller using fossil_free().
49 */
50 char *archive_base_name(int rid){
51 char *zName;
52 int i;
53 char c;
54 zName = db_text(0,
55 "SELECT coalesce(config.value,'unnamed')||"
56 " strftime('-%%Y%%m%%d%%H%%M%%S-',event.mtime)||"
57 " substr(blob.uuid,1,10)"
58 " FROM blob, event LEFT JOIN config"
59 " WHERE blob.rid=%d"
60 " AND event.objid=%d"
61 " AND config.name='project-name'",
62 rid, rid);
63 for(i=0; (c = zName[i])!=0; i++){
64 if( fossil_isupper(c) ){
65 zName[i] = fossil_tolower(c);
66 }else if( !fossil_isalnum(c) && c!='_' && c!='-' ){
67 /* 123456789 123456789 123456 */
68 zName[i] = "abcdefghijklmnopqrstuvwxyz"[(unsigned)c%26];
69 }
70 }
71 return zName;
72 }
73
74
75
76 /*
77 ** field lengths of 'ustar' name and prefix fields.
78 */
79 #define USTAR_NAME_LEN 100
@@ -653,19 +692,11 @@
692 if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){
693 zOut = 0;
694 }
695
696 if( zName==0 ){
697 zName = archive_base_name(rid);
 
 
 
 
 
 
 
 
698 }
699 tarball_of_checkin(rid, zOut ? &tarball : 0,
700 zName, pInclude, pExclude, listFlag);
701 glob_free(pInclude);
702 glob_free(pExclude);
703
+1 -9
--- src/zip.c
+++ src/zip.c
@@ -864,19 +864,11 @@
864864
if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){
865865
zOut = 0;
866866
}
867867
868868
if( zName==0 ){
869
- zName = db_text("default-name",
870
- "SELECT replace(%Q,' ','_') "
871
- " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
872
- " || substr(blob.uuid, 1, 10)"
873
- " FROM event, blob"
874
- " WHERE event.objid=%d"
875
- " AND blob.rid=%d",
876
- db_get("project-name", "unnamed"), rid, rid
877
- );
869
+ zName = archive_base_name(rid);
878870
}
879871
zip_of_checkin(eType, rid, zOut ? &zip : 0,
880872
zName, pInclude, pExclude, listFlag);
881873
glob_free(pInclude);
882874
glob_free(pExclude);
883875
--- src/zip.c
+++ src/zip.c
@@ -864,19 +864,11 @@
864 if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){
865 zOut = 0;
866 }
867
868 if( zName==0 ){
869 zName = db_text("default-name",
870 "SELECT replace(%Q,' ','_') "
871 " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
872 " || substr(blob.uuid, 1, 10)"
873 " FROM event, blob"
874 " WHERE event.objid=%d"
875 " AND blob.rid=%d",
876 db_get("project-name", "unnamed"), rid, rid
877 );
878 }
879 zip_of_checkin(eType, rid, zOut ? &zip : 0,
880 zName, pInclude, pExclude, listFlag);
881 glob_free(pInclude);
882 glob_free(pExclude);
883
--- src/zip.c
+++ src/zip.c
@@ -864,19 +864,11 @@
864 if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){
865 zOut = 0;
866 }
867
868 if( zName==0 ){
869 zName = archive_base_name(rid);
 
 
 
 
 
 
 
 
870 }
871 zip_of_checkin(eType, rid, zOut ? &zip : 0,
872 zName, pInclude, pExclude, listFlag);
873 glob_free(pInclude);
874 glob_free(pExclude);
875

Keyboard Shortcuts

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