Fossil SCM

/fileedit now embeds the current open leaf list and the file list for the current checkin (defaulting to the most recent leaf) in the page content, saving 2 XHR requests at startup. If passed filename= without checkin= then it tries to load the given file from the most recent leaf.

stephan 2020-08-10 11:53 trunk
Commit 5fc0f7c33ab41e81327e20ec8b0eadc9121b95a4d27a247646b2b803168d9425
+152 -101
--- src/fileedit.c
+++ src/fileedit.c
@@ -965,11 +965,12 @@
965965
** populates:
966966
**
967967
** - *zRevUuid = the fully-expanded value of zRev (owned by the
968968
** caller). zRevUuid may be NULL.
969969
**
970
-** - *vid = the RID of zRevUuid. May not be NULL.
970
+** - *pVid = the RID of zRevUuid. pVid May be NULL. If the vid
971
+** cannot be resolved or is ambiguous, pVid is not assigned.
971972
**
972973
** - *frid = the RID of zFilename's blob content. May not be NULL
973974
** unless zFilename is also NULL. If BOTH of zFilename and frid are
974975
** NULL then no confirmation is done on the filename argument - only
975976
** zRev is checked.
@@ -979,38 +980,41 @@
979980
** returns false, it queues up an error response and the caller must
980981
** return immediately.
981982
*/
982983
static int fileedit_ajax_setup_filerev(const char * zRev,
983984
char ** zRevUuid,
984
- int * vid,
985
+ int * pVid,
985986
const char * zFilename,
986987
int * frid){
987988
char * zFileUuid = 0; /* file content UUID */
988989
const int checkFile = zFilename!=0 || frid!=0;
990
+ int vid = 0;
989991
990992
if(checkFile && !fileedit_ajax_check_filename(zFilename)){
991993
return 0;
992994
}
993
- *vid = symbolic_name_to_rid(zRev, "ci");
994
- if(0==*vid){
995
+ vid = symbolic_name_to_rid(zRev, "ci");
996
+ if(0==vid){
995997
ajax_route_error(404,"Cannot resolve name as a checkin: %s",
996998
zRev);
997999
return 0;
998
- }else if(*vid<0){
1000
+ }else if(vid<0){
9991001
ajax_route_error(400,"Checkin name is ambiguous: %s",
10001002
zRev);
10011003
return 0;
1004
+ }else if(pVid!=0){
1005
+ *pVid = vid;
10021006
}
10031007
if(checkFile){
1004
- zFileUuid = fileedit_file_uuid(zFilename, *vid, 0);
1008
+ zFileUuid = fileedit_file_uuid(zFilename, vid, 0);
10051009
if(zFileUuid==0){
10061010
ajax_route_error(404, "Checkin does not contain file.");
10071011
return 0;
10081012
}
10091013
}
10101014
if(zRevUuid!=0){
1011
- *zRevUuid = rid_to_uuid(*vid);
1015
+ *zRevUuid = rid_to_uuid(vid);
10121016
}
10131017
if(checkFile){
10141018
assert(zFileUuid!=0);
10151019
if(frid!=0){
10161020
*frid = fast_uuid_to_rid(zFileUuid);
@@ -1290,10 +1294,92 @@
12901294
end_fail:
12911295
#undef fail
12921296
fossil_free(zFileUuid);
12931297
return rc ? rc : 500;
12941298
}
1299
+
1300
+/*
1301
+** Renders a list of all open leaves in JSON form:
1302
+**
1303
+** [
1304
+** {checkin: UUID, branch: branchName, timestamp: string}
1305
+** ]
1306
+**
1307
+** The entries are ordered newest first.
1308
+**
1309
+** If zFirstUuid is not NULL then *zFirstUuid is set to a copy of the
1310
+** full UUID of the first (most recent) leaf, which must be freed by
1311
+** the caller. It is set to 0 if there are no leaves.
1312
+*/
1313
+static void fileedit_render_leaves_list(char ** zFirstUuid){
1314
+ Blob sql = empty_blob;
1315
+ Stmt q = empty_Stmt;
1316
+ int i = 0;
1317
+
1318
+ if(zFirstUuid){
1319
+ *zFirstUuid = 0;
1320
+ }
1321
+ blob_append(&sql, timeline_query_for_tty(), -1);
1322
+ blob_append_sql(&sql, " AND blob.rid IN (SElECT rid FROM leaf "
1323
+ "WHERE NOT EXISTS("
1324
+ "SELECT 1 from tagxref WHERE tagid=%d AND "
1325
+ "tagtype>0 AND rid=leaf.rid"
1326
+ ")) "
1327
+ "ORDER BY mtime DESC", TAG_CLOSED);
1328
+ db_prepare_blob(&q, &sql);
1329
+ CX("[");
1330
+ while( SQLITE_ROW==db_step(&q) ){
1331
+ const char * zUuid = db_column_text(&q, 1);
1332
+ if(i++){
1333
+ CX(",");
1334
+ }else if(zFirstUuid){
1335
+ *zFirstUuid = fossil_strdup(zUuid);
1336
+ }
1337
+ CX("{");
1338
+ CX("\"checkin\":%!j,", zUuid);
1339
+ CX("\"branch\":%!j,", db_column_text(&q, 7));
1340
+ CX("\"timestamp\":%!j", db_column_text(&q, 2));
1341
+ CX("}");
1342
+ }
1343
+ CX("]");
1344
+ db_finalize(&q);
1345
+}
1346
+
1347
+/*
1348
+** For the given fully resolved UUID, renders a JSON object containing
1349
+** the fileeedit-editable files in that checkin:
1350
+**
1351
+** {
1352
+** checkin: UUID,
1353
+** editableFiles: [ filename1, ... filenameN ]
1354
+** }
1355
+**
1356
+** They are sorted by name using filename_collation().
1357
+*/
1358
+static void fileedit_render_checkin_files(const char * zFullUuid){
1359
+ Blob sql = empty_blob;
1360
+ Stmt q = empty_Stmt;
1361
+ int i = 0;
1362
+
1363
+ CX("{\"checkin\":%!j,"
1364
+ "\"editableFiles\":[", zFullUuid);
1365
+ blob_append_sql(&sql, "SELECT filename FROM files_of_checkin(%Q) "
1366
+ "ORDER BY filename %s",
1367
+ zFullUuid, filename_collation());
1368
+ db_prepare_blob(&q, &sql);
1369
+ while( SQLITE_ROW==db_step(&q) ){
1370
+ const char * zFilename = db_column_text(&q, 0);
1371
+ if(fileedit_is_editable(zFilename)){
1372
+ if(i++){
1373
+ CX(",");
1374
+ }
1375
+ CX("%!j", zFilename);
1376
+ }
1377
+ }
1378
+ db_finalize(&q);
1379
+ CX("]}");
1380
+}
12951381
12961382
/*
12971383
** AJAX route /fileedit?ajax=filelist
12981384
**
12991385
** Fetches a JSON-format list of leaves and/or filenames for use in
@@ -1318,66 +1404,27 @@
13181404
** }
13191405
**
13201406
** On error it produces a JSON response as documented for
13211407
** ajax_route_error().
13221408
*/
1323
-static void fileedit_ajax_filelist(void){
1409
+static void fileedit_ajax_filelist(){
13241410
const char * zCi = PD("checkin",P("ci"));
1325
- Blob sql = empty_blob;
1326
- Stmt q = empty_Stmt;
1327
- int i = 0;
13281411
13291412
if(!ajax_route_bootstrap(1,0)){
13301413
return;
13311414
}
13321415
cgi_set_content_type("application/json");
13331416
if(zCi!=0){
13341417
char * zCiFull = 0;
1335
- int vid = 0;
1336
- if(0==fileedit_ajax_setup_filerev(zCi, &zCiFull, &vid, 0, 0)){
1418
+ if(0==fileedit_ajax_setup_filerev(zCi, &zCiFull, 0, 0, 0)){
13371419
/* Error already reported */
13381420
return;
13391421
}
1340
- CX("{\"checkin\":%!j,"
1341
- "\"editableFiles\":[", zCiFull);
1342
- blob_append_sql(&sql, "SELECT filename FROM files_of_checkin(%Q) "
1343
- "ORDER BY filename %s",
1344
- zCiFull, filename_collation());
1345
- db_prepare_blob(&q, &sql);
1346
- while( SQLITE_ROW==db_step(&q) ){
1347
- const char * zFilename = db_column_text(&q, 0);
1348
- if(fileedit_is_editable(zFilename)){
1349
- if(i++){
1350
- CX(",");
1351
- }
1352
- CX("%!j", zFilename);
1353
- }
1354
- }
1355
- db_finalize(&q);
1356
- CX("]}");
1422
+ fileedit_render_checkin_files(zCiFull);
1423
+ fossil_free(zCiFull);
13571424
}else if(P("leaves")!=0){
1358
- blob_append(&sql, timeline_query_for_tty(), -1);
1359
- blob_append_sql(&sql, " AND blob.rid IN (SElECT rid FROM leaf "
1360
- "WHERE NOT EXISTS("
1361
- "SELECT 1 from tagxref WHERE tagid=%d AND "
1362
- "tagtype>0 AND rid=leaf.rid"
1363
- ")) "
1364
- "ORDER BY mtime DESC", TAG_CLOSED);
1365
- db_prepare_blob(&q, &sql);
1366
- CX("[");
1367
- while( SQLITE_ROW==db_step(&q) ){
1368
- if(i++){
1369
- CX(",");
1370
- }
1371
- CX("{");
1372
- CX("\"checkin\":%!j,", db_column_text(&q, 1));
1373
- CX("\"branch\":%!j,", db_column_text(&q, 7));
1374
- CX("\"timestamp\":%!j", db_column_text(&q, 2));
1375
- CX("}");
1376
- }
1377
- CX("]");
1378
- db_finalize(&q);
1425
+ fileedit_render_leaves_list(0);
13791426
}else{
13801427
ajax_route_error(500, "Unhandled URL argument.");
13811428
}
13821429
}
13831430
@@ -1509,24 +1556,14 @@
15091556
** an internal implementation detail and may change with any given
15101557
** build of this code. An unknown "name" value triggers an error, as
15111558
** documented for ajax_route_error().
15121559
*/
15131560
void fileedit_page(void){
1514
- const char * zFilename = 0; /* filename. We'll accept 'name'
1515
- because that param is handled
1516
- specially by the core. */
1517
- const char * zRev = 0; /* checkin version */
15181561
const char * zFileMime = 0; /* File mime type guess */
15191562
CheckinMiniInfo cimi; /* Checkin state */
15201563
int previewRenderMode = AJAX_RENDER_GUESS; /* preview mode */
15211564
Blob err = empty_blob; /* Error report */
1522
- Blob endScript = empty_blob; /* Script code to run at the
1523
- end. This content will be
1524
- combined into a single JS
1525
- function call, thus each
1526
- entry must end with a
1527
- semicolon. */
15281565
const char *zAjax = P("name"); /* Name of AJAX route for
15291566
sub-dispatching. */
15301567
15311568
/* Allow no access to this page without check-in privilege */
15321569
login_check_credentials();
@@ -1590,14 +1627,11 @@
15901627
** transaction cleanly.
15911628
*/
15921629
{
15931630
int isMissingArg = 0;
15941631
if(fileedit_setup_cimi_from_p(&cimi, &err, &isMissingArg)==0){
1595
- zFilename = cimi.zFilename;
1596
- zRev = cimi.zParentUuid;
1597
- assert(zRev);
1598
- assert(zFilename);
1632
+ assert(cimi.zFilename);
15991633
zFileMime = mimetype_from_name(cimi.zFilename);
16001634
}else if(isMissingArg!=0){
16011635
/* Squelch these startup warnings - they're non-fatal now but
16021636
** used to be fatal. */
16031637
blob_reset(&err);
@@ -1932,40 +1966,10 @@
19321966
"file/check-in combination are discarded.</li>");
19331967
CX("</ul>");
19341968
}
19351969
CX("</div>"/*#fileedit-tab-help*/);
19361970
1937
- {
1938
- /* Dynamically populate the editor, display any error in the err
1939
- ** blob, and/or switch to tab #0, where the file selector
1940
- ** lives... */
1941
- blob_appendf(&endScript, "fossil.config['fileedit-glob'] = ");
1942
- glob_render_as_json(fileedit_glob(), &endScript);
1943
- blob_append(&endScript, ";\n", 2);
1944
- blob_append(&endScript, "fossil.onPageLoad(", -1);
1945
- if(zRev && zFilename){
1946
- assert(0==blob_size(&err));
1947
- blob_appendf(&endScript,
1948
- "()=>fossil.page.loadFile(%!j,%!j)",
1949
- zFilename, cimi.zParentUuid);
1950
- }else{
1951
- blob_appendf(&endScript,"function(){\n");
1952
- if(blob_size(&err)>0){
1953
- blob_appendf(&endScript,
1954
- "fossil.error(%!j);\n",
1955
- blob_str(&err));
1956
- }
1957
- blob_appendf(&endScript,
1958
- "fossil.page.tabs.switchToTab(0);\n");
1959
- blob_appendf(&endScript,"}");
1960
- }
1961
- blob_appendf(&endScript,");\n");
1962
- }
1963
-
1964
- blob_reset(&err);
1965
- CheckinMiniInfo_cleanup(&cimi);
1966
-
19671971
builtin_request_js("sbsdiff.js");
19681972
style_emit_fossil_js_apis(0, "fetch", "dom", "tabs", "confirmer",
19691973
"storage", 0);
19701974
builtin_fulfill_js_requests();
19711975
/*
@@ -1977,19 +1981,66 @@
19771981
** the JS multiple times.
19781982
*/
19791983
ajax_emit_js_preview_modes(1);
19801984
builtin_request_js("fossil.page.fileedit.js");
19811985
builtin_fulfill_js_requests();
1982
- if(blob_size(&endScript)>0){
1986
+ {
1987
+ /* Dynamically populate the editor, display any error in the err
1988
+ ** blob, and/or switch to tab #0, where the file selector
1989
+ ** lives. The extra C scopes here correspond to JS-level scopes,
1990
+ ** to improve grokability. */
19831991
style_emit_script_tag(0,0);
19841992
CX("\n(function(){\n");
1985
- CX("try{\n%b}\n"
1986
- "catch(e){"
1993
+ CX("try{\n");
1994
+ {
1995
+ char * zFirstLeafUuid = 0;
1996
+ CX("fossil.config['fileedit-glob'] = ");
1997
+ glob_render_json_to_cgi(fileedit_glob());
1998
+ CX(";\n");
1999
+ if(blob_size(&err)>0){
2000
+ CX("fossil.error(%!j);\n", blob_str(&err));
2001
+ }
2002
+ /* Populate the page with the current leaves and, if available,
2003
+ the selected checkin's file list, to save 1 or 2 XHR requests
2004
+ at startup. That makes this page uncacheable, but compressed
2005
+ delivery of this page is currently less than 6k. */
2006
+ CX("fossil.page.initialLeaves = ");
2007
+ fileedit_render_leaves_list(cimi.zParentUuid ? 0 : &zFirstLeafUuid);
2008
+ CX(";\n");
2009
+ if(zFirstLeafUuid){
2010
+ assert(!cimi.zParentUuid);
2011
+ cimi.zParentUuid = zFirstLeafUuid;
2012
+ zFirstLeafUuid = 0;
2013
+ }
2014
+ if(cimi.zParentUuid){
2015
+ CX("fossil.page.initialFiles = ");
2016
+ fileedit_render_checkin_files(cimi.zParentUuid);
2017
+ CX(";\n");
2018
+ }
2019
+ CX("fossil.onPageLoad(function(){\n");
2020
+ {
2021
+ if(blob_size(&err)>0){
2022
+ CX("fossil.error(%!j);\n",
2023
+ blob_str(&err));
2024
+ CX("fossil.page.tabs.switchToTab(0);\n");
2025
+ }
2026
+ if(cimi.zParentUuid && cimi.zFilename){
2027
+ CX("fossil.page.loadFile(%!j,%!j);\n",
2028
+ cimi.zFilename, cimi.zParentUuid)
2029
+ /* Reminder we cannot embed the JSON-format
2030
+ content of the file here because if it contains
2031
+ a SCRIPT tag then it will break the whole page. */;
2032
+ }
2033
+ }
2034
+ CX("});\n")/*fossil.onPageLoad()*/;
2035
+ }
2036
+ CX("}catch(e){"
19872037
"fossil.error(e); console.error('Exception:',e);"
1988
- "}\n",
1989
- &endScript);
1990
- CX("})();");
2038
+ "}\n");
2039
+ CX("})();")/*anonymous function*/;
19912040
style_emit_script_tag(1,0);
19922041
}
2042
+ blob_reset(&err);
2043
+ CheckinMiniInfo_cleanup(&cimi);
19932044
db_end_transaction(0);
19942045
style_footer();
19952046
}
19962047
--- src/fileedit.c
+++ src/fileedit.c
@@ -965,11 +965,12 @@
965 ** populates:
966 **
967 ** - *zRevUuid = the fully-expanded value of zRev (owned by the
968 ** caller). zRevUuid may be NULL.
969 **
970 ** - *vid = the RID of zRevUuid. May not be NULL.
 
971 **
972 ** - *frid = the RID of zFilename's blob content. May not be NULL
973 ** unless zFilename is also NULL. If BOTH of zFilename and frid are
974 ** NULL then no confirmation is done on the filename argument - only
975 ** zRev is checked.
@@ -979,38 +980,41 @@
979 ** returns false, it queues up an error response and the caller must
980 ** return immediately.
981 */
982 static int fileedit_ajax_setup_filerev(const char * zRev,
983 char ** zRevUuid,
984 int * vid,
985 const char * zFilename,
986 int * frid){
987 char * zFileUuid = 0; /* file content UUID */
988 const int checkFile = zFilename!=0 || frid!=0;
 
989
990 if(checkFile && !fileedit_ajax_check_filename(zFilename)){
991 return 0;
992 }
993 *vid = symbolic_name_to_rid(zRev, "ci");
994 if(0==*vid){
995 ajax_route_error(404,"Cannot resolve name as a checkin: %s",
996 zRev);
997 return 0;
998 }else if(*vid<0){
999 ajax_route_error(400,"Checkin name is ambiguous: %s",
1000 zRev);
1001 return 0;
 
 
1002 }
1003 if(checkFile){
1004 zFileUuid = fileedit_file_uuid(zFilename, *vid, 0);
1005 if(zFileUuid==0){
1006 ajax_route_error(404, "Checkin does not contain file.");
1007 return 0;
1008 }
1009 }
1010 if(zRevUuid!=0){
1011 *zRevUuid = rid_to_uuid(*vid);
1012 }
1013 if(checkFile){
1014 assert(zFileUuid!=0);
1015 if(frid!=0){
1016 *frid = fast_uuid_to_rid(zFileUuid);
@@ -1290,10 +1294,92 @@
1290 end_fail:
1291 #undef fail
1292 fossil_free(zFileUuid);
1293 return rc ? rc : 500;
1294 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1295
1296 /*
1297 ** AJAX route /fileedit?ajax=filelist
1298 **
1299 ** Fetches a JSON-format list of leaves and/or filenames for use in
@@ -1318,66 +1404,27 @@
1318 ** }
1319 **
1320 ** On error it produces a JSON response as documented for
1321 ** ajax_route_error().
1322 */
1323 static void fileedit_ajax_filelist(void){
1324 const char * zCi = PD("checkin",P("ci"));
1325 Blob sql = empty_blob;
1326 Stmt q = empty_Stmt;
1327 int i = 0;
1328
1329 if(!ajax_route_bootstrap(1,0)){
1330 return;
1331 }
1332 cgi_set_content_type("application/json");
1333 if(zCi!=0){
1334 char * zCiFull = 0;
1335 int vid = 0;
1336 if(0==fileedit_ajax_setup_filerev(zCi, &zCiFull, &vid, 0, 0)){
1337 /* Error already reported */
1338 return;
1339 }
1340 CX("{\"checkin\":%!j,"
1341 "\"editableFiles\":[", zCiFull);
1342 blob_append_sql(&sql, "SELECT filename FROM files_of_checkin(%Q) "
1343 "ORDER BY filename %s",
1344 zCiFull, filename_collation());
1345 db_prepare_blob(&q, &sql);
1346 while( SQLITE_ROW==db_step(&q) ){
1347 const char * zFilename = db_column_text(&q, 0);
1348 if(fileedit_is_editable(zFilename)){
1349 if(i++){
1350 CX(",");
1351 }
1352 CX("%!j", zFilename);
1353 }
1354 }
1355 db_finalize(&q);
1356 CX("]}");
1357 }else if(P("leaves")!=0){
1358 blob_append(&sql, timeline_query_for_tty(), -1);
1359 blob_append_sql(&sql, " AND blob.rid IN (SElECT rid FROM leaf "
1360 "WHERE NOT EXISTS("
1361 "SELECT 1 from tagxref WHERE tagid=%d AND "
1362 "tagtype>0 AND rid=leaf.rid"
1363 ")) "
1364 "ORDER BY mtime DESC", TAG_CLOSED);
1365 db_prepare_blob(&q, &sql);
1366 CX("[");
1367 while( SQLITE_ROW==db_step(&q) ){
1368 if(i++){
1369 CX(",");
1370 }
1371 CX("{");
1372 CX("\"checkin\":%!j,", db_column_text(&q, 1));
1373 CX("\"branch\":%!j,", db_column_text(&q, 7));
1374 CX("\"timestamp\":%!j", db_column_text(&q, 2));
1375 CX("}");
1376 }
1377 CX("]");
1378 db_finalize(&q);
1379 }else{
1380 ajax_route_error(500, "Unhandled URL argument.");
1381 }
1382 }
1383
@@ -1509,24 +1556,14 @@
1509 ** an internal implementation detail and may change with any given
1510 ** build of this code. An unknown "name" value triggers an error, as
1511 ** documented for ajax_route_error().
1512 */
1513 void fileedit_page(void){
1514 const char * zFilename = 0; /* filename. We'll accept 'name'
1515 because that param is handled
1516 specially by the core. */
1517 const char * zRev = 0; /* checkin version */
1518 const char * zFileMime = 0; /* File mime type guess */
1519 CheckinMiniInfo cimi; /* Checkin state */
1520 int previewRenderMode = AJAX_RENDER_GUESS; /* preview mode */
1521 Blob err = empty_blob; /* Error report */
1522 Blob endScript = empty_blob; /* Script code to run at the
1523 end. This content will be
1524 combined into a single JS
1525 function call, thus each
1526 entry must end with a
1527 semicolon. */
1528 const char *zAjax = P("name"); /* Name of AJAX route for
1529 sub-dispatching. */
1530
1531 /* Allow no access to this page without check-in privilege */
1532 login_check_credentials();
@@ -1590,14 +1627,11 @@
1590 ** transaction cleanly.
1591 */
1592 {
1593 int isMissingArg = 0;
1594 if(fileedit_setup_cimi_from_p(&cimi, &err, &isMissingArg)==0){
1595 zFilename = cimi.zFilename;
1596 zRev = cimi.zParentUuid;
1597 assert(zRev);
1598 assert(zFilename);
1599 zFileMime = mimetype_from_name(cimi.zFilename);
1600 }else if(isMissingArg!=0){
1601 /* Squelch these startup warnings - they're non-fatal now but
1602 ** used to be fatal. */
1603 blob_reset(&err);
@@ -1932,40 +1966,10 @@
1932 "file/check-in combination are discarded.</li>");
1933 CX("</ul>");
1934 }
1935 CX("</div>"/*#fileedit-tab-help*/);
1936
1937 {
1938 /* Dynamically populate the editor, display any error in the err
1939 ** blob, and/or switch to tab #0, where the file selector
1940 ** lives... */
1941 blob_appendf(&endScript, "fossil.config['fileedit-glob'] = ");
1942 glob_render_as_json(fileedit_glob(), &endScript);
1943 blob_append(&endScript, ";\n", 2);
1944 blob_append(&endScript, "fossil.onPageLoad(", -1);
1945 if(zRev && zFilename){
1946 assert(0==blob_size(&err));
1947 blob_appendf(&endScript,
1948 "()=>fossil.page.loadFile(%!j,%!j)",
1949 zFilename, cimi.zParentUuid);
1950 }else{
1951 blob_appendf(&endScript,"function(){\n");
1952 if(blob_size(&err)>0){
1953 blob_appendf(&endScript,
1954 "fossil.error(%!j);\n",
1955 blob_str(&err));
1956 }
1957 blob_appendf(&endScript,
1958 "fossil.page.tabs.switchToTab(0);\n");
1959 blob_appendf(&endScript,"}");
1960 }
1961 blob_appendf(&endScript,");\n");
1962 }
1963
1964 blob_reset(&err);
1965 CheckinMiniInfo_cleanup(&cimi);
1966
1967 builtin_request_js("sbsdiff.js");
1968 style_emit_fossil_js_apis(0, "fetch", "dom", "tabs", "confirmer",
1969 "storage", 0);
1970 builtin_fulfill_js_requests();
1971 /*
@@ -1977,19 +1981,66 @@
1977 ** the JS multiple times.
1978 */
1979 ajax_emit_js_preview_modes(1);
1980 builtin_request_js("fossil.page.fileedit.js");
1981 builtin_fulfill_js_requests();
1982 if(blob_size(&endScript)>0){
 
 
 
 
1983 style_emit_script_tag(0,0);
1984 CX("\n(function(){\n");
1985 CX("try{\n%b}\n"
1986 "catch(e){"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1987 "fossil.error(e); console.error('Exception:',e);"
1988 "}\n",
1989 &endScript);
1990 CX("})();");
1991 style_emit_script_tag(1,0);
1992 }
 
 
1993 db_end_transaction(0);
1994 style_footer();
1995 }
1996
--- src/fileedit.c
+++ src/fileedit.c
@@ -965,11 +965,12 @@
965 ** populates:
966 **
967 ** - *zRevUuid = the fully-expanded value of zRev (owned by the
968 ** caller). zRevUuid may be NULL.
969 **
970 ** - *pVid = the RID of zRevUuid. pVid May be NULL. If the vid
971 ** cannot be resolved or is ambiguous, pVid is not assigned.
972 **
973 ** - *frid = the RID of zFilename's blob content. May not be NULL
974 ** unless zFilename is also NULL. If BOTH of zFilename and frid are
975 ** NULL then no confirmation is done on the filename argument - only
976 ** zRev is checked.
@@ -979,38 +980,41 @@
980 ** returns false, it queues up an error response and the caller must
981 ** return immediately.
982 */
983 static int fileedit_ajax_setup_filerev(const char * zRev,
984 char ** zRevUuid,
985 int * pVid,
986 const char * zFilename,
987 int * frid){
988 char * zFileUuid = 0; /* file content UUID */
989 const int checkFile = zFilename!=0 || frid!=0;
990 int vid = 0;
991
992 if(checkFile && !fileedit_ajax_check_filename(zFilename)){
993 return 0;
994 }
995 vid = symbolic_name_to_rid(zRev, "ci");
996 if(0==vid){
997 ajax_route_error(404,"Cannot resolve name as a checkin: %s",
998 zRev);
999 return 0;
1000 }else if(vid<0){
1001 ajax_route_error(400,"Checkin name is ambiguous: %s",
1002 zRev);
1003 return 0;
1004 }else if(pVid!=0){
1005 *pVid = vid;
1006 }
1007 if(checkFile){
1008 zFileUuid = fileedit_file_uuid(zFilename, vid, 0);
1009 if(zFileUuid==0){
1010 ajax_route_error(404, "Checkin does not contain file.");
1011 return 0;
1012 }
1013 }
1014 if(zRevUuid!=0){
1015 *zRevUuid = rid_to_uuid(vid);
1016 }
1017 if(checkFile){
1018 assert(zFileUuid!=0);
1019 if(frid!=0){
1020 *frid = fast_uuid_to_rid(zFileUuid);
@@ -1290,10 +1294,92 @@
1294 end_fail:
1295 #undef fail
1296 fossil_free(zFileUuid);
1297 return rc ? rc : 500;
1298 }
1299
1300 /*
1301 ** Renders a list of all open leaves in JSON form:
1302 **
1303 ** [
1304 ** {checkin: UUID, branch: branchName, timestamp: string}
1305 ** ]
1306 **
1307 ** The entries are ordered newest first.
1308 **
1309 ** If zFirstUuid is not NULL then *zFirstUuid is set to a copy of the
1310 ** full UUID of the first (most recent) leaf, which must be freed by
1311 ** the caller. It is set to 0 if there are no leaves.
1312 */
1313 static void fileedit_render_leaves_list(char ** zFirstUuid){
1314 Blob sql = empty_blob;
1315 Stmt q = empty_Stmt;
1316 int i = 0;
1317
1318 if(zFirstUuid){
1319 *zFirstUuid = 0;
1320 }
1321 blob_append(&sql, timeline_query_for_tty(), -1);
1322 blob_append_sql(&sql, " AND blob.rid IN (SElECT rid FROM leaf "
1323 "WHERE NOT EXISTS("
1324 "SELECT 1 from tagxref WHERE tagid=%d AND "
1325 "tagtype>0 AND rid=leaf.rid"
1326 ")) "
1327 "ORDER BY mtime DESC", TAG_CLOSED);
1328 db_prepare_blob(&q, &sql);
1329 CX("[");
1330 while( SQLITE_ROW==db_step(&q) ){
1331 const char * zUuid = db_column_text(&q, 1);
1332 if(i++){
1333 CX(",");
1334 }else if(zFirstUuid){
1335 *zFirstUuid = fossil_strdup(zUuid);
1336 }
1337 CX("{");
1338 CX("\"checkin\":%!j,", zUuid);
1339 CX("\"branch\":%!j,", db_column_text(&q, 7));
1340 CX("\"timestamp\":%!j", db_column_text(&q, 2));
1341 CX("}");
1342 }
1343 CX("]");
1344 db_finalize(&q);
1345 }
1346
1347 /*
1348 ** For the given fully resolved UUID, renders a JSON object containing
1349 ** the fileeedit-editable files in that checkin:
1350 **
1351 ** {
1352 ** checkin: UUID,
1353 ** editableFiles: [ filename1, ... filenameN ]
1354 ** }
1355 **
1356 ** They are sorted by name using filename_collation().
1357 */
1358 static void fileedit_render_checkin_files(const char * zFullUuid){
1359 Blob sql = empty_blob;
1360 Stmt q = empty_Stmt;
1361 int i = 0;
1362
1363 CX("{\"checkin\":%!j,"
1364 "\"editableFiles\":[", zFullUuid);
1365 blob_append_sql(&sql, "SELECT filename FROM files_of_checkin(%Q) "
1366 "ORDER BY filename %s",
1367 zFullUuid, filename_collation());
1368 db_prepare_blob(&q, &sql);
1369 while( SQLITE_ROW==db_step(&q) ){
1370 const char * zFilename = db_column_text(&q, 0);
1371 if(fileedit_is_editable(zFilename)){
1372 if(i++){
1373 CX(",");
1374 }
1375 CX("%!j", zFilename);
1376 }
1377 }
1378 db_finalize(&q);
1379 CX("]}");
1380 }
1381
1382 /*
1383 ** AJAX route /fileedit?ajax=filelist
1384 **
1385 ** Fetches a JSON-format list of leaves and/or filenames for use in
@@ -1318,66 +1404,27 @@
1404 ** }
1405 **
1406 ** On error it produces a JSON response as documented for
1407 ** ajax_route_error().
1408 */
1409 static void fileedit_ajax_filelist(){
1410 const char * zCi = PD("checkin",P("ci"));
 
 
 
1411
1412 if(!ajax_route_bootstrap(1,0)){
1413 return;
1414 }
1415 cgi_set_content_type("application/json");
1416 if(zCi!=0){
1417 char * zCiFull = 0;
1418 if(0==fileedit_ajax_setup_filerev(zCi, &zCiFull, 0, 0, 0)){
 
1419 /* Error already reported */
1420 return;
1421 }
1422 fileedit_render_checkin_files(zCiFull);
1423 fossil_free(zCiFull);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1424 }else if(P("leaves")!=0){
1425 fileedit_render_leaves_list(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1426 }else{
1427 ajax_route_error(500, "Unhandled URL argument.");
1428 }
1429 }
1430
@@ -1509,24 +1556,14 @@
1556 ** an internal implementation detail and may change with any given
1557 ** build of this code. An unknown "name" value triggers an error, as
1558 ** documented for ajax_route_error().
1559 */
1560 void fileedit_page(void){
 
 
 
 
1561 const char * zFileMime = 0; /* File mime type guess */
1562 CheckinMiniInfo cimi; /* Checkin state */
1563 int previewRenderMode = AJAX_RENDER_GUESS; /* preview mode */
1564 Blob err = empty_blob; /* Error report */
 
 
 
 
 
 
1565 const char *zAjax = P("name"); /* Name of AJAX route for
1566 sub-dispatching. */
1567
1568 /* Allow no access to this page without check-in privilege */
1569 login_check_credentials();
@@ -1590,14 +1627,11 @@
1627 ** transaction cleanly.
1628 */
1629 {
1630 int isMissingArg = 0;
1631 if(fileedit_setup_cimi_from_p(&cimi, &err, &isMissingArg)==0){
1632 assert(cimi.zFilename);
 
 
 
1633 zFileMime = mimetype_from_name(cimi.zFilename);
1634 }else if(isMissingArg!=0){
1635 /* Squelch these startup warnings - they're non-fatal now but
1636 ** used to be fatal. */
1637 blob_reset(&err);
@@ -1932,40 +1966,10 @@
1966 "file/check-in combination are discarded.</li>");
1967 CX("</ul>");
1968 }
1969 CX("</div>"/*#fileedit-tab-help*/);
1970
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1971 builtin_request_js("sbsdiff.js");
1972 style_emit_fossil_js_apis(0, "fetch", "dom", "tabs", "confirmer",
1973 "storage", 0);
1974 builtin_fulfill_js_requests();
1975 /*
@@ -1977,19 +1981,66 @@
1981 ** the JS multiple times.
1982 */
1983 ajax_emit_js_preview_modes(1);
1984 builtin_request_js("fossil.page.fileedit.js");
1985 builtin_fulfill_js_requests();
1986 {
1987 /* Dynamically populate the editor, display any error in the err
1988 ** blob, and/or switch to tab #0, where the file selector
1989 ** lives. The extra C scopes here correspond to JS-level scopes,
1990 ** to improve grokability. */
1991 style_emit_script_tag(0,0);
1992 CX("\n(function(){\n");
1993 CX("try{\n");
1994 {
1995 char * zFirstLeafUuid = 0;
1996 CX("fossil.config['fileedit-glob'] = ");
1997 glob_render_json_to_cgi(fileedit_glob());
1998 CX(";\n");
1999 if(blob_size(&err)>0){
2000 CX("fossil.error(%!j);\n", blob_str(&err));
2001 }
2002 /* Populate the page with the current leaves and, if available,
2003 the selected checkin's file list, to save 1 or 2 XHR requests
2004 at startup. That makes this page uncacheable, but compressed
2005 delivery of this page is currently less than 6k. */
2006 CX("fossil.page.initialLeaves = ");
2007 fileedit_render_leaves_list(cimi.zParentUuid ? 0 : &zFirstLeafUuid);
2008 CX(";\n");
2009 if(zFirstLeafUuid){
2010 assert(!cimi.zParentUuid);
2011 cimi.zParentUuid = zFirstLeafUuid;
2012 zFirstLeafUuid = 0;
2013 }
2014 if(cimi.zParentUuid){
2015 CX("fossil.page.initialFiles = ");
2016 fileedit_render_checkin_files(cimi.zParentUuid);
2017 CX(";\n");
2018 }
2019 CX("fossil.onPageLoad(function(){\n");
2020 {
2021 if(blob_size(&err)>0){
2022 CX("fossil.error(%!j);\n",
2023 blob_str(&err));
2024 CX("fossil.page.tabs.switchToTab(0);\n");
2025 }
2026 if(cimi.zParentUuid && cimi.zFilename){
2027 CX("fossil.page.loadFile(%!j,%!j);\n",
2028 cimi.zFilename, cimi.zParentUuid)
2029 /* Reminder we cannot embed the JSON-format
2030 content of the file here because if it contains
2031 a SCRIPT tag then it will break the whole page. */;
2032 }
2033 }
2034 CX("});\n")/*fossil.onPageLoad()*/;
2035 }
2036 CX("}catch(e){"
2037 "fossil.error(e); console.error('Exception:',e);"
2038 "}\n");
2039 CX("})();")/*anonymous function*/;
 
2040 style_emit_script_tag(1,0);
2041 }
2042 blob_reset(&err);
2043 CheckinMiniInfo_cleanup(&cimi);
2044 db_end_transaction(0);
2045 style_footer();
2046 }
2047
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -285,30 +285,44 @@
285285
this.e.selectCi,
286286
this.e.selectFiles
287287
),"Loading leaves...");
288288
D.disable(this.e.btnLoadFile, this.e.selectFiles, this.e.selectCi);
289289
const self = this;
290
- F.fetch('fileedit/filelist',{
291
- urlParams:'leaves',
292
- responseType: 'json',
293
- onload: function(list){
294
- D.append(D.clearElement(self.e.ciListLabel),
295
- "Open leaves (newest first):");
296
- self.cache.checkins = list;
297
- D.clearElement(D.enable(self.e.selectCi));
298
- let loadThisOne;
299
- list.forEach(function(o,n){
300
- if(!n) loadThisOne = o;
301
- self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch;
302
- D.option(self.e.selectCi, o.checkin,
303
- o.timestamp+' ['+o.branch+']: '
304
- +F.hashDigits(o.checkin));
305
- });
306
- F.storage.setJSON(self.cache.branchKey, self.cache.branchNames);
307
- self.loadFiles(loadThisOne ? loadThisOne.checkin : false);
308
- }
309
- });
290
+ const onload = function(list){
291
+ D.append(D.clearElement(self.e.ciListLabel),
292
+ "Open leaves (newest first):");
293
+ self.cache.checkins = list;
294
+ D.clearElement(D.enable(self.e.selectCi));
295
+ let loadThisOne = P.initialFiles/*possibly injected at page-load time*/;
296
+ if(loadThisOne){
297
+ self.cache.files[loadThisOne.checkin] = loadThisOne;
298
+ delete P.initialFiles;
299
+ }
300
+ list.forEach(function(o,n){
301
+ if(!n && !loadThisOne) loadThisOne = o;
302
+ self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch;
303
+ D.option(self.e.selectCi, o.checkin,
304
+ o.timestamp+' ['+o.branch+']: '
305
+ +F.hashDigits(o.checkin));
306
+ });
307
+ F.storage.setJSON(self.cache.branchKey, self.cache.branchNames);
308
+ if(loadThisOne){
309
+ self.e.selectCi.value = loadThisOne.checkin;
310
+ }
311
+ self.loadFiles(loadThisOne ? loadThisOne.checkin : false);
312
+ };
313
+ if(P.initialLeaves/*injected at page-load time.*/){
314
+ const lv = P.initialLeaves;
315
+ delete P.initialLeaves;
316
+ onload(lv);
317
+ }else{
318
+ F.fetch('fileedit/filelist',{
319
+ urlParams:'leaves',
320
+ responseType: 'json',
321
+ onload: onload
322
+ });
323
+ }
310324
},
311325
/**
312326
Loads the file list for the given checkin UUID. It uses a
313327
cached copy on subsequent calls for the same UUID. If passed a
314328
falsy value, it instead clears and disables the file selection
@@ -411,11 +425,11 @@
411425
);
412426
if(F.config['fileedit-glob']){
413427
D.append(
414428
this.e.container,
415429
D.append(
416
- D.div(),
430
+ D.span(),
417431
D.append(D.code(),"fileedit-glob"),
418432
" config setting = ",
419433
D.append(D.code(), JSON.stringify(F.config['fileedit-glob']))
420434
)
421435
);
422436
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -285,30 +285,44 @@
285 this.e.selectCi,
286 this.e.selectFiles
287 ),"Loading leaves...");
288 D.disable(this.e.btnLoadFile, this.e.selectFiles, this.e.selectCi);
289 const self = this;
290 F.fetch('fileedit/filelist',{
291 urlParams:'leaves',
292 responseType: 'json',
293 onload: function(list){
294 D.append(D.clearElement(self.e.ciListLabel),
295 "Open leaves (newest first):");
296 self.cache.checkins = list;
297 D.clearElement(D.enable(self.e.selectCi));
298 let loadThisOne;
299 list.forEach(function(o,n){
300 if(!n) loadThisOne = o;
301 self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch;
302 D.option(self.e.selectCi, o.checkin,
303 o.timestamp+' ['+o.branch+']: '
304 +F.hashDigits(o.checkin));
305 });
306 F.storage.setJSON(self.cache.branchKey, self.cache.branchNames);
307 self.loadFiles(loadThisOne ? loadThisOne.checkin : false);
308 }
309 });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310 },
311 /**
312 Loads the file list for the given checkin UUID. It uses a
313 cached copy on subsequent calls for the same UUID. If passed a
314 falsy value, it instead clears and disables the file selection
@@ -411,11 +425,11 @@
411 );
412 if(F.config['fileedit-glob']){
413 D.append(
414 this.e.container,
415 D.append(
416 D.div(),
417 D.append(D.code(),"fileedit-glob"),
418 " config setting = ",
419 D.append(D.code(), JSON.stringify(F.config['fileedit-glob']))
420 )
421 );
422
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -285,30 +285,44 @@
285 this.e.selectCi,
286 this.e.selectFiles
287 ),"Loading leaves...");
288 D.disable(this.e.btnLoadFile, this.e.selectFiles, this.e.selectCi);
289 const self = this;
290 const onload = function(list){
291 D.append(D.clearElement(self.e.ciListLabel),
292 "Open leaves (newest first):");
293 self.cache.checkins = list;
294 D.clearElement(D.enable(self.e.selectCi));
295 let loadThisOne = P.initialFiles/*possibly injected at page-load time*/;
296 if(loadThisOne){
297 self.cache.files[loadThisOne.checkin] = loadThisOne;
298 delete P.initialFiles;
299 }
300 list.forEach(function(o,n){
301 if(!n && !loadThisOne) loadThisOne = o;
302 self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch;
303 D.option(self.e.selectCi, o.checkin,
304 o.timestamp+' ['+o.branch+']: '
305 +F.hashDigits(o.checkin));
306 });
307 F.storage.setJSON(self.cache.branchKey, self.cache.branchNames);
308 if(loadThisOne){
309 self.e.selectCi.value = loadThisOne.checkin;
310 }
311 self.loadFiles(loadThisOne ? loadThisOne.checkin : false);
312 };
313 if(P.initialLeaves/*injected at page-load time.*/){
314 const lv = P.initialLeaves;
315 delete P.initialLeaves;
316 onload(lv);
317 }else{
318 F.fetch('fileedit/filelist',{
319 urlParams:'leaves',
320 responseType: 'json',
321 onload: onload
322 });
323 }
324 },
325 /**
326 Loads the file list for the given checkin UUID. It uses a
327 cached copy on subsequent calls for the same UUID. If passed a
328 falsy value, it instead clears and disables the file selection
@@ -411,11 +425,11 @@
425 );
426 if(F.config['fileedit-glob']){
427 D.append(
428 this.e.container,
429 D.append(
430 D.span(),
431 D.append(D.code(),"fileedit-glob"),
432 " config setting = ",
433 D.append(D.code(), JSON.stringify(F.config['fileedit-glob']))
434 )
435 );
436
+16 -1
--- src/glob.c
+++ src/glob.c
@@ -170,11 +170,11 @@
170170
** Appends the given glob to the given buffer in the form of a
171171
** JS/JSON-compatible array. It requires that pDest have been
172172
** initialized. If pGlob is NULL or empty it emits [] (an empty
173173
** array).
174174
*/
175
-void glob_render_as_json(Glob *pGlob, Blob *pDest){
175
+void glob_render_json_to_blob(Glob *pGlob, Blob *pDest){
176176
int i = 0;
177177
blob_append(pDest, "[", 1);
178178
for( ; pGlob && i < pGlob->nPattern; ++i ){
179179
if(i){
180180
blob_append(pDest, ",", 1);
@@ -181,10 +181,25 @@
181181
}
182182
blob_appendf(pDest, "%!j", pGlob->azPattern[i]);
183183
}
184184
blob_append(pDest, "]", 1);
185185
}
186
+/*
187
+** Functionally equivalent to glob_render_json_to_blob()
188
+** but outputs via cgi_print().
189
+*/
190
+void glob_render_json_to_cgi(Glob *pGlob){
191
+ int i = 0;
192
+ CX("[");
193
+ for( ; pGlob && i < pGlob->nPattern; ++i ){
194
+ if(i){
195
+ CX(",");
196
+ }
197
+ CX("%!j", pGlob->azPattern[i]);
198
+ }
199
+ CX("]");
200
+}
186201
187202
/*
188203
** COMMAND: test-glob
189204
**
190205
** Usage: %fossil test-glob PATTERN STRING...
191206
--- src/glob.c
+++ src/glob.c
@@ -170,11 +170,11 @@
170 ** Appends the given glob to the given buffer in the form of a
171 ** JS/JSON-compatible array. It requires that pDest have been
172 ** initialized. If pGlob is NULL or empty it emits [] (an empty
173 ** array).
174 */
175 void glob_render_as_json(Glob *pGlob, Blob *pDest){
176 int i = 0;
177 blob_append(pDest, "[", 1);
178 for( ; pGlob && i < pGlob->nPattern; ++i ){
179 if(i){
180 blob_append(pDest, ",", 1);
@@ -181,10 +181,25 @@
181 }
182 blob_appendf(pDest, "%!j", pGlob->azPattern[i]);
183 }
184 blob_append(pDest, "]", 1);
185 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
187 /*
188 ** COMMAND: test-glob
189 **
190 ** Usage: %fossil test-glob PATTERN STRING...
191
--- src/glob.c
+++ src/glob.c
@@ -170,11 +170,11 @@
170 ** Appends the given glob to the given buffer in the form of a
171 ** JS/JSON-compatible array. It requires that pDest have been
172 ** initialized. If pGlob is NULL or empty it emits [] (an empty
173 ** array).
174 */
175 void glob_render_json_to_blob(Glob *pGlob, Blob *pDest){
176 int i = 0;
177 blob_append(pDest, "[", 1);
178 for( ; pGlob && i < pGlob->nPattern; ++i ){
179 if(i){
180 blob_append(pDest, ",", 1);
@@ -181,10 +181,25 @@
181 }
182 blob_appendf(pDest, "%!j", pGlob->azPattern[i]);
183 }
184 blob_append(pDest, "]", 1);
185 }
186 /*
187 ** Functionally equivalent to glob_render_json_to_blob()
188 ** but outputs via cgi_print().
189 */
190 void glob_render_json_to_cgi(Glob *pGlob){
191 int i = 0;
192 CX("[");
193 for( ; pGlob && i < pGlob->nPattern; ++i ){
194 if(i){
195 CX(",");
196 }
197 CX("%!j", pGlob->azPattern[i]);
198 }
199 CX("]");
200 }
201
202 /*
203 ** COMMAND: test-glob
204 **
205 ** Usage: %fossil test-glob PATTERN STRING...
206

Keyboard Shortcuts

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