|
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
|
|