Fossil SCM

fossil-scm / src / bundle.c
Blame History Raw 817 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 if( db_column_type(&q,2)==SQLITE_INTEGER ){
464
/* This is a delta against another bundle entry which was not
465
** imported because the repository already has it. Use the
466
** repository's copy. [forum:2629a1deaa] */
467
Blob basis;
468
rid = db_int(0,"SELECT rid FROM blob, bblob"
469
" WHERE bblob.blobid=%d"
470
" AND blob.uuid=bblob.uuid",
471
db_column_int(&q,2));
472
content_get(rid, &basis);
473
blob_delta_apply(&basis, &c1, &c2);
474
blob_reset(&basis);
475
blob_reset(&c1);
476
}else{
477
c2 = c1;
478
}
479
if( hname_verify_hash(&c2, blob_buffer(&h1), blob_size(&h1))==0 ){
480
fossil_fatal("artifact hash error on %b", &h1);
481
}
482
rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv);
483
if( rid==0 ){
484
fossil_fatal("%s", g.zErrMsg);
485
}else{
486
if( !isPriv ) content_make_public(rid);
487
content_get(rid, &c1);
488
manifest_crosslink(rid, &c1, MC_NO_ERRORS);
489
db_multi_exec("INSERT INTO got(rid) VALUES(%d)",rid);
490
}
491
bundle_import_elements(db_column_int(&q,3), &c2, isPriv);
492
blob_reset(&c2);
493
}
494
db_finalize(&q);
495
if( iSrc>0 ) bag_remove(&busy, iSrc);
496
}
497
498
/*
499
** Extract an item from content from the bundle
500
*/
501
static void bundle_extract_item(
502
int blobid, /* ID of the item to extract */
503
Blob *pOut /* Write the content into this blob */
504
){
505
Stmt q;
506
Blob x, basis, h1;
507
static Bag busy;
508
509
db_prepare(&q, "SELECT uuid, delta, data FROM bblob"
510
" WHERE blobid=%d", blobid);
511
if( db_step(&q)!=SQLITE_ROW ){
512
db_finalize(&q);
513
fossil_fatal("no such item: %d", blobid);
514
}
515
if( bag_find(&busy, blobid) ) fossil_fatal("delta loop");
516
blob_zero(&x);
517
db_column_blob(&q, 2, &x);
518
blob_uncompress(&x, &x);
519
if( db_column_type(&q,1)==SQLITE_INTEGER ){
520
bundle_extract_item(db_column_int(&q,1), &basis);
521
blob_delta_apply(&basis, &x, pOut);
522
blob_reset(&basis);
523
blob_reset(&x);
524
}else if( db_column_type(&q,1)==SQLITE_TEXT ){
525
int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
526
db_column_text(&q,1));
527
if( rid==0 ){
528
fossil_fatal("cannot find delta basis %s", db_column_text(&q,1));
529
}
530
content_get(rid, &basis);
531
db_column_blob(&q, 2, &x);
532
blob_delta_apply(&basis, &x, pOut);
533
blob_reset(&basis);
534
blob_reset(&x);
535
}else{
536
*pOut = x;
537
}
538
blob_zero(&h1);
539
db_column_blob(&q, 0, &h1);
540
if( hname_verify_hash(pOut, blob_buffer(&h1), blob_size(&h1))==0 ){
541
fossil_fatal("incorrect hash for artifact %b", &h1);
542
}
543
blob_reset(&h1);
544
bag_remove(&busy, blobid);
545
db_finalize(&q);
546
}
547
548
/* fossil bundle cat BUNDLE HASH...
549
**
550
** Write elements of a bundle on standard output
551
*/
552
static void bundle_cat_cmd(void){
553
int i;
554
Blob x;
555
verify_all_options();
556
if( g.argc<5 ) usage("cat BUNDLE HASH...");
557
bundle_attach_file(g.argv[3], "b1", 0);
558
blob_zero(&x);
559
for(i=4; i<g.argc; i++){
560
int blobid = db_int(0,"SELECT blobid FROM bblob WHERE uuid LIKE '%q%%'",
561
g.argv[i]);
562
if( blobid==0 ){
563
fossil_fatal("no such artifact in bundle: %s", g.argv[i]);
564
}
565
bundle_extract_item(blobid, &x);
566
blob_write_to_file(&x, "-");
567
blob_reset(&x);
568
}
569
}
570
571
572
/* fossil bundle import BUNDLE ?OPTIONS?
573
**
574
** Attempt to import the changes contained in BUNDLE. Make the change
575
** private so that they do not sync.
576
**
577
** OPTIONS:
578
** --force Import even if the project-code does not match
579
** --publish Imported changes are not private
580
*/
581
static void bundle_import_cmd(void){
582
int forceFlag = find_option("force","f",0)!=0;
583
int isPriv = find_option("publish",0,0)==0;
584
char *zMissingDeltas;
585
verify_all_options();
586
if ( g.argc!=4 ) usage("import BUNDLE ?OPTIONS?");
587
bundle_attach_file(g.argv[3], "b1", 0);
588
589
/* Only import a bundle that was generated from a repo with the same
590
** project code, unless the --force flag is true */
591
if( !forceFlag ){
592
if( !db_exists("SELECT 1 FROM config, bconfig"
593
" WHERE config.name='project-code'"
594
" AND bconfig.bcname='project-code'"
595
" AND config.value=bconfig.bcvalue;")
596
){
597
fossil_fatal("project-code in the bundle does not match the "
598
"repository project code. (override with --force).");
599
}
600
}
601
602
/* If the bundle contains deltas with a basis that is external to the
603
** bundle and those external basis files are missing from the local
604
** repo, then the delta encodings cannot be decoded and the bundle cannot
605
** be extracted. */
606
zMissingDeltas = db_text(0,
607
"SELECT group_concat(substr(delta,1,10),' ')"
608
" FROM bblob"
609
" WHERE typeof(delta)='text' AND octet_length(delta)>=%d"
610
" AND NOT EXISTS(SELECT 1 FROM blob WHERE uuid=bblob.delta)",
611
HNAME_MIN);
612
if( zMissingDeltas && zMissingDeltas[0] ){
613
fossil_fatal("delta basis artifacts not found in repository: %s",
614
zMissingDeltas);
615
}
616
617
db_begin_transaction();
618
db_multi_exec(
619
"CREATE TEMP TABLE bix("
620
" blobid INTEGER PRIMARY KEY,"
621
" delta INTEGER"
622
");"
623
"CREATE INDEX bixdelta ON bix(delta);"
624
"INSERT INTO bix(blobid,delta)"
625
" SELECT blobid,"
626
" CASE WHEN typeof(delta)=='integer'"
627
" THEN delta ELSE 0 END"
628
" FROM bblob"
629
" WHERE NOT EXISTS("
630
" SELECT 1 FROM blob WHERE uuid=bblob.uuid AND size>=0"
631
" );"
632
"UPDATE bix SET delta=0"
633
" WHERE delta>0 AND delta NOT IN (SELECT blobid FROM bix);"
634
"CREATE TEMP TABLE got(rid INTEGER PRIMARY KEY ON CONFLICT IGNORE);"
635
);
636
manifest_crosslink_begin();
637
bundle_import_elements(0, 0, isPriv);
638
manifest_crosslink_end(0);
639
describe_artifacts_to_stdout("IN got", "Imported content:");
640
db_end_transaction(0);
641
}
642
643
/* fossil bundle purge BUNDLE
644
**
645
** Try to undo a prior "bundle import BUNDLE".
646
**
647
** If the --force option is omitted, then this will only work if
648
** there have been no check-ins or tags added that use the import.
649
**
650
** This routine never removes content that is not already in the bundle
651
** so the bundle serves as a backup. The purge can be undone using
652
** "fossil bundle import BUNDLE".
653
*/
654
static void bundle_purge_cmd(void){
655
int bForce = find_option("force",0,0)!=0;
656
int bTest = find_option("test",0,0)!=0; /* Undocumented --test option */
657
const char *zFile = g.argv[3];
658
verify_all_options();
659
if ( g.argc!=4 ) usage("purge BUNDLE ?OPTIONS?");
660
bundle_attach_file(zFile, "b1", 0);
661
db_begin_transaction();
662
663
/* Find all check-ins of the bundle */
664
db_multi_exec(
665
"CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY);"
666
"INSERT OR IGNORE INTO ok SELECT blob.rid FROM bblob, blob, plink"
667
" WHERE bblob.uuid=blob.uuid"
668
" AND plink.cid=blob.rid;"
669
);
670
671
/* Check to see if new check-ins have been committed to check-ins in
672
** the bundle. Do not allow the purge if that is true and if --force
673
** is omitted.
674
*/
675
if( !bForce ){
676
Stmt q;
677
int n = 0;
678
db_prepare(&q,
679
"SELECT cid FROM plink WHERE pid IN ok AND cid NOT IN ok"
680
);
681
while( db_step(&q)==SQLITE_ROW ){
682
whatis_rid(db_column_int(&q,0),0,0);
683
fossil_print("%.78c\n", '-');
684
n++;
685
}
686
db_finalize(&q);
687
if( n>0 ){
688
fossil_fatal("check-ins above are derived from check-ins in the bundle.");
689
}
690
}
691
692
/* Find all files associated with those check-ins that are used
693
** nowhere else. */
694
find_checkin_associates("ok", 1);
695
696
/* Check to see if any associated files are not in the bundle. Issue
697
** an error if there are any, unless --force is used.
698
*/
699
if( !bForce ){
700
db_multi_exec(
701
"CREATE TEMP TABLE err1(rid INTEGER PRIMARY KEY);"
702
"INSERT INTO err1 "
703
" SELECT blob.rid FROM ok CROSS JOIN blob"
704
" WHERE blob.rid=ok.rid"
705
" AND blob.uuid NOT IN (SELECT uuid FROM bblob);"
706
);
707
if( db_changes() ){
708
describe_artifacts_to_stdout("IN err1", 0);
709
fossil_fatal("artifacts above associated with bundle check-ins "
710
" are not in the bundle");
711
}else{
712
db_multi_exec("DROP TABLE err1;");
713
}
714
}
715
716
if( bTest ){
717
describe_artifacts_to_stdout(
718
"IN (SELECT blob.rid FROM ok, blob, bblob"
719
" WHERE blob.rid=ok.rid AND blob.uuid=bblob.uuid)",
720
"Purged artifacts found in the bundle:");
721
describe_artifacts_to_stdout(
722
"IN (SELECT blob.rid FROM ok, blob"
723
" WHERE blob.rid=ok.rid "
724
" AND blob.uuid NOT IN (SELECT uuid FROM bblob))",
725
"Purged artifacts NOT in the bundle:");
726
describe_artifacts_to_stdout(
727
"IN (SELECT blob.rid FROM bblob, blob"
728
" WHERE blob.uuid=bblob.uuid "
729
" AND blob.rid NOT IN ok)",
730
"Artifacts in the bundle but not purged:");
731
}else{
732
purge_artifact_list("ok",0,0);
733
}
734
db_end_transaction(0);
735
}
736
737
/*
738
** COMMAND: bundle*
739
**
740
** Usage: %fossil bundle SUBCOMMAND ARGS...
741
**
742
** > fossil bundle append BUNDLE FILE...
743
**
744
** Add files named on the command line to BUNDLE. This subcommand has
745
** little practical use and is mostly intended for testing.
746
**
747
** > fossil bundle cat BUNDLE HASH...
748
**
749
** Extract one or more artifacts from the bundle and write them
750
** consecutively on standard output. This subcommand was designed
751
** for testing and introspection of bundles and is not something
752
** commonly used.
753
**
754
** > fossil bundle export BUNDLE ?OPTIONS?
755
**
756
** Generate a new bundle, in the file named BUNDLE, that contains a
757
** subset of the check-ins in the repository (usually a single branch)
758
** described by the --branch, --from, --to, and/or --checkin options,
759
** at least one of which is required. If BUNDLE already exists, the
760
** specified content is added to the bundle.
761
**
762
** --branch BRANCH Package all check-ins on BRANCH
763
** --from TAG1 --to TAG2 Package check-ins between TAG1 and TAG2
764
** --checkin TAG Package the single check-in TAG
765
** --standalone Do no use delta-encoding against
766
** artifacts not in the bundle
767
**
768
** > fossil bundle extend BUNDLE
769
**
770
** The BUNDLE must already exist. This subcommand adds to the bundle
771
** any check-ins that are descendants of check-ins already in the bundle,
772
** and any tags that apply to artifacts in the bundle.
773
**
774
** > fossil bundle import BUNDLE ?--publish?
775
**
776
** Import all content from BUNDLE into the repository. By default, the
777
** imported files are private and will not sync. Use the --publish
778
** option to make the import public.
779
**
780
** > fossil bundle ls BUNDLE
781
**
782
** List the contents of BUNDLE on standard output
783
**
784
** > fossil bundle purge BUNDLE
785
**
786
** Remove from the repository all files that are used exclusively
787
** by check-ins in BUNDLE. This has the effect of undoing a
788
** "fossil bundle import".
789
**
790
** See also: [[publish]]
791
*/
792
void bundle_cmd(void){
793
const char *zSubcmd;
794
int n;
795
if( g.argc<4 ) usage("SUBCOMMAND BUNDLE ?OPTIONS?");
796
zSubcmd = g.argv[2];
797
db_find_and_open_repository(0,0);
798
n = (int)strlen(zSubcmd);
799
if( strncmp(zSubcmd, "append", n)==0 ){
800
bundle_append_cmd();
801
}else if( strncmp(zSubcmd, "cat", n)==0 ){
802
bundle_cat_cmd();
803
}else if( strncmp(zSubcmd, "export", n)==0 ){
804
bundle_export_cmd();
805
}else if( strncmp(zSubcmd, "extend", n)==0 ){
806
fossil_fatal("not yet implemented");
807
}else if( strncmp(zSubcmd, "import", n)==0 ){
808
bundle_import_cmd();
809
}else if( strncmp(zSubcmd, "ls", n)==0 ){
810
bundle_ls_cmd();
811
}else if( strncmp(zSubcmd, "purge", n)==0 ){
812
bundle_purge_cmd();
813
}else{
814
fossil_fatal("unknown subcommand for bundle: %s", zSubcmd);
815
}
816
}
817

Keyboard Shortcuts

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