Fossil SCM

Implement the "fossil get" command.

drh 2025-10-15 17:15 get-command
Commit 552eee775ac6ad7ebaa9ccbe125c08e283ad17587172ac85693afbbf5ad4ca42
+3 -5
--- .editorconfig
+++ .editorconfig
@@ -1,14 +1,12 @@
1
-# EditorConfig (https://editorconfig.com) Configuration for Fossil
2
-#
3
-# Following https://fossil-scm.org/fossil/doc/trunk/www/style.wiki
4
-#
1
+# EditorConfig (https://editorconfig.com) configuration suitable for
2
+# "drh projects": fossil, althttpd, and their kin.
53
64
# Defaults for all files
75
[*]
86
end_of_line = lf
97
insert_final_newline = true
108
indent_style = space
119
indent_size = 2
1210
13
-[{Makefile,Makefile.*,*.mk}]
11
+[{Makefile,Makefile.*,*.make,*.make.in}]
1412
indent_style = tab
1513
--- .editorconfig
+++ .editorconfig
@@ -1,14 +1,12 @@
1 # EditorConfig (https://editorconfig.com) Configuration for Fossil
2 #
3 # Following https://fossil-scm.org/fossil/doc/trunk/www/style.wiki
4 #
5
6 # Defaults for all files
7 [*]
8 end_of_line = lf
9 insert_final_newline = true
10 indent_style = space
11 indent_size = 2
12
13 [{Makefile,Makefile.*,*.mk}]
14 indent_style = tab
15
--- .editorconfig
+++ .editorconfig
@@ -1,14 +1,12 @@
1 # EditorConfig (https://editorconfig.com) configuration suitable for
2 # "drh projects": fossil, althttpd, and their kin.
 
 
3
4 # Defaults for all files
5 [*]
6 end_of_line = lf
7 insert_final_newline = true
8 indent_style = space
9 indent_size = 2
10
11 [{Makefile,Makefile.*,*.make,*.make.in}]
12 indent_style = tab
13
+1 -1
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1
-2.28
1
+2.0
22
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.28
2
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.0
2
--- src/checkout.c
+++ src/checkout.c
@@ -19,10 +19,11 @@
1919
** from the local repository.
2020
*/
2121
#include "config.h"
2222
#include "checkout.h"
2323
#include <assert.h>
24
+#include <zlib.h>
2425
2526
/*
2627
** Check to see if there is an existing check-out that has been
2728
** modified. Return values:
2829
**
@@ -429,5 +430,183 @@
429430
}
430431
unlink_local_database(1);
431432
db_close(1);
432433
unlink_local_database(0);
433434
}
435
+
436
+
437
+/*
438
+** COMMAND: get
439
+**
440
+** Usage: %fossil get URL ?VERSION? ?OPTIONS?
441
+**
442
+** Download a single check-in from a remote repository named URL and
443
+** unpack all of the files locally. The check-in is identified by VERSION.
444
+**
445
+** URL can be a traditional URL like one of:
446
+**
447
+** * https://domain.com/project
448
+** * ssh://my-server/project.fossil
449
+** * file:/home/user/Fossils/project.fossil
450
+**
451
+** Or URL can be just the name of a local repository without the "file:"
452
+** prefix.
453
+**
454
+** This command works by downloading an SQL archive of the requested
455
+** check-in and then extracting all the files from the archive.
456
+**
457
+** Options:
458
+** --dest DIRECTORY Extract files into DIRECTORY. Use "--dest ."
459
+** to extract into the local directory.
460
+**
461
+** -f|--force Overwrite existing files
462
+**
463
+** --sqlar ARCHIVE Store the check-out in an SQL-archive rather
464
+** than unpacking them into separate files.
465
+**
466
+** -v|--verbose Show all files as they are extracted
467
+*/
468
+void get_cmd(void){
469
+ int forceFlag = find_option("force","f",0)!=0;
470
+ int bVerbose = find_option("verbose","v",0)!=0;
471
+ int bDebug = find_option("debug",0,0)!=0;
472
+ const char *zSqlArchive = find_option("sqlar",0,1);
473
+ const char *z;
474
+ char *zDest = 0; /* Where to store results */
475
+ const char *zUrl; /* Url to get */
476
+ const char *zVers; /* Version name to get */
477
+ unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
478
+ Blob in, out; /* I/O for the HTTP request */
479
+ Blob file; /* A file to extract */
480
+ sqlite3 *db; /* Database containing downloaded sqlar */
481
+ sqlite3_stmt *pStmt; /* Statement for querying the database */
482
+ int rc; /* Result of subroutine calls */
483
+
484
+ z = find_option("dest",0,1);
485
+ if( z ) zDest = fossil_strdup(z);
486
+ verify_all_options();
487
+ if( g.argc<3 || g.argc>4 ){
488
+ usage("get URL ?VERSION? ?OPTIONS?");
489
+ }
490
+ zUrl = g.argv[2];
491
+ zVers = g.argc==4 ? g.argv[3] : "trunk";
492
+
493
+ /* Parse the URL of the repository */
494
+ url_parse(zUrl, 0);
495
+
496
+ /* Construct an appropriate name for the destination directory */
497
+ if( zDest==0 ){
498
+ int i;
499
+ const char *zTail;
500
+ const char *zDot;
501
+ int n;
502
+ if( g.url.isFile ){
503
+ zTail = file_tail(g.url.name);
504
+ }else{
505
+ zTail = file_tail(g.url.path);
506
+ }
507
+ zDot = strchr(zTail,'.');
508
+ if( zDot==0 ) zDot = zTail+strlen(zTail);
509
+ n = (int)(zDot - zTail);
510
+ zDest = mprintf("%.*s-%s", n, zTail, zVers);
511
+ for(i=0; zDest[i]; i++){
512
+ char c = zDest[i];
513
+ if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){
514
+ zDest[i] = '-';
515
+ }
516
+ }
517
+ }
518
+ if( bDebug ){
519
+ fossil_print("dest = %s\n", zDest);
520
+ }
521
+
522
+ /* Error checking */
523
+ if( !forceFlag ){
524
+ if( zSqlArchive ){
525
+ if( file_isdir(zSqlArchive, ExtFILE)>0 ){
526
+ fossil_fatal("file already exists: \"%s\"", zSqlArchive);
527
+ }
528
+ }else if( file_isdir(zDest, ExtFILE)>0 ){
529
+ if( fossil_strcmp(zDest,".")==0 ){
530
+ if( file_directory_size(zDest,0,1) ){
531
+ fossil_fatal("current directory is not empty");
532
+ }
533
+ }else{
534
+ fossil_fatal("\"%s\" already exists", zDest);
535
+ }
536
+ }
537
+ }
538
+
539
+ /* Construct a subpath on the URL if necessary */
540
+ if( g.url.isSsh || g.url.isFile ){
541
+ g.url.subpath = mprintf("/sqlar?name=%t&r=%t", zDest, zVers);
542
+ }
543
+
544
+ if( bDebug ){
545
+ urlparse_print(0);
546
+ }
547
+
548
+ /* Fetch the ZIP archive for the requested check-in */
549
+ blob_init(&in, 0, 0);
550
+ blob_init(&out, 0, 0);
551
+ if( bDebug ) mHttpFlags |= HTTP_VERBOSE;
552
+ http_exchange(&in, &out, mHttpFlags, 4, 0);
553
+
554
+ if( zSqlArchive ){
555
+ blob_write_to_file(&out, zSqlArchive);
556
+ if( bVerbose ) fossil_print("%s\n", zSqlArchive);
557
+ return;
558
+ }
559
+
560
+ rc = sqlite3_open(":memory:", &db);
561
+ if( rc==SQLITE_OK ){
562
+ int sz = blob_size(&out);
563
+ rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz,
564
+ SQLITE_DESERIALIZE_READONLY);
565
+ }
566
+ if( rc!=SQLITE_OK ){
567
+ fossil_fatal("Cannot create an in-memory database: %s",
568
+ sqlite3_errmsg(db));
569
+ }
570
+ rc = sqlite3_prepare_v2(db,
571
+ "SELECT name, mode, sz, data"
572
+ " FROM sqlar", -1, &pStmt, 0);
573
+ if( rc!=0 ){
574
+ fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db));
575
+ }
576
+ blob_init(&file, 0, 0);
577
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
578
+ const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0);
579
+ int mode = sqlite3_column_int(pStmt, 1);
580
+ int sz = sqlite3_column_int(pStmt, 2);
581
+ if( mode & 0x4000 ){
582
+ /* A directory name */
583
+ file_mkdir(zFilename, ExtFILE, 1);
584
+ }else{
585
+ /* A file */
586
+ unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3);
587
+ unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3);
588
+ unsigned long int nOut2 = (unsigned long int)sz;
589
+ blob_resize(&file, sz);
590
+ if( nIn<sz ){
591
+ rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2,
592
+ inBuf, nIn);
593
+ if( rc!=Z_OK ){
594
+ fossil_fatal("Failed to uncompress file %s", zFilename);
595
+ }
596
+ }else{
597
+ memcpy(blob_buffer(&file), inBuf, sz);
598
+ }
599
+ blob_write_to_file(&file, zFilename);
600
+ if( mode & 0x40 ){
601
+ file_setexe(zFilename, 1);
602
+ }
603
+ blob_zero(&file);
604
+ if( bVerbose ){
605
+ fossil_print("%s\n", zFilename);
606
+ }
607
+ }
608
+ }
609
+ sqlite3_finalize(pStmt);
610
+ sqlite3_close(db);
611
+ blob_zero(&out);
612
+}
434613
--- src/checkout.c
+++ src/checkout.c
@@ -19,10 +19,11 @@
19 ** from the local repository.
20 */
21 #include "config.h"
22 #include "checkout.h"
23 #include <assert.h>
 
