|
1
|
/* |
|
2
|
** Copyright (c) 2007 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 check-out versions of the project |
|
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
|
** |
|
30
|
** 0: There is an existing check-out but it is unmodified |
|
31
|
** 1: There is a modified check-out - there are unsaved changes |
|
32
|
*/ |
|
33
|
int unsaved_changes(unsigned int cksigFlags){ |
|
34
|
int vid; |
|
35
|
db_must_be_within_tree(); |
|
36
|
vid = db_lget_int("checkout",0); |
|
37
|
vfile_check_signature(vid, cksigFlags|CKSIG_ENOTFILE); |
|
38
|
return db_exists("SELECT 1 FROM vfile WHERE chnged" |
|
39
|
" OR coalesce(origname!=pathname,0)"); |
|
40
|
} |
|
41
|
|
|
42
|
/* |
|
43
|
** Undo the current check-out. Unlink all files from the disk. |
|
44
|
** Clear the VFILE table. |
|
45
|
** |
|
46
|
** Also delete any directory that becomes empty as a result of deleting |
|
47
|
** files due to this operation, as long as that directory is not the |
|
48
|
** current working directory and is not on the empty-dirs list. |
|
49
|
*/ |
|
50
|
void uncheckout(int vid){ |
|
51
|
char *zPwd; |
|
52
|
if( vid<=0 ) return; |
|
53
|
sqlite3_create_function(g.db, "dirname",1,SQLITE_UTF8,0, |
|
54
|
file_dirname_sql_function, 0, 0); |
|
55
|
sqlite3_create_function(g.db, "unlink",1,SQLITE_UTF8|SQLITE_DIRECTONLY,0, |
|
56
|
file_delete_sql_function, 0, 0); |
|
57
|
sqlite3_create_function(g.db, "rmdir", 1, SQLITE_UTF8|SQLITE_DIRECTONLY, 0, |
|
58
|
file_rmdir_sql_function, 0, 0); |
|
59
|
db_multi_exec( |
|
60
|
"CREATE TEMP TABLE dir_to_delete(name TEXT %s PRIMARY KEY)WITHOUT ROWID", |
|
61
|
filename_collation() |
|
62
|
); |
|
63
|
db_multi_exec( |
|
64
|
"INSERT OR IGNORE INTO dir_to_delete(name)" |
|
65
|
" SELECT dirname(pathname) FROM vfile" |
|
66
|
" WHERE vid=%d AND mrid>0", |
|
67
|
vid |
|
68
|
); |
|
69
|
do{ |
|
70
|
db_multi_exec( |
|
71
|
"INSERT OR IGNORE INTO dir_to_delete(name)" |
|
72
|
" SELECT dirname(name) FROM dir_to_delete;" |
|
73
|
); |
|
74
|
}while( db_changes() ); |
|
75
|
db_multi_exec( |
|
76
|
"SELECT unlink(%Q||pathname) FROM vfile" |
|
77
|
" WHERE vid=%d AND mrid>0;", |
|
78
|
g.zLocalRoot, vid |
|
79
|
); |
|
80
|
ensure_empty_dirs_created(1); |
|
81
|
zPwd = file_getcwd(0,0); |
|
82
|
db_multi_exec( |
|
83
|
"SELECT rmdir(%Q||name) FROM dir_to_delete" |
|
84
|
" WHERE (%Q||name)<>%Q ORDER BY name DESC", |
|
85
|
g.zLocalRoot, g.zLocalRoot, zPwd |
|
86
|
); |
|
87
|
fossil_free(zPwd); |
|
88
|
db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); |
|
89
|
} |
|
90
|
|
|
91
|
|
|
92
|
/* |
|
93
|
** Given the abbreviated hash of a version, load the content of that |
|
94
|
** version in the VFILE table. Return the VID for the version. |
|
95
|
** |
|
96
|
** If anything goes wrong, panic. |
|
97
|
*/ |
|
98
|
int load_vfile(const char *zName, int forceMissingFlag){ |
|
99
|
Blob uuid; |
|
100
|
int vid; |
|
101
|
|
|
102
|
blob_init(&uuid, zName, -1); |
|
103
|
if( name_to_uuid(&uuid, 1, "ci") ){ |
|
104
|
fossil_fatal("%s", g.zErrMsg); |
|
105
|
} |
|
106
|
vid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &uuid); |
|
107
|
if( vid==0 ){ |
|
108
|
fossil_fatal("no such check-in: %s", g.argv[2]); |
|
109
|
} |
|
110
|
if( !is_a_version(vid) ){ |
|
111
|
fossil_fatal("object [%S] is not a check-in", blob_str(&uuid)); |
|
112
|
} |
|
113
|
if( load_vfile_from_rid(vid) && !forceMissingFlag ){ |
|
114
|
fossil_fatal("missing content, unable to check out"); |
|
115
|
}; |
|
116
|
return vid; |
|
117
|
} |
|
118
|
|
|
119
|
/* |
|
120
|
** Set or clear the vfile.isexe flag for a file. |
|
121
|
*/ |
|
122
|
static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ |
|
123
|
static Stmt s; |
|
124
|
db_static_prepare(&s, |
|
125
|
"UPDATE vfile SET isexe=:isexe" |
|
126
|
" WHERE vid=:vid AND pathname=:path AND isexe!=:isexe" |
|
127
|
); |
|
128
|
db_bind_int(&s, ":isexe", onoff); |
|
129
|
db_bind_int(&s, ":vid", vid); |
|
130
|
db_bind_text(&s, ":path", zFilename); |
|
131
|
db_step(&s); |
|
132
|
db_reset(&s); |
|
133
|
} |
|
134
|
|
|
135
|
/* |
|
136
|
** Set or clear the execute permission bit (as appropriate) for all |
|
137
|
** files in the current check-out, and replace files that have |
|
138
|
** symlink bit with actual symlinks. |
|
139
|
*/ |
|
140
|
void checkout_set_all_exe(int vid){ |
|
141
|
Blob filename; |
|
142
|
int baseLen; |
|
143
|
Manifest *pManifest; |
|
144
|
ManifestFile *pFile; |
|
145
|
|
|
146
|
/* Check the EXE permission status of all files |
|
147
|
*/ |
|
148
|
pManifest = manifest_get(vid, CFTYPE_MANIFEST, 0); |
|
149
|
if( pManifest==0 ) return; |
|
150
|
blob_zero(&filename); |
|
151
|
blob_appendf(&filename, "%s", g.zLocalRoot); |
|
152
|
baseLen = blob_size(&filename); |
|
153
|
manifest_file_rewind(pManifest); |
|
154
|
while( (pFile = manifest_file_next(pManifest, 0))!=0 ){ |
|
155
|
int isExe; |
|
156
|
blob_append(&filename, pFile->zName, -1); |
|
157
|
isExe = pFile->zPerm && strstr(pFile->zPerm, "x"); |
|
158
|
file_setexe(blob_str(&filename), isExe); |
|
159
|
set_or_clear_isexe(pFile->zName, vid, isExe); |
|
160
|
blob_resize(&filename, baseLen); |
|
161
|
} |
|
162
|
blob_reset(&filename); |
|
163
|
manifest_destroy(pManifest); |
|
164
|
} |
|
165
|
|
|
166
|
|
|
167
|
/* |
|
168
|
** If the "manifest" setting is true, then automatically generate |
|
169
|
** files named "manifest" and "manifest.uuid" containing, respectively, |
|
170
|
** the text of the manifest and the artifact ID of the manifest. |
|
171
|
** If the manifest setting is set, but is not a boolean value, then treat |
|
172
|
** each character as a flag to enable writing "manifest", "manifest.uuid" or |
|
173
|
** "manifest.tags". |
|
174
|
*/ |
|
175
|
void manifest_to_disk(int vid){ |
|
176
|
char *zManFile; |
|
177
|
int flg; |
|
178
|
|
|
179
|
flg = db_get_manifest_setting(0); |
|
180
|
|
|
181
|
if( flg & MFESTFLG_RAW ){ |
|
182
|
Blob manifest = BLOB_INITIALIZER; |
|
183
|
content_get(vid, &manifest); |
|
184
|
sterilize_manifest(&manifest, CFTYPE_MANIFEST); |
|
185
|
zManFile = mprintf("%smanifest", g.zLocalRoot); |
|
186
|
blob_write_to_file(&manifest, zManFile); |
|
187
|
free(zManFile); |
|
188
|
blob_reset(&manifest); |
|
189
|
}else{ |
|
190
|
if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){ |
|
191
|
zManFile = mprintf("%smanifest", g.zLocalRoot); |
|
192
|
file_delete(zManFile); |
|
193
|
free(zManFile); |
|
194
|
} |
|
195
|
} |
|
196
|
if( flg & MFESTFLG_UUID ){ |
|
197
|
Blob hash; |
|
198
|
zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
|
199
|
blob_set_dynamic(&hash, rid_to_uuid(vid)); |
|
200
|
blob_append(&hash, "\n", 1); |
|
201
|
blob_write_to_file(&hash, zManFile); |
|
202
|
free(zManFile); |
|
203
|
blob_reset(&hash); |
|
204
|
}else{ |
|
205
|
if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){ |
|
206
|
zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
|
207
|
file_delete(zManFile); |
|
208
|
free(zManFile); |
|
209
|
} |
|
210
|
} |
|
211
|
if( flg & MFESTFLG_TAGS ){ |
|
212
|
Blob taglist = BLOB_INITIALIZER; |
|
213
|
zManFile = mprintf("%smanifest.tags", g.zLocalRoot); |
|
214
|
get_checkin_taglist(vid, &taglist); |
|
215
|
blob_write_to_file(&taglist, zManFile); |
|
216
|
free(zManFile); |
|
217
|
blob_reset(&taglist); |
|
218
|
}else{ |
|
219
|
if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.tags'") ){ |
|
220
|
zManFile = mprintf("%smanifest.tags", g.zLocalRoot); |
|
221
|
file_delete(zManFile); |
|
222
|
free(zManFile); |
|
223
|
} |
|
224
|
} |
|
225
|
} |
|
226
|
|
|
227
|
/* |
|
228
|
** Find the branch name and all symbolic tags for a particular check-in |
|
229
|
** identified by "rid". |
|
230
|
** |
|
231
|
** The branch name is actually only extracted if this procedure is run |
|
232
|
** from within a local check-out. And the branch name is not the branch |
|
233
|
** name for "rid" but rather the branch name for the current check-out. |
|
234
|
** It is unclear if the rid parameter is always the same as the current |
|
235
|
** check-out. |
|
236
|
*/ |
|
237
|
void get_checkin_taglist(int rid, Blob *pOut){ |
|
238
|
Stmt stmt; |
|
239
|
char *zCurrent; |
|
240
|
blob_reset(pOut); |
|
241
|
zCurrent = db_text(0, "SELECT value FROM tagxref" |
|
242
|
" WHERE rid=%d AND tagid=%d", rid, TAG_BRANCH); |
|
243
|
blob_appendf(pOut, "branch %s\n", zCurrent); |
|
244
|
db_prepare(&stmt, "SELECT substr(tagname, 5)" |
|
245
|
" FROM tagxref, tag" |
|
246
|
" WHERE tagxref.rid=%d" |
|
247
|
" AND tagxref.tagtype>0" |
|
248
|
" AND tag.tagid=tagxref.tagid" |
|
249
|
" AND tag.tagname GLOB 'sym-*'", rid); |
|
250
|
while( db_step(&stmt)==SQLITE_ROW ){ |
|
251
|
const char *zName; |
|
252
|
zName = db_column_text(&stmt, 0); |
|
253
|
blob_appendf(pOut, "tag %s\n", zName); |
|
254
|
} |
|
255
|
db_reset(&stmt); |
|
256
|
db_finalize(&stmt); |
|
257
|
} |
|
258
|
|
|
259
|
|
|
260
|
/* |
|
261
|
** COMMAND: checkout* |
|
262
|
** COMMAND: co# |
|
263
|
** |
|
264
|
** Usage: %fossil checkout ?VERSION | --latest? ?OPTIONS? |
|
265
|
** or: %fossil co ?VERSION | --latest? ?OPTIONS? |
|
266
|
** |
|
267
|
** NOTE: Most people use "fossil update" instead of "fossil checkout" for |
|
268
|
** day-to-day operations. If you are new to Fossil and trying to learn your |
|
269
|
** way around, it is recommended that you become familiar with the |
|
270
|
** "fossil update" command first. |
|
271
|
** |
|
272
|
** This command changes the current check-out to the version specified |
|
273
|
** as an argument. The command aborts if there are edited files in the |
|
274
|
** current check-out unless the --force option is used. The --keep option |
|
275
|
** leaves files on disk unchanged, except the manifest and manifest.uuid |
|
276
|
** files. |
|
277
|
** |
|
278
|
** The --latest flag can be used in place of VERSION to check-out the |
|
279
|
** latest version in the repository. |
|
280
|
** |
|
281
|
** Options: |
|
282
|
** -f|--force Ignore edited files in the current check-out |
|
283
|
** -k|--keep Only update the manifest file(s) |
|
284
|
** --force-missing Force check-out even if content is missing |
|
285
|
** --prompt Prompt before overwriting when --force is used |
|
286
|
** --setmtime Set timestamps of all files to match their SCM-side |
|
287
|
** times (the timestamp of the last check-in which modified |
|
288
|
** them) |
|
289
|
** |
|
290
|
** See also: [[update]] |
|
291
|
*/ |
|
292
|
void checkout_cmd(void){ |
|
293
|
int forceFlag; /* Force check-out even if edits exist */ |
|
294
|
int forceMissingFlag; /* Force check-out even if missing content */ |
|
295
|
int keepFlag; /* Do not change any files on disk */ |
|
296
|
int latestFlag; /* Check out the latest version */ |
|
297
|
char *zVers; /* Version to check out */ |
|
298
|
int promptFlag; /* True to prompt before overwriting */ |
|
299
|
int vid, prior; |
|
300
|
int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
|
301
|
Blob cksum1, cksum1b, cksum2; |
|
302
|
|
|
303
|
db_must_be_within_tree(); |
|
304
|
db_begin_transaction(); |
|
305
|
forceMissingFlag = find_option("force-missing",0,0)!=0; |
|
306
|
keepFlag = find_option("keep","k",0)!=0; |
|
307
|
forceFlag = find_option("force","f",0)!=0; |
|
308
|
latestFlag = find_option("latest",0,0)!=0; |
|
309
|
promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0; |
|
310
|
setmtimeFlag = find_option("setmtime",0,0)!=0; |
|
311
|
|
|
312
|
if( keepFlag != 0 ){ |
|
313
|
/* After flag collection, in order not to affect promptFlag */ |
|
314
|
forceFlag=1; |
|
315
|
} |
|
316
|
|
|
317
|
/* We should be done with options.. */ |
|
318
|
verify_all_options(); |
|
319
|
|
|
320
|
if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){ |
|
321
|
usage("VERSION|--latest ?--force? ?--keep?"); |
|
322
|
} |
|
323
|
if( !forceFlag && unsaved_changes(0) ){ |
|
324
|
fossil_fatal("there are unsaved changes in the current check-out"); |
|
325
|
} |
|
326
|
if( forceFlag ){ |
|
327
|
db_multi_exec("DELETE FROM vfile"); |
|
328
|
prior = 0; |
|
329
|
}else{ |
|
330
|
prior = db_lget_int("checkout",0); |
|
331
|
} |
|
332
|
if( latestFlag ){ |
|
333
|
compute_leaves(db_lget_int("checkout",0), 1); |
|
334
|
zVers = db_text(0, "SELECT uuid FROM leaves, event, blob" |
|
335
|
" WHERE event.objid=leaves.rid AND blob.rid=leaves.rid" |
|
336
|
" ORDER BY event.mtime DESC"); |
|
337
|
if( zVers==0 ){ |
|
338
|
zVers = db_text(0, "SELECT uuid FROM event, blob" |
|
339
|
" WHERE event.objid=blob.rid AND event.type='ci'" |
|
340
|
" ORDER BY event.mtime DESC"); |
|
341
|
} |
|
342
|
if( zVers==0 ){ |
|
343
|
db_end_transaction(0); |
|
344
|
return; |
|
345
|
} |
|
346
|
}else{ |
|
347
|
zVers = g.argv[2]; |
|
348
|
} |
|
349
|
vid = load_vfile(zVers, forceMissingFlag); |
|
350
|
if( prior==vid ){ |
|
351
|
if( setmtimeFlag ) vfile_check_signature(vid, CKSIG_SETMTIME); |
|
352
|
db_end_transaction(0); |
|
353
|
return; |
|
354
|
} |
|
355
|
if( !keepFlag ){ |
|
356
|
uncheckout(prior); |
|
357
|
} |
|
358
|
db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); |
|
359
|
if( !keepFlag ){ |
|
360
|
vfile_to_disk(vid, 0, !g.fQuiet, promptFlag); |
|
361
|
} |
|
362
|
checkout_set_all_exe(vid); |
|
363
|
ensure_empty_dirs_created(0); |
|
364
|
db_set_checkout(vid, 1); |
|
365
|
undo_reset(); |
|
366
|
db_multi_exec("DELETE FROM vmerge"); |
|
367
|
if( !keepFlag && db_get_boolean("repo-cksum",1) ){ |
|
368
|
vfile_aggregate_checksum_manifest(vid, &cksum1, &cksum1b); |
|
369
|
vfile_aggregate_checksum_disk(vid, &cksum2); |
|
370
|
if( blob_compare(&cksum1, &cksum2) ){ |
|
371
|
fossil_print("WARNING: manifest checksum does not agree with disk\n"); |
|
372
|
} |
|
373
|
if( blob_size(&cksum1b) && blob_compare(&cksum1, &cksum1b) ){ |
|
374
|
fossil_print("WARNING: manifest checksum does not agree with manifest\n"); |
|
375
|
} |
|
376
|
} |
|
377
|
if( setmtimeFlag ) vfile_check_signature(vid, CKSIG_SETMTIME); |
|
378
|
db_end_transaction(0); |
|
379
|
} |
|
380
|
|
|
381
|
/* |
|
382
|
** Unlink the local database file |
|
383
|
*/ |
|
384
|
static void unlink_local_database(int manifestOnly){ |
|
385
|
const char *zReserved; |
|
386
|
int i; |
|
387
|
for(i=0; (zReserved = fossil_reserved_name(i, 1))!=0; i++){ |
|
388
|
if( manifestOnly==0 || zReserved[0]=='m' ){ |
|
389
|
char *z; |
|
390
|
z = mprintf("%s%s", g.zLocalRoot, zReserved); |
|
391
|
file_delete(z); |
|
392
|
free(z); |
|
393
|
} |
|
394
|
} |
|
395
|
} |
|
396
|
|
|
397
|
/* |
|
398
|
** COMMAND: close* |
|
399
|
** |
|
400
|
** Usage: %fossil close ?OPTIONS? |
|
401
|
** |
|
402
|
** The opposite of "[[open]]". Close the current database connection. |
|
403
|
** Require a -f or --force flag if there are unsaved changes in the |
|
404
|
** current check-out or if there is non-empty stash. |
|
405
|
** |
|
406
|
** Options: |
|
407
|
** -f|--force Necessary to close a check-out with uncommitted changes |
|
408
|
** |
|
409
|
** See also: [[open]] |
|
410
|
*/ |
|
411
|
void close_cmd(void){ |
|
412
|
int forceFlag = find_option("force","f",0)!=0; |
|
413
|
db_must_be_within_tree(); |
|
414
|
|
|
415
|
/* We should be done with options.. */ |
|
416
|
verify_all_options(); |
|
417
|
|
|
418
|
if( !forceFlag && unsaved_changes(0) ){ |
|
419
|
fossil_fatal("there are unsaved changes in the current check-out"); |
|
420
|
} |
|
421
|
if( !forceFlag |
|
422
|
&& db_table_exists("localdb","stash") |
|
423
|
&& db_exists("SELECT 1 FROM localdb.stash") |
|
424
|
){ |
|
425
|
fossil_fatal("closing the check-out will delete your stash"); |
|
426
|
} |
|
427
|
if( db_is_writeable("repository") ){ |
|
428
|
db_unset_mprintf(1, "ckout:%q", g.zLocalRoot); |
|
429
|
} |
|
430
|
unlink_local_database(1); |
|
431
|
db_close(1); |
|
432
|
unlink_local_database(0); |
|
433
|
} |
|
434
|
|
|
435
|
|
|
436
|
/* |
|
437
|
** COMMAND: get |
|
438
|
** |
|
439
|
** Usage: %fossil get URL ?VERSION? ?OPTIONS? |
|
440
|
** |
|
441
|
** Download a single check-in from a remote repository named URL and |
|
442
|
** unpack all of the files into a subdirectory. The specific check-in |
|
443
|
** to download is identified by VERSION. If VERSION is omitted, the |
|
444
|
** latest trunk check-in is used. The URL can be a traditional "https:", |
|
445
|
** "ssh:", or "file:" URL similar to the examples shown below, or it can |
|
446
|
** be the name of a local repository/ |
|
447
|
** |
|
448
|
** * https://domain.com/project |
|
449
|
** * ssh://my-server/project.fossil |
|
450
|
** * file:/home/user/Fossils/project.fossil |
|
451
|
** |
|
452
|
** This command works by downloading an SQL archive of the requested |
|
453
|
** check-in and then extracting all the files from the archive. |
|
454
|
** |
|
455
|
** Options: |
|
456
|
** --dest DIRECTORY Extract files into DIRECTORY. Use "--dest ." to |
|
457
|
** extract into the local directory. If this option is |
|
458
|
** omitted, Fossil invents a subdirectory name derived |
|
459
|
** from base filename in the URL and from the VERSION. |
|
460
|
** -f|--force Overwrite existing files |
|
461
|
** --list List all the files that would have been checked |
|
462
|
** out but do not actually write anything to the |
|
463
|
** filesystem. |
|
464
|
** --sqlar ARCHIVE Leave the check-out in an SQL-archive named ARCHIVE |
|
465
|
** rather than unpacking into separate files. |
|
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 bQuiet = g.fQuiet; |
|
472
|
int bDebug = find_option("debug",0,0)!=0; |
|
473
|
int bList = find_option("list",0,0)!=0; |
|
474
|
const char *zSqlArchive = find_option("sqlar",0,1); |
|
475
|
const char *z; |
|
476
|
char *zDest = 0; /* Where to store results */ |
|
477
|
char *zSql; /* SQL used to query the results */ |
|
478
|
const char *zUrl; /* Url to get */ |
|
479
|
const char *zVers; /* Version name to get */ |
|
480
|
unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; |
|
481
|
Blob in, out; /* I/O for the HTTP request */ |
|
482
|
Blob file; /* A file to extract */ |
|
483
|
sqlite3 *db; /* Database containing downloaded sqlar */ |
|
484
|
sqlite3_stmt *pStmt; /* Statement for querying the database */ |
|
485
|
int rc; /* Result of subroutine calls */ |
|
486
|
int nFile = 0; /* Number of files written */ |
|
487
|
int nDir = 0; /* Number of directories written */ |
|
488
|
i64 nByte = 0; /* Number of bytes written */ |
|
489
|
|
|
490
|
z = find_option("dest",0,1); |
|
491
|
if( z ) zDest = fossil_strdup(z); |
|
492
|
verify_all_options(); |
|
493
|
if( g.argc<3 || g.argc>4 ){ |
|
494
|
usage("URL ?VERSION? ?OPTIONS?"); |
|
495
|
} |
|
496
|
zUrl = g.argv[2]; |
|
497
|
zVers = g.argc==4 ? g.argv[3] : db_main_branch(); |
|
498
|
|
|
499
|
/* Parse the URL of the repository */ |
|
500
|
url_parse(zUrl, 0); |
|
501
|
|
|
502
|
/* Construct an appropriate name for the destination directory */ |
|
503
|
if( zDest==0 ){ |
|
504
|
int i; |
|
505
|
const char *zTail; |
|
506
|
const char *zDot; |
|
507
|
int n; |
|
508
|
if( g.url.isFile ){ |
|
509
|
zTail = file_tail(g.url.name); |
|
510
|
}else{ |
|
511
|
zTail = file_tail(g.url.path); |
|
512
|
} |
|
513
|
zDot = strchr(zTail,'.'); |
|
514
|
if( zDot==0 ) zDot = zTail+strlen(zTail); |
|
515
|
n = (int)(zDot - zTail); |
|
516
|
zDest = mprintf("%.*s-%s", n, zTail, zVers); |
|
517
|
for(i=0; zDest[i]; i++){ |
|
518
|
char c = zDest[i]; |
|
519
|
if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){ |
|
520
|
zDest[i] = '-'; |
|
521
|
} |
|
522
|
} |
|
523
|
} |
|
524
|
if( bDebug ){ |
|
525
|
fossil_print("dest = %s\n", zDest); |
|
526
|
} |
|
527
|
|
|
528
|
/* Error checking */ |
|
529
|
if( zDest!=file_tail(zDest) ){ |
|
530
|
fossil_fatal("--dest must be a simple directory name, not a path"); |
|
531
|
} |
|
532
|
if( zVers!=file_tail(zVers) ){ |
|
533
|
fossil_fatal("The \"fossil get\" command does not currently work with" |
|
534
|
" version names that contain \"/\". This will be fixed in" |
|
535
|
" a future release."); |
|
536
|
} |
|
537
|
/* To relax the restrictions above, change the subpath URL formula below |
|
538
|
** to use query parameters. Ex: /sqlar?r=%t&name=%t */ |
|
539
|
|
|
540
|
if( !forceFlag ){ |
|
541
|
if( zSqlArchive ){ |
|
542
|
if( file_isdir(zSqlArchive, ExtFILE)>0 ){ |
|
543
|
fossil_fatal("file already exists: \"%s\"", zSqlArchive); |
|
544
|
} |
|
545
|
}else if( file_isdir(zDest, ExtFILE)>0 ){ |
|
546
|
if( fossil_strcmp(zDest,".")==0 ){ |
|
547
|
if( file_directory_list(zDest,0,1,1,0) ){ |
|
548
|
fossil_fatal("current directory is not empty"); |
|
549
|
} |
|
550
|
}else{ |
|
551
|
fossil_fatal("\"%s\" already exists", zDest); |
|
552
|
} |
|
553
|
} |
|
554
|
} |
|
555
|
|
|
556
|
/* Construct a subpath on the URL if necessary */ |
|
557
|
if( g.url.isFile ){ |
|
558
|
g.url.subpath = mprintf("/sqlar/%t/%t.sqlar", zVers, zDest); |
|
559
|
}else{ |
|
560
|
g.url.subpath = mprintf("%s/sqlar/%t/%t.sqlar", g.url.path, zVers, zDest); |
|
561
|
} |
|
562
|
|
|
563
|
if( bDebug ){ |
|
564
|
urlparse_print(0); |
|
565
|
} |
|
566
|
|
|
567
|
/* Fetch the ZIP archive for the requested check-in */ |
|
568
|
blob_init(&in, 0, 0); |
|
569
|
blob_init(&out, 0, 0); |
|
570
|
if( bDebug ) mHttpFlags |= HTTP_VERBOSE; |
|
571
|
if( bQuiet ) mHttpFlags |= HTTP_QUIET; |
|
572
|
rc = http_exchange(&in, &out, mHttpFlags, 4, 0); |
|
573
|
if( rc |
|
574
|
|| out.nUsed<512 |
|
575
|
|| (out.nUsed%512)!=0 |
|
576
|
|| memcmp(out.aData,"SQLite format 3",16)!=0 |
|
577
|
){ |
|
578
|
fossil_fatal("Server did not return the requested check-in."); |
|
579
|
} |
|
580
|
|
|
581
|
if( zSqlArchive ){ |
|
582
|
blob_write_to_file(&out, zSqlArchive); |
|
583
|
if( bVerbose ) fossil_print("%s\n", zSqlArchive); |
|
584
|
return; |
|
585
|
} |
|
586
|
|
|
587
|
rc = sqlite3_open(":memory:", &db); |
|
588
|
if( rc==SQLITE_OK ){ |
|
589
|
int sz = blob_size(&out); |
|
590
|
rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz, |
|
591
|
SQLITE_DESERIALIZE_READONLY); |
|
592
|
} |
|
593
|
if( rc!=SQLITE_OK ){ |
|
594
|
fossil_fatal("Cannot create an in-memory database: %s", |
|
595
|
sqlite3_errmsg(db)); |
|
596
|
} |
|
597
|
zSql = mprintf("SELECT name, mode, sz, data, mtime FROM sqlar" |
|
598
|
" WHERE name GLOB '%q*'", zDest); |
|
599
|
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
|
600
|
fossil_free(zSql); |
|
601
|
if( rc!=0 ){ |
|
602
|
fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db)); |
|
603
|
} |
|
604
|
blob_init(&file, 0, 0); |
|
605
|
while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
|
606
|
const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0); |
|
607
|
int mode = sqlite3_column_int(pStmt, 1); |
|
608
|
int sz = sqlite3_column_int(pStmt, 2); |
|
609
|
if( bList ){ |
|
610
|
fossil_print("%s\n", zFilename); |
|
611
|
}else if( mode & 0x4000 ){ |
|
612
|
/* A directory name */ |
|
613
|
nDir++; |
|
614
|
file_mkdir(zFilename, ExtFILE, 1); |
|
615
|
}else{ |
|
616
|
/* A file */ |
|
617
|
unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3); |
|
618
|
unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3); |
|
619
|
unsigned long int nOut2 = (unsigned long int)sz; |
|
620
|
nFile++; |
|
621
|
nByte += sz; |
|
622
|
blob_resize(&file, sz); |
|
623
|
if( nIn<sz ){ |
|
624
|
rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2, |
|
625
|
inBuf, nIn); |
|
626
|
if( rc!=Z_OK ){ |
|
627
|
fossil_fatal("Failed to uncompress file %s", zFilename); |
|
628
|
} |
|
629
|
}else{ |
|
630
|
memcpy(blob_buffer(&file), inBuf, sz); |
|
631
|
} |
|
632
|
blob_write_to_file(&file, zFilename); |
|
633
|
if( mode & 0x40 ){ |
|
634
|
file_setexe(zFilename, 1); |
|
635
|
} |
|
636
|
blob_zero(&file); |
|
637
|
file_set_mtime(zFilename, sqlite3_column_int64(pStmt,4)); |
|
638
|
if( bVerbose ){ |
|
639
|
fossil_print("%s\n", zFilename); |
|
640
|
} |
|
641
|
} |
|
642
|
} |
|
643
|
sqlite3_finalize(pStmt); |
|
644
|
sqlite3_close(db); |
|
645
|
blob_zero(&out); |
|
646
|
if( !bVerbose && !bQuiet && nFile>0 && zDest ){ |
|
647
|
fossil_print("%d files (%,lld bytes) written into %s", |
|
648
|
nFile, nByte, zDest); |
|
649
|
if( nDir>1 ){ |
|
650
|
fossil_print(" and %d subdirectories\n", nDir-1); |
|
651
|
}else{ |
|
652
|
fossil_print("\n"); |
|
653
|
} |
|
654
|
} |
|
655
|
} |
|
656
|
|