Fossil SCM

fossil-scm / src / bundle.c
Blame History Raw 800 lines
1
/*
2
** Copyright (c) 2014 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used to implement and manage a "bundle" file.
19
*/
20
#include "config.h"
21
#include "bundle.h"
22
#include <assert.h>
23
24
/*
25
** SQL code used to initialize the schema of a bundle.
26
**
27
** The bblob.delta field can be an integer, a text string, or NULL.
28
** If an integer, then the corresponding blobid is the delta basis.
29
** If a text string, then that string is a SHA1 hash for the delta
30
** basis, which is presumably in the main repository. If NULL, then
31
** data contains content without delta compression.
32
*/
33
static const char zBundleInit[] =
34
@ CREATE TABLE IF NOT EXISTS "%w".bconfig(
35
@ bcname TEXT,
36
@ bcvalue ANY
37
@ );
38
@ CREATE TABLE IF NOT EXISTS "%w".bblob(
39
@ blobid INTEGER PRIMARY KEY, -- Blob ID
40
@ uuid TEXT NOT NULL, -- hash of expanded blob
41
@ sz INT NOT NULL, -- Size of blob after expansion
42
@ delta ANY, -- Delta compression basis, or NULL
43
@ notes TEXT, -- Description of content
44
@ data BLOB -- compressed content
45
@ );
46
;
47
48
/*
49
** Attach a bundle file to the current database connection using the
50
** attachment name zBName.
51
*/
52
static void bundle_attach_file(
53
const char *zFile, /* Name of the file that contains the bundle */
54
const char *zBName, /* Attachment name */
55
int doInit /* Initialize a new bundle, if true */
56
){
57
int rc;
58
char *zErrMsg = 0;
59
char *zSql;
60
if( !doInit && file_size(zFile, ExtFILE)<0 ){
61
fossil_fatal("no such file: %s", zFile);
62
}
63
assert( g.db );
64
zSql = sqlite3_mprintf("ATTACH %Q AS %Q", zFile, zBName);
65
if( zSql==0 ) fossil_fatal("out of memory");
66
rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg);
67
sqlite3_free(zSql);
68
if( rc!=SQLITE_OK || zErrMsg ){
69
if( zErrMsg==0 ) zErrMsg = (char*)sqlite3_errmsg(g.db);
70
fossil_fatal("not a valid bundle: %s", zFile);
71
}
72
if( doInit ){
73
db_multi_exec(zBundleInit /*works-like:"%w%w"*/, zBName, zBName);
74
}else{
75
sqlite3_stmt *pStmt;
76
zSql = sqlite3_mprintf("SELECT bcname, bcvalue"
77
" FROM \"%w\".bconfig", zBName);
78
if( zSql==0 ) fossil_fatal("out of memory");
79
rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, 0);
80
if( rc ) fossil_fatal("not a valid bundle: %s", zFile);
81
sqlite3_free(zSql);
82
sqlite3_finalize(pStmt);
83
zSql = sqlite3_mprintf("SELECT blobid, uuid, sz, delta, notes, data"
84
" FROM \"%w\".bblob", zBName);
85
if( zSql==0 ) fossil_fatal("out of memory");
86
rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, 0);
87
if( rc ) fossil_fatal("not a valid bundle: %s", zFile);
88
sqlite3_free(zSql);
89
sqlite3_finalize(pStmt);
90
}
91
}
92
93
/*
94
** fossil bundle ls BUNDLE ?OPTIONS?
95
**
96
** Display the content of a bundle in human-readable form.
97
*/
98
static void bundle_ls_cmd(void){
99
Stmt q;
100
sqlite3_int64 sumSz = 0;
101
sqlite3_int64 sumLen = 0;
102
int bDetails = find_option("details","l",0)!=0;
103
verify_all_options();
104
if( g.argc!=4 ) usage("ls BUNDLE ?OPTIONS?");
105
bundle_attach_file(g.argv[3], "b1", 0);
106
db_prepare(&q,
107
"SELECT bcname, bcvalue FROM bconfig"
108
" WHERE typeof(bcvalue)='text'"
109
" AND bcvalue NOT GLOB char(0x2a,0x0a,0x2a);"
110
);
111
while( db_step(&q)==SQLITE_ROW ){
112
fossil_print("%s: %s\n", db_column_text(&q,0), db_column_text(&q,1));
113
}
114
db_finalize(&q);
115
fossil_print("%.78c\n",'-');
116
if( bDetails ){
117
db_prepare(&q,
118
"SELECT blobid, substr(uuid,1,10), coalesce(substr(delta,1,10),''),"
119
" sz, octet_length(data), notes"
120
" FROM bblob"
121
);
122
while( db_step(&q)==SQLITE_ROW ){
123
fossil_print("%4d %10s %10s %8d %8d %s\n",
124
db_column_int(&q,0),
125
db_column_text(&q,1),
126
db_column_text(&q,2),
127
db_column_int(&q,3),
128
db_column_int(&q,4),
129
db_column_text(&q,5));
130
sumSz += db_column_int(&q,3);
131
sumLen += db_column_int(&q,4);
132
}
133
db_finalize(&q);
134
fossil_print("%27s %8lld %8lld\n", "Total:", sumSz, sumLen);
135
}else{
136
db_prepare(&q,
137
"SELECT substr(uuid,1,16), notes FROM bblob"
138
);
139
while( db_step(&q)==SQLITE_ROW ){
140
fossil_print("%16s %s\n",
141
db_column_text(&q,0),
142
db_column_text(&q,1));
143
}
144
db_finalize(&q);
145
}
146
}
147
148
/*
149
** Implement the "fossil bundle append BUNDLE FILE..." command. Add
150
** the named files into the BUNDLE. Create the BUNDLE if it does not
151
** already exist.
152
*/
153
static void bundle_append_cmd(void){
154
Blob content, hash;
155
int i;
156
Stmt q;
157
158
verify_all_options();
159
bundle_attach_file(g.argv[3], "b1", 1);
160
db_prepare(&q,
161
"INSERT INTO bblob(blobid, uuid, sz, delta, data, notes) "
162
"VALUES(NULL, $uuid, $sz, NULL, $data, $filename)");
163
db_begin_transaction();
164
for(i=4; i<g.argc; i++){
165
int sz;
166
blob_read_from_file(&content, g.argv[i], ExtFILE);
167
sz = blob_size(&content);
168
sha1sum_blob(&content, &hash);
169
blob_compress(&content, &content);
170
db_bind_text(&q, "$uuid", blob_str(&hash));
171
db_bind_int(&q, "$sz", sz);
172
db_bind_blob(&q, "$data", &content);
173
db_bind_text(&q, "$filename", g.argv[i]);
174
db_step(&q);
175
db_reset(&q);
176
blob_reset(&content);
177
blob_reset(&hash);
178
}
179
db_end_transaction(0);
180
db_finalize(&q);
181
}
182
183
/*
184
** Identify a subsection of the check-in tree using command-line switches.
185
** There must be one of the following switch available:
186
**
187
** --branch BRANCHNAME All check-ins on the most recent
188
** instance of BRANCHNAME
189
** --from TAG1 [--to TAG2] Check-in TAG1 and all primary descendants
190
** up to and including TAG2
191
** --checkin TAG Check-in TAG only
192
**
193
** Store the RIDs for all applicable check-ins in the zTab table that
194
** should already exist. Invoke fossil_fatal() if any kind of error is
195
** seen.
196
*/
197
void subtree_from_arguments(const char *zTab){
198
const char *zBr;
199
const char *zFrom;
200
const char *zTo;
201
const char *zCkin;
202
int rid = 0, endRid;
203
204
zBr = find_option("branch",0,1);
205
zFrom = find_option("from",0,1);
206
zTo = find_option("to",0,1);
207
zCkin = find_option("checkin",0,1);
208
if( zCkin ){
209
if( zFrom ) fossil_fatal("cannot use both --checkin and --from");
210
if( zBr ) fossil_fatal("cannot use both --checkin and --branch");
211
rid = symbolic_name_to_rid(zCkin, "ci");
212
endRid = rid;
213
}else{
214
endRid = zTo ? name_to_typed_rid(zTo, "ci") : 0;
215
}
216
if( zFrom ){
217
rid = name_to_typed_rid(zFrom, "ci");
218
}else if( zBr ){
219
rid = name_to_typed_rid(zBr, "br");
220
}else if( zCkin==0 ){
221
fossil_fatal("need one of: --branch, --from, --checkin");
222
}
223
db_multi_exec("INSERT OR IGNORE INTO \"%w\" VALUES(%d)", zTab, rid);
224
if( rid!=endRid ){
225
Blob sql;
226
blob_zero(&sql);
227
blob_appendf(&sql,
228
"WITH RECURSIVE child(rid) AS (VALUES(%d) UNION ALL "
229
" SELECT cid FROM plink, child"
230
" WHERE plink.pid=child.rid"
231
" AND plink.isPrim", rid);
232
if( endRid>0 ){
233
double endTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d",
234
endRid);
235
blob_appendf(&sql,
236
" AND child.rid!=%d"
237
" AND (SELECT mtime FROM event WHERE objid=plink.cid)<=%.17g",
238
endRid, endTime
239
);
240
}
241
if( zBr ){
242
blob_appendf(&sql,
243
" AND EXISTS(SELECT 1 FROM tagxref"
244
" WHERE tagid=%d AND tagtype>0"
245
" AND value=%Q and rid=plink.cid)",
246
TAG_BRANCH, zBr);
247
}
248
blob_appendf(&sql, ") INSERT OR IGNORE INTO \"%w\" SELECT rid FROM child;",
249
zTab);
250
db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/);
251
}
252
}
253
254
/*
255
** COMMAND: test-subtree
256
**
257
** Usage: %fossil test-subtree ?OPTIONS?
258
**
259
** Show the subset of check-ins that match the supplied options. This
260
** command is used to test the subtree_from_options() subroutine in the
261
** implementation and does not really have any other practical use that
262
** we know of.
263
**
264
** Options:
265
** --branch BRANCH Include only check-ins on BRANCH
266
** --from TAG Start the subtree at TAG
267
** --to TAG End the subtree at TAG
268
** --checkin TAG The subtree is the single check-in TAG
269
** --all Include FILE and TAG artifacts
270
** --exclusive Include FILES exclusively on check-ins
271
*/
272
void test_subtree_cmd(void){
273
int bAll = find_option("all",0,0)!=0;
274
int bExcl = find_option("exclusive",0,0)!=0;
275
db_find_and_open_repository(0,0);
276
db_begin_transaction();
277
db_multi_exec("CREATE TEMP TABLE tobundle(rid INTEGER PRIMARY KEY);");
278
subtree_from_arguments("tobundle");
279
verify_all_options();
280
if( bAll ) find_checkin_associates("tobundle",bExcl);
281
describe_artifacts_to_stdout("IN tobundle", 0);
282
db_end_transaction(1);
283
}
284
285
/* fossil bundle export BUNDLE ?OPTIONS?
286
**
287
** OPTIONS:
288
** --branch BRANCH --from TAG --to TAG
289
** --checkin TAG
290
** --standalone
291
*/
292
static void bundle_export_cmd(void){
293
int bStandalone = find_option("standalone",0,0)!=0;
294
int mnToBundle; /* Minimum RID in the bundle */
295
Stmt q;
296
297
/* Decode the arguments (like --branch) that specify which artifacts
298
** should be in the bundle */
299
db_multi_exec("CREATE TEMP TABLE tobundle(rid INTEGER PRIMARY KEY);");
300
subtree_from_arguments("tobundle");
301
find_checkin_associates("tobundle", 0);
302
verify_all_options();
303
describe_artifacts("IN tobundle");
304
305
if( g.argc!=4 ) usage("export BUNDLE ?OPTIONS?");
306
/* Create the new bundle */
307
bundle_attach_file(g.argv[3], "b1", 1);
308
db_begin_transaction();
309
310
/* Add 'mtime' and 'project-code' entries to the bconfig table */
311
db_multi_exec(
312
"INSERT INTO bconfig(bcname,bcvalue)"
313
" VALUES('mtime',datetime('now'));"
314
);
315
db_multi_exec(
316
"INSERT INTO bconfig(bcname,bcvalue)"
317
" SELECT name, value FROM config"
318
" WHERE name IN ('project-code','parent-project-code');"
319
);
320
321
/* Directly copy content from the repository into the bundle as long
322
** as the repository content is a delta from some other artifact that
323
** is also in the bundle.
324
*/
325
db_multi_exec(
326
"REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes) "
327
" SELECT"
328
" tobundle.rid,"
329
" blob.uuid,"
330
" blob.size,"
331
" delta.srcid,"
332
" blob.content,"
333
" (SELECT summary FROM description WHERE rid=blob.rid)"
334
" FROM tobundle, blob, delta"
335
" WHERE blob.rid=tobundle.rid"
336
" AND delta.rid=tobundle.rid"
337
" AND delta.srcid IN tobundle;"
338
);
339
340
/* For all the remaining artifacts, we need to construct their deltas
341
** manually.
342
*/
343
mnToBundle = db_int(0,"SELECT min(rid) FROM tobundle");
344
db_prepare(&q,
345
"SELECT rid FROM tobundle"
346
" WHERE rid NOT IN (SELECT blobid FROM bblob)"
347
" ORDER BY +rid;"
348
);
349
while( db_step(&q)==SQLITE_ROW ){
350
Blob content;
351
int rid = db_column_int(&q,0);
352
int deltaFrom = 0;
353
354
/* Get the raw, uncompressed content of the artifact into content */
355
content_get(rid, &content);
356
357
/* Try to find another artifact, not within the bundle, that is a
358
** plausible candidate for being a delta basis for the content. Set
359
** deltaFrom to the RID of that other artifact. Leave deltaFrom set
360
** to zero if the content should not be delta-compressed
361
*/
362
if( !bStandalone ){
363
if( db_exists("SELECT 1 FROM plink WHERE cid=%d",rid) ){
364
deltaFrom = db_int(0,
365
"SELECT max(cid) FROM plink"
366
" WHERE cid<%d", mnToBundle);
367
}else{
368
deltaFrom = db_int(0,
369
"SELECT max(fid) FROM mlink"
370
" WHERE fnid=(SELECT fnid FROM mlink WHERE fid=%d)"
371
" AND fid<%d", rid, mnToBundle);
372
}
373
}
374
375
/* Try to insert the artifact as a delta
376
*/
377
if( deltaFrom ){
378
Blob basis, delta;
379
content_get(deltaFrom, &basis);
380
blob_delta_create(&basis, &content, &delta);
381
if( blob_size(&delta)>0.9*blob_size(&content) ){
382
deltaFrom = 0;
383
}else{
384
Stmt ins;
385
blob_compress(&delta, &delta);
386
db_prepare(&ins,
387
"REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes)"
388
" SELECT %d, uuid, size, (SELECT uuid FROM blob WHERE rid=%d),"
389
" :delta, (SELECT summary FROM description WHERE rid=blob.rid)"
390
" FROM blob WHERE rid=%d", rid, deltaFrom, rid);
391
db_bind_blob(&ins, ":delta", &delta);
392
db_step(&ins);
393
db_finalize(&ins);
394
}
395
blob_reset(&basis);
396
blob_reset(&delta);
397
}
398
399
/* If unable to insert the artifact as a delta, insert full-text */
400
if( deltaFrom==0 ){
401
Stmt ins;
402
blob_compress(&content, &content);
403
db_prepare(&ins,
404
"REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes)"
405
" SELECT rid, uuid, size, NULL, :content,"
406
" (SELECT summary FROM description WHERE rid=blob.rid)"
407
" FROM blob WHERE rid=%d", rid);
408
db_bind_blob(&ins, ":content", &content);
409
db_step(&ins);
410
db_finalize(&ins);
411
}
412
blob_reset(&content);
413
}
414
db_finalize(&q);
415
416
db_end_transaction(0);
417
}
418
419
420
/*
421
** There is a TEMP table bix(blobid,delta) containing a set of purgeitems
422
** that need to be transferred to the BLOB table. This routine does
423
** all items that have srcid=iSrc. The pBasis blob holds the content
424
** of the source document if iSrc>0.
425
*/
426
static void bundle_import_elements(int iSrc, Blob *pBasis, int isPriv){
427
Stmt q;
428
static Bag busy;
429
assert( pBasis!=0 || iSrc==0 );
430
if( iSrc>0 ){
431
if( bag_find(&busy, iSrc) ){
432
fossil_fatal("delta loop while uncompressing bundle artifacts");
433
}
434
bag_insert(&busy, iSrc);
435
}
436
db_prepare(&q,
437
"SELECT uuid, data, bblob.delta, bix.blobid"
438
" FROM bix, bblob"
439
" WHERE bix.delta=%d"
440
" AND bix.blobid=bblob.blobid;",
441
iSrc
442
);
443
while( db_step(&q)==SQLITE_ROW ){
444
Blob h1, c1, c2;
445
int rid;
446
blob_zero(&h1);
447
db_column_blob(&q, 0, &h1);
448
blob_zero(&c1);
449
db_column_blob(&q, 1, &c1);
450
blob_uncompress(&c1, &c1);
451
blob_zero(&c2);
452
if( db_column_type(&q,2)==SQLITE_TEXT && db_column_bytes(&q,2)>=HNAME_MIN ){
453
Blob basis;
454
rid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q",
455
db_column_text(&q,2));
456
content_get(rid, &basis);
457
blob_delta_apply(&basis, &c1, &c2);
458
blob_reset(&basis);
459
blob_reset(&c1);
460
}else if( pBasis ){
461
blob_delta_apply(pBasis, &c1, &c2);
462
blob_reset(&c1);
463
}else{
464
c2 = c1;
465
}
466
if( hname_verify_hash(&c2, blob_buffer(&h1), blob_size(&h1))==0 ){
467
fossil_fatal("artifact hash error on %b", &h1);
468
}
469
rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv);
470
if( rid==0 ){
471
fossil_fatal("%s", g.zErrMsg);
472
}else{
473
if( !isPriv ) content_make_public(rid);
474
content_get(rid, &c1);
475
manifest_crosslink(rid, &c1, MC_NO_ERRORS);
476
db_multi_exec("INSERT INTO got(rid) VALUES(%d)",rid);
477
}
478
bundle_import_elements(db_column_int(&q,3), &c2, isPriv);
479
blob_reset(&c2);
480
}
481
db_finalize(&q);
482
if( iSrc>0 ) bag_remove(&busy, iSrc);
483
}
484
485
/*
486
** Extract an item from content from the bundle
487
*/
488
static void bundle_extract_item(
489
int blobid, /* ID of the item to extract */
490
Blob *pOut /* Write the content into this blob */
491
){
492
Stmt q;
493
Blob x, basis, h1;
494
static Bag busy;
495
496
db_prepare(&q, "SELECT uuid, delta, data FROM bblob"
497
" WHERE blobid=%d", blobid);
498
if( db_step(&q)!=SQLITE_ROW ){
499
db_finalize(&q);
500
fossil_fatal("no such item: %d", blobid);
501
}
502
if( bag_find(&busy, blobid) ) fossil_fatal("delta loop");
503
blob_zero(&x);
504
db_column_blob(&q, 2, &x);
505
blob_uncompress(&x, &x);
506
if( db_column_type(&q,1)==SQLITE_INTEGER ){
507
bundle_extract_item(db_column_int(&q,1), &basis);
508
blob_delta_apply(&basis, &x, pOut);
509
blob_reset(&basis);
510
blob_reset(&x);
511
}else if( db_column_type(&q,1)==SQLITE_TEXT ){
512
int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
513
db_column_text(&q,1));
514
if( rid==0 ){
515
fossil_fatal("cannot find delta basis %s", db_column_text(&q,1));
516
}
517
content_get(rid, &basis);
518
db_column_blob(&q, 2, &x);
519
blob_delta_apply(&basis, &x, pOut);
520
blob_reset(&basis);
521
blob_reset(&x);
522
}else{
523
*pOut = x;
524
}
525
blob_zero(&h1);
526
db_column_blob(&q, 0, &h1);
527
if( hname_verify_hash(pOut, blob_buffer(&h1), blob_size(&h1))==0 ){
528
fossil_fatal("incorrect hash for artifact %b", &h1);
529
}
530
blob_reset(&h1);
531
bag_remove(&busy, blobid);
532
db_finalize(&q);
533
}
534
535
/* fossil bundle cat BUNDLE HASH...
536
**
537
** Write elements of a bundle on standard output
538
*/
539
static void bundle_cat_cmd(void){
540
int i;
541
Blob x;
542
verify_all_options();
543
if( g.argc<5 ) usage("cat BUNDLE HASH...");
544
bundle_attach_file(g.argv[3], "b1", 0);
545
blob_zero(&x);
546
for(i=4; i<g.argc; i++){
547
int blobid = db_int(0,"SELECT blobid FROM bblob WHERE uuid LIKE '%q%%'",
548
g.argv[i]);
549
if( blobid==0 ){
550
fossil_fatal("no such artifact in bundle: %s", g.argv[i]);
551
}
552
bundle_extract_item(blobid, &x);
553
blob_write_to_file(&x, "-");
554
blob_reset(&x);
555
}
556
}
557
558
559
/* fossil bundle import BUNDLE ?OPTIONS?
560
**
561
** Attempt to import the changes contained in BUNDLE. Make the change
562
** private so that they do not sync.
563
**
564
** OPTIONS:
565
** --force Import even if the project-code does not match
566
** --publish Imported changes are not private
567
*/
568
static void bundle_import_cmd(void){
569
int forceFlag = find_option("force","f",0)!=0;
570
int isPriv = find_option("publish",0,0)==0;
571
char *zMissingDeltas;
572
verify_all_options();
573
if ( g.argc!=4 ) usage("import BUNDLE ?OPTIONS?");
574
bundle_attach_file(g.argv[3], "b1", 0);
575
576
/* Only import a bundle that was generated from a repo with the same
577
** project code, unless the --force flag is true */
578
if( !forceFlag ){
579
if( !db_exists("SELECT 1 FROM config, bconfig"
580
" WHERE config.name='project-code'"
581
" AND bconfig.bcname='project-code'"
582
" AND config.value=bconfig.bcvalue;")
583
){
584
fossil_fatal("project-code in the bundle does not match the "
585
"repository project code. (override with --force).");
586
}
587
}
588
589
/* If the bundle contains deltas with a basis that is external to the
590
** bundle and those external basis files are missing from the local
591
** repo, then the delta encodings cannot be decoded and the bundle cannot
592
** be extracted. */
593
zMissingDeltas = db_text(0,
594
"SELECT group_concat(substr(delta,1,10),' ')"
595
" FROM bblob"
596
" WHERE typeof(delta)='text' AND octet_length(delta)>=%d"
597
" AND NOT EXISTS(SELECT 1 FROM blob WHERE uuid=bblob.delta)",
598
HNAME_MIN);
599
if( zMissingDeltas && zMissingDeltas[0] ){
600
fossil_fatal("delta basis artifacts not found in repository: %s",
601
zMissingDeltas);
602
}
603
604
db_begin_transaction();
605
db_multi_exec(
606
"CREATE TEMP TABLE bix("
607
" blobid INTEGER PRIMARY KEY,"
608
" delta INTEGER"
609
");"
610
"CREATE INDEX bixdelta ON bix(delta);"
611
"INSERT INTO bix(blobid,delta)"
612
" SELECT blobid,"
613
" CASE WHEN typeof(delta)=='integer'"
614
" THEN delta ELSE 0 END"
615
" FROM bblob"
616
" WHERE NOT EXISTS(SELECT 1 FROM blob WHERE uuid=bblob.uuid AND size>=0);"
617
"CREATE TEMP TABLE got(rid INTEGER PRIMARY KEY ON CONFLICT IGNORE);"
618
);
619
manifest_crosslink_begin();
620
bundle_import_elements(0, 0, isPriv);
621
manifest_crosslink_end(0);
622
describe_artifacts_to_stdout("IN got", "Imported content:");
623
db_end_transaction(0);
624
}
625
626
/* fossil bundle purge BUNDLE
627
**
628
** Try to undo a prior "bundle import BUNDLE".
629
**
630
** If the --force option is omitted, then this will only work if
631
** there have been no check-ins or tags added that use the import.
632
**
633
** This routine never removes content that is not already in the bundle
634
** so the bundle serves as a backup. The purge can be undone using
635
** "fossil bundle import BUNDLE".
636
*/
637
static void bundle_purge_cmd(void){
638
int bForce = find_option("force",0,0)!=0;
639
int bTest = find_option("test",0,0)!=0; /* Undocumented --test option */
640
const char *zFile = g.argv[3];
641
verify_all_options();
642
if ( g.argc!=4 ) usage("purge BUNDLE ?OPTIONS?");
643
bundle_attach_file(zFile, "b1", 0);
644
db_begin_transaction();
645
646
/* Find all check-ins of the bundle */
647
db_multi_exec(
648
"CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY);"
649
"INSERT OR IGNORE INTO ok SELECT blob.rid FROM bblob, blob, plink"
650
" WHERE bblob.uuid=blob.uuid"
651
" AND plink.cid=blob.rid;"
652
);
653
654
/* Check to see if new check-ins have been committed to check-ins in
655
** the bundle. Do not allow the purge if that is true and if --force
656
** is omitted.
657
*/
658
if( !bForce ){
659
Stmt q;
660
int n = 0;
661
db_prepare(&q,
662
"SELECT cid FROM plink WHERE pid IN ok AND cid NOT IN ok"
663
);
664
while( db_step(&q)==SQLITE_ROW ){
665
whatis_rid(db_column_int(&q,0),0);
666
fossil_print("%.78c\n", '-');
667
n++;
668
}
669
db_finalize(&q);
670
if( n>0 ){
671
fossil_fatal("check-ins above are derived from check-ins in the bundle.");
672
}
673
}
674
675
/* Find all files associated with those check-ins that are used
676
** nowhere else. */
677
find_checkin_associates("ok", 1);
678
679
/* Check to see if any associated files are not in the bundle. Issue
680
** an error if there are any, unless --force is used.
681
*/
682
if( !bForce ){
683
db_multi_exec(
684
"CREATE TEMP TABLE err1(rid INTEGER PRIMARY KEY);"
685
"INSERT INTO err1 "
686
" SELECT blob.rid FROM ok CROSS JOIN blob"
687
" WHERE blob.rid=ok.rid"
688
" AND blob.uuid NOT IN (SELECT uuid FROM bblob);"
689
);
690
if( db_changes() ){
691
describe_artifacts_to_stdout("IN err1", 0);
692
fossil_fatal("artifacts above associated with bundle check-ins "
693
" are not in the bundle");
694
}else{
695
db_multi_exec("DROP TABLE err1;");
696
}
697
}
698
699
if( bTest ){
700
describe_artifacts_to_stdout(
701
"IN (SELECT blob.rid FROM ok, blob, bblob"
702
" WHERE blob.rid=ok.rid AND blob.uuid=bblob.uuid)",
703
"Purged artifacts found in the bundle:");
704
describe_artifacts_to_stdout(
705
"IN (SELECT blob.rid FROM ok, blob"
706
" WHERE blob.rid=ok.rid "
707
" AND blob.uuid NOT IN (SELECT uuid FROM bblob))",
708
"Purged artifacts NOT in the bundle:");
709
describe_artifacts_to_stdout(
710
"IN (SELECT blob.rid FROM bblob, blob"
711
" WHERE blob.uuid=bblob.uuid "
712
" AND blob.rid NOT IN ok)",
713
"Artifacts in the bundle but not purged:");
714
}else{
715
purge_artifact_list("ok",0,0);
716
}
717
db_end_transaction(0);
718
}
719
720
/*
721
** COMMAND: bundle*
722
**
723
** Usage: %fossil bundle SUBCOMMAND ARGS...
724
**
725
** > fossil bundle append BUNDLE FILE...
726
**
727
** Add files named on the command line to BUNDLE. This subcommand has
728
** little practical use and is mostly intended for testing.
729
**
730
** > fossil bundle cat BUNDLE HASH...
731
**
732
** Extract one or more artifacts from the bundle and write them
733
** consecutively on standard output. This subcommand was designed
734
** for testing and introspection of bundles and is not something
735
** commonly used.
736
**
737
** > fossil bundle export BUNDLE ?OPTIONS?
738
**
739
** Generate a new bundle, in the file named BUNDLE, that contains a
740
** subset of the check-ins in the repository (usually a single branch)
741
** described by the --branch, --from, --to, and/or --checkin options,
742
** at least one of which is required. If BUNDLE already exists, the
743
** specified content is added to the bundle.
744
**
745
** --branch BRANCH Package all check-ins on BRANCH
746
** --from TAG1 --to TAG2 Package check-ins between TAG1 and TAG2
747
** --checkin TAG Package the single check-in TAG
748
** --standalone Do no use delta-encoding against
749
** artifacts not in the bundle
750
**
751
** > fossil bundle extend BUNDLE
752
**
753
** The BUNDLE must already exist. This subcommand adds to the bundle
754
** any check-ins that are descendants of check-ins already in the bundle,
755
** and any tags that apply to artifacts in the bundle.
756
**
757
** > fossil bundle import BUNDLE ?--publish?
758
**
759
** Import all content from BUNDLE into the repository. By default, the
760
** imported files are private and will not sync. Use the --publish
761
** option to make the import public.
762
**
763
** > fossil bundle ls BUNDLE
764
**
765
** List the contents of BUNDLE on standard output
766
**
767
** > fossil bundle purge BUNDLE
768
**
769
** Remove from the repository all files that are used exclusively
770
** by check-ins in BUNDLE. This has the effect of undoing a
771
** "fossil bundle import".
772
**
773
** See also: [[publish]]
774
*/
775
void bundle_cmd(void){
776
const char *zSubcmd;
777
int n;
778
if( g.argc<4 ) usage("SUBCOMMAND BUNDLE ?OPTIONS?");
779
zSubcmd = g.argv[2];
780
db_find_and_open_repository(0,0);
781
n = (int)strlen(zSubcmd);
782
if( strncmp(zSubcmd, "append", n)==0 ){
783
bundle_append_cmd();
784
}else if( strncmp(zSubcmd, "cat", n)==0 ){
785
bundle_cat_cmd();
786
}else if( strncmp(zSubcmd, "export", n)==0 ){
787
bundle_export_cmd();
788
}else if( strncmp(zSubcmd, "extend", n)==0 ){
789
fossil_fatal("not yet implemented");
790
}else if( strncmp(zSubcmd, "import", n)==0 ){
791
bundle_import_cmd();
792
}else if( strncmp(zSubcmd, "ls", n)==0 ){
793
bundle_ls_cmd();
794
}else if( strncmp(zSubcmd, "purge", n)==0 ){
795
bundle_purge_cmd();
796
}else{
797
fossil_fatal("unknown subcommand for bundle: %s", zSubcmd);
798
}
799
}
800

Keyboard Shortcuts

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