24
25 /*
26 ** Check to see if there is an existing check-out that has been
27 ** modified. Return values:
28 **
@@ -429,5 +430,183 @@
429 }
430 unlink_local_database(1);
431 db_close(1);
432 unlink_local_database(0);
433 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
--- src/checkout.c
+++ src/checkout.c
@@ -19,10 +19,11 @@
19 ** from the local repository.
20 */
21 #include "config.h"
22 #include "checkout.h"
23 #include <assert.h>
24 #include <zlib.h>
25
26 /*
27 ** Check to see if there is an existing check-out that has been
28 ** modified. Return values:
29 **
@@ -429,5 +430,183 @@
430 }
431 unlink_local_database(1);
432 db_close(1);
433 unlink_local_database(0);
434 }
435
436
437 /*
438 ** COMMAND: get
439 **
440 ** Usage: %fossil get URL ?VERSION? ?OPTIONS?
441 **
442 ** Download a single check-in from a remote repository named URL and
443 ** unpack all of the files locally. The check-in is identified by VERSION.
444 **
445 ** URL can be a traditional URL like one of:
446 **
447 ** * https://domain.com/project
448 ** * ssh://my-server/project.fossil
449 ** * file:/home/user/Fossils/project.fossil
450 **
451 ** Or URL can be just the name of a local repository without the "file:"
452 ** prefix.
453 **
454 ** This command works by downloading an SQL archive of the requested
455 ** check-in and then extracting all the files from the archive.
456 **
457 ** Options:
458 ** --dest DIRECTORY Extract files into DIRECTORY. Use "--dest ."
459 ** to extract into the local directory.
460 **
461 ** -f|--force Overwrite existing files
462 **
463 ** --sqlar ARCHIVE Store the check-out in an SQL-archive rather
464 ** than unpacking them into separate files.
465 **
466 ** -v|--verbose Show all files as they are extracted
467 */
468 void get_cmd(void){
469 int forceFlag = find_option("force","f",0)!=0;
470 int bVerbose = find_option("verbose","v",0)!=0;
471 int bDebug = find_option("debug",0,0)!=0;
472 const char *zSqlArchive = find_option("sqlar",0,1);
473 const char *z;
474 char *zDest = 0; /* Where to store results */
475 const char *zUrl; /* Url to get */
476 const char *zVers; /* Version name to get */
477 unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
478 Blob in, out; /* I/O for the HTTP request */
479 Blob file; /* A file to extract */
480 sqlite3 *db; /* Database containing downloaded sqlar */
481 sqlite3_stmt *pStmt; /* Statement for querying the database */
482 int rc; /* Result of subroutine calls */
483
484 z = find_option("dest",0,1);
485 if( z ) zDest = fossil_strdup(z);
486 verify_all_options();
487 if( g.argc<3 || g.argc>4 ){
488 usage("get URL ?VERSION? ?OPTIONS?");
489 }
490 zUrl = g.argv[2];
491 zVers = g.argc==4 ? g.argv[3] : "trunk";
492
493 /* Parse the URL of the repository */
494 url_parse(zUrl, 0);
495
496 /* Construct an appropriate name for the destination directory */
497 if( zDest==0 ){
498 int i;
499 const char *zTail;
500 const char *zDot;
501 int n;
502 if( g.url.isFile ){
503 zTail = file_tail(g.url.name);
504 }else{
505 zTail = file_tail(g.url.path);
506 }
507 zDot = strchr(zTail,'.');
508 if( zDot==0 ) zDot = zTail+strlen(zTail);
509 n = (int)(zDot - zTail);
510 zDest = mprintf("%.*s-%s", n, zTail, zVers);
511 for(i=0; zDest[i]; i++){
512 char c = zDest[i];
513 if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){
514 zDest[i] = '-';
515 }
516 }
517 }
518 if( bDebug ){
519 fossil_print("dest = %s\n", zDest);
520 }
521
522 /* Error checking */
523 if( !forceFlag ){
524 if( zSqlArchive ){
525 if( file_isdir(zSqlArchive, ExtFILE)>0 ){
526 fossil_fatal("file already exists: \"%s\"", zSqlArchive);
527 }
528 }else if( file_isdir(zDest, ExtFILE)>0 ){
529 if( fossil_strcmp(zDest,".")==0 ){
530 if( file_directory_size(zDest,0,1) ){
531 fossil_fatal("current directory is not empty");
532 }
533 }else{
534 fossil_fatal("\"%s\" already exists", zDest);
535 }
536 }
537 }
538
539 /* Construct a subpath on the URL if necessary */
540 if( g.url.isSsh || g.url.isFile ){
541 g.url.subpath = mprintf("/sqlar?name=%t&r=%t", zDest, zVers);
542 }
543
544 if( bDebug ){
545 urlparse_print(0);
546 }
547
548 /* Fetch the ZIP archive for the requested check-in */
549 blob_init(&in, 0, 0);
550 blob_init(&out, 0, 0);
551 if( bDebug ) mHttpFlags |= HTTP_VERBOSE;
552 http_exchange(&in, &out, mHttpFlags, 4, 0);
553
554 if( zSqlArchive ){
555 blob_write_to_file(&out, zSqlArchive);
556 if( bVerbose ) fossil_print("%s\n", zSqlArchive);
557 return;
558 }
559
560 rc = sqlite3_open(":memory:", &db);
561 if( rc==SQLITE_OK ){
562 int sz = blob_size(&out);
563 rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz,
564 SQLITE_DESERIALIZE_READONLY);
565 }
566 if( rc!=SQLITE_OK ){
567 fossil_fatal("Cannot create an in-memory database: %s",
568 sqlite3_errmsg(db));
569 }
570 rc = sqlite3_prepare_v2(db,
571 "SELECT name, mode, sz, data"
572 " FROM sqlar", -1, &pStmt, 0);
573 if( rc!=0 ){
574 fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db));
575 }
576 blob_init(&file, 0, 0);
577 while( sqlite3_step(pStmt)==SQLITE_ROW ){
578 const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0);
579 int mode = sqlite3_column_int(pStmt, 1);
580 int sz = sqlite3_column_int(pStmt, 2);
581 if( mode & 0x4000 ){
582 /* A directory name */
583 file_mkdir(zFilename, ExtFILE, 1);
584 }else{
585 /* A file */
586 unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3);
587 unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3);
588 unsigned long int nOut2 = (unsigned long int)sz;
589 blob_resize(&file, sz);
590 if( nIn<sz ){
591 rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2,
592 inBuf, nIn);
593 if( rc!=Z_OK ){
594 fossil_fatal("Failed to uncompress file %s", zFilename);
595 }
596 }else{
597 memcpy(blob_buffer(&file), inBuf, sz);
598 }
599 blob_write_to_file(&file, zFilename);
600 if( mode & 0x40 ){
601 file_setexe(zFilename, 1);
602 }
603 blob_zero(&file);
604 if( bVerbose ){
605 fossil_print("%s\n", zFilename);
606 }
607 }
608 }
609 sqlite3_finalize(pStmt);
610 sqlite3_close(db);
611 blob_zero(&out);
612 }
613
+6 -6
--- src/zip.c
+++ src/zip.c
@@ -406,16 +406,16 @@
406406
sqlite3_exec(p->db,
407407
"PRAGMA page_size=512;"
408408
"PRAGMA journal_mode = off;"
409409
"PRAGMA cache_spill = off;"
410410
"BEGIN;"
411
- "CREATE TABLE sqlar("
412
- "name TEXT PRIMARY KEY, -- name of the file\n"
413
- "mode INT, -- access permissions\n"
414
- "mtime INT, -- last modification time\n"
415
- "sz INT, -- original file size\n"
416
- "data BLOB -- compressed content\n"
411
+ "CREATE TABLE sqlar(\n"
412
+ " name TEXT PRIMARY KEY, -- name of the file\n"
413
+ " mode INT, -- access permissions\n"
414
+ " mtime INT, -- last modification time\n"
415
+ " sz INT, -- original file size\n"
416
+ " data BLOB -- compressed content\n"
417417
");", 0, 0, 0
418418
);
419419
sqlite3_prepare(p->db,
420420
"INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1,
421421
&p->pInsert, 0
422422
--- src/zip.c
+++ src/zip.c
@@ -406,16 +406,16 @@
406 sqlite3_exec(p->db,
407 "PRAGMA page_size=512;"
408 "PRAGMA journal_mode = off;"
409 "PRAGMA cache_spill = off;"
410 "BEGIN;"
411 "CREATE TABLE sqlar("
412 "name TEXT PRIMARY KEY, -- name of the file\n"
413 "mode INT, -- access permissions\n"
414 "mtime INT, -- last modification time\n"
415 "sz INT, -- original file size\n"
416 "data BLOB -- compressed content\n"
417 ");", 0, 0, 0
418 );
419 sqlite3_prepare(p->db,
420 "INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1,
421 &p->pInsert, 0
422
--- src/zip.c
+++ src/zip.c
@@ -406,16 +406,16 @@
406 sqlite3_exec(p->db,
407 "PRAGMA page_size=512;"
408 "PRAGMA journal_mode = off;"
409 "PRAGMA cache_spill = off;"
410 "BEGIN;"
411 "CREATE TABLE sqlar(\n"
412 " name TEXT PRIMARY KEY, -- name of the file\n"
413 " mode INT, -- access permissions\n"
414 " mtime INT, -- last modification time\n"
415 " sz INT, -- original file size\n"
416 " data BLOB -- compressed content\n"
417 ");", 0, 0, 0
418 );
419 sqlite3_prepare(p->db,
420 "INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1,
421 &p->pInsert, 0
422

Keyboard Shortcuts

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