Fossil SCM

fossil-scm / src / clone.c
Blame History Raw 438 lines
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 clone a repository
19
*/
20
#include "config.h"
21
#include "clone.h"
22
#include <assert.h>
23
24
/*
25
** If there are public BLOBs that deltas from private BLOBs, then
26
** undeltify the public BLOBs so that the private BLOBs may be safely
27
** deleted.
28
*/
29
void fix_private_blob_dependencies(int showWarning){
30
Bag toUndelta;
31
Stmt q;
32
int rid;
33
34
/* Careful: We are about to delete all BLOB entries that are private.
35
** So make sure that any no public BLOBs are deltas from a private BLOB.
36
** Otherwise after the deletion, we won't be able to recreate the public
37
** BLOBs.
38
*/
39
db_prepare(&q,
40
"SELECT "
41
" rid, (SELECT uuid FROM blob WHERE rid=delta.rid),"
42
" srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)"
43
" FROM delta"
44
" WHERE srcid in private AND rid NOT IN private"
45
);
46
bag_init(&toUndelta);
47
while( db_step(&q)==SQLITE_ROW ){
48
int rid = db_column_int(&q, 0);
49
const char *zId = db_column_text(&q, 1);
50
int srcid = db_column_int(&q, 2);
51
const char *zSrc = db_column_text(&q, 3);
52
if( showWarning ){
53
fossil_warning(
54
"public artifact %S (%d) is a delta from private artifact %S (%d)",
55
zId, rid, zSrc, srcid
56
);
57
}
58
bag_insert(&toUndelta, rid);
59
}
60
db_finalize(&q);
61
while( (rid = bag_first(&toUndelta))>0 ){
62
content_undelta(rid);
63
bag_remove(&toUndelta, rid);
64
}
65
bag_clear(&toUndelta);
66
}
67
68
/*
69
** Delete all private content from a repository.
70
*/
71
void delete_private_content(void){
72
fix_private_blob_dependencies(1);
73
db_multi_exec(
74
"DELETE FROM blob WHERE rid IN private;"
75
"DELETE FROM delta WHERE rid IN private;"
76
"DELETE FROM private;"
77
"DROP TABLE IF EXISTS modreq;"
78
);
79
}
80
81
82
/*
83
** COMMAND: clone
84
**
85
** Usage: %fossil clone ?OPTIONS? URI ?FILENAME?
86
**
87
** Make a clone of a repository specified by URI in the local
88
** file named FILENAME. If FILENAME is omitted, then an appropriate
89
** filename is deduced from last element of the path in the URL.
90
**
91
** URI may be one of the following forms ([...] denotes optional elements):
92
**
93
** * HTTP/HTTPS protocol:
94
**
95
** http[s]://[userid[:password]@]host[:port][/path]
96
**
97
** * SSH protocol:
98
**
99
** ssh://[userid@]host[:port]/path/to/repo.fossil[?fossil=path/fossil.exe]
100
**
101
** * Filesystem:
102
**
103
** [file://]path/to/repo.fossil
104
**
105
** For ssh and filesystem, path must have an extra leading
106
** '/' to use an absolute path.
107
**
108
** Use %HH escapes for special characters in the userid and
109
** password. For example "%40" in place of "@", "%2f" in place
110
** of "/", and "%3a" in place of ":".
111
**
112
** Note that in Fossil (in contrast to some other DVCSes) a repository
113
** is distinct from a check-out. Cloning a repository is not the same thing
114
** as opening a repository. This command always clones the repository. This
115
** command might also open the repository, but only if the --no-open option
116
** is omitted and either the --workdir option is included or the FILENAME
117
** argument is omitted. Use the separate [[open]] command to open a
118
** repository that was previously cloned and already exists on the
119
** local machine.
120
**
121
** By default, the current login name is used to create the default
122
** admin user for the new clone. This can be overridden using
123
** the -A|--admin-user parameter.
124
**
125
** Options:
126
** -A|--admin-user USERNAME Make USERNAME the administrator
127
** -B|--httpauth USER:PASS Add HTTP Basic Authorization to requests
128
** --nested Allow opening a repository inside an opened
129
** check-out
130
** --nocompress Omit extra delta compression
131
** --no-open Clone only. Do not open a check-out.
132
** --once Don't remember the URI.
133
** --private Also clone private branches
134
** --proxy PROXY Use the specified HTTP proxy
135
** --save-http-password Remember the HTTP password without asking
136
** -c|--ssh-command SSH Use SSH as the "ssh" command
137
** --ssl-identity FILENAME Use the SSL identity if requested by the server
138
** --transport-command CMD Use CMD to move messages to the server and back
139
** -u|--unversioned Also sync unversioned content
140
** -v|--verbose Show more statistics in output
141
** --workdir DIR Also open a check-out in DIR
142
** --xverbose Extra debugging output
143
**
144
** See also: [[init]], [[open]]
145
*/
146
void clone_cmd(void){
147
char *zPassword;
148
const char *zDefaultUser; /* Optional name of the default user */
149
const char *zHttpAuth; /* HTTP Authorization user:pass information */
150
int nErr = 0;
151
int urlFlags = URL_PROMPT_PW | URL_REMEMBER;
152
int syncFlags = SYNC_CLONE;
153
int noCompress = find_option("nocompress",0,0)!=0;
154
int noOpen = find_option("no-open",0,0)!=0;
155
int allowNested = find_option("nested",0,0)!=0; /* Used by open */
156
const char *zRepo = 0; /* Name of the new local repository file */
157
const char *zWorkDir = 0; /* Open in this directory, if not zero */
158
159
160
/* Also clone private branches */
161
if( find_option("private",0,0)!=0 ) syncFlags |= SYNC_PRIVATE;
162
if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER;
163
if( find_option("save-http-password",0,0)!=0 ){
164
urlFlags &= ~URL_PROMPT_PW;
165
urlFlags |= URL_REMEMBER_PW;
166
}
167
if( find_option("verbose","v",0)!=0) syncFlags |= SYNC_VERBOSE;
168
if( find_option("xverbose",0,0)!=0) syncFlags |= SYNC_XVERBOSE;
169
if( find_option("unversioned","u",0)!=0 ){
170
syncFlags |= SYNC_UNVERSIONED;
171
if( syncFlags & SYNC_VERBOSE ){
172
syncFlags |= SYNC_UV_TRACE;
173
}
174
}
175
zHttpAuth = find_option("httpauth","B",1);
176
zDefaultUser = find_option("admin-user","A",1);
177
zWorkDir = find_option("workdir", 0, 1);
178
clone_ssh_find_options();
179
url_proxy_options();
180
g.zHttpCmd = find_option("transport-command",0,1);
181
182
/* We should be done with options.. */
183
verify_all_options();
184
185
if( g.argc < 3 ){
186
usage("?OPTIONS? FILE-OR-URL ?NEW-REPOSITORY?");
187
}
188
db_open_config(0, 0);
189
if( g.argc==4 ){
190
zRepo = g.argv[3];
191
}else{
192
char *zBase = url_to_repo_basename(g.argv[2]);
193
if( zBase==0 ){
194
fossil_fatal(
195
"unable to guess a repository name from the url \"%s\".\n"
196
"give the repository filename as an additional argument.",
197
g.argv[2]);
198
}
199
zRepo = mprintf("./%s.fossil", zBase);
200
if( zWorkDir==0 ){
201
zWorkDir = mprintf("./%s", zBase);
202
}
203
fossil_free(zBase);
204
}
205
if( -1 != file_size(zRepo, ExtFILE) ){
206
fossil_fatal("file already exists: %s", zRepo);
207
}
208
/* Fail before clone if open will fail because inside an open check-out */
209
if( zWorkDir!=0 && zWorkDir[0]!=0 && !noOpen ){
210
if( db_open_local_v2(0, allowNested) ){
211
fossil_fatal("there is already an open tree at %s", g.zLocalRoot);
212
}
213
}
214
url_parse(g.argv[2], urlFlags);
215
if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
216
if( g.url.isFile ){
217
file_copy(g.url.name, zRepo);
218
db_close(1);
219
db_open_repository(zRepo);
220
db_open_config(1,0);
221
db_record_repository_filename(zRepo);
222
url_remember();
223
if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content();
224
shun_artifacts();
225
db_create_default_users(1, zDefaultUser);
226
if( zDefaultUser ){
227
g.zLogin = zDefaultUser;
228
}else{
229
g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'");
230
}
231
fossil_print("Repository cloned into %s\n", zRepo);
232
}else{
233
db_close_config();
234
db_create_repository(zRepo);
235
db_open_repository(zRepo);
236
db_open_config(0,0);
237
db_begin_transaction();
238
db_record_repository_filename(zRepo);
239
db_initial_setup(0, 0, zDefaultUser);
240
user_select();
241
db_set("content-schema", CONTENT_SCHEMA, 0);
242
db_set("aux-schema", AUX_SCHEMA_MAX, 0);
243
db_set("rebuilt", get_version(), 0);
244
db_unset("hash-policy", 0);
245
remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, g.argv[2]);
246
url_remember();
247
if( g.zSSLIdentity!=0 ){
248
/* If the --ssl-identity option was specified, store it as a setting */
249
Blob fn;
250
blob_zero(&fn);
251
file_canonical_name(g.zSSLIdentity, &fn, 0);
252
db_unprotect(PROTECT_ALL);
253
db_set("ssl-identity", blob_str(&fn), 0);
254
db_protect_pop();
255
blob_reset(&fn);
256
}
257
db_unprotect(PROTECT_CONFIG);
258
db_multi_exec(
259
"REPLACE INTO config(name,value,mtime)"
260
" VALUES('server-code', lower(hex(randomblob(20))), now());"
261
"DELETE FROM config WHERE name='project-code';"
262
);
263
db_protect_pop();
264
url_enable_proxy(0);
265
clone_ssh_db_set_options();
266
url_get_password_if_needed();
267
g.xlinkClusterOnly = 1;
268
nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0,0);
269
g.xlinkClusterOnly = 0;
270
verify_cancel();
271
db_end_transaction(0);
272
db_close(1);
273
if( nErr ){
274
file_delete(zRepo);
275
if( g.fHttpTrace ){
276
fossil_fatal(
277
"server returned an error - clone aborted\n\n%s",
278
http_last_trace_reply()
279
);
280
}else{
281
fossil_fatal(
282
"server returned an error - clone aborted\n"
283
"Rerun using --httptrace for more detail"
284
);
285
}
286
}
287
db_open_repository(zRepo);
288
}
289
db_begin_transaction();
290
if( db_exists("SELECT 1 FROM delta WHERE srcId IN phantom") ){
291
fossil_fatal("there are unresolved deltas -"
292
" the clone is probably incomplete and unusable.");
293
}
294
fossil_print("Rebuilding repository meta-data...\n");
295
rebuild_db(1, 0);
296
if( !noCompress ){
297
int nDelta = 0;
298
i64 nByte;
299
fossil_print("Extra delta compression... "); fflush(stdout);
300
nByte = extra_deltification(&nDelta);
301
if( nDelta==1 ){
302
fossil_print("1 delta saves %,lld bytes\n", nByte);
303
}else if( nDelta>1 ){
304
fossil_print("%d deltas save %,lld bytes\n", nDelta, nByte);
305
}else{
306
fossil_print("none found\n");
307
}
308
}
309
db_end_transaction(0);
310
fossil_print("Vacuuming the database... "); fflush(stdout);
311
if( db_int(0, "PRAGMA page_count")>1000
312
&& db_int(0, "PRAGMA page_size")<8192 ){
313
db_multi_exec("PRAGMA page_size=8192;");
314
}
315
db_unprotect(PROTECT_ALL);
316
db_multi_exec("VACUUM");
317
db_protect_pop();
318
fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
319
fossil_print("server-id: %s\n", db_get("server-code", 0));
320
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
321
fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
322
hash_user_password(g.zLogin);
323
if( zWorkDir!=0 && zWorkDir[0]!=0 && !noOpen ){
324
Blob cmd;
325
fossil_print("opening the new %s repository in directory %s...\n",
326
zRepo, zWorkDir);
327
blob_init(&cmd, 0, 0);
328
blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
329
blob_append(&cmd, " open ", -1);
330
blob_append_escaped_arg(&cmd, zRepo, 1);
331
blob_append(&cmd, " --nosync --workdir ", -1);
332
blob_append_escaped_arg(&cmd, zWorkDir, 1);
333
if( allowNested ){
334
blob_append(&cmd, " --nested", -1);
335
}
336
fossil_system(blob_str(&cmd));
337
blob_reset(&cmd);
338
}
339
}
340
341
/*
342
** If user chooses to use HTTP Authentication over unencrypted HTTP,
343
** remember decision. Otherwise, if the URL is being changed and no
344
** preference has been indicated, err on the safe side and revert the
345
** decision. Set the global preference if the URL is not being changed.
346
*/
347
void remember_or_get_http_auth(
348
const char *zHttpAuth, /* Credentials in the form "user:password" */
349
int fRemember, /* True to remember credentials for later reuse */
350
const char *zUrl /* URL for which these credentials apply */
351
){
352
if( zHttpAuth && zHttpAuth[0] ){
353
g.zHttpAuth = fossil_strdup(zHttpAuth);
354
}
355
if( fRemember ){
356
if( g.zHttpAuth && g.zHttpAuth[0] ){
357
set_httpauth(g.zHttpAuth);
358
}else if( zUrl && zUrl[0] ){
359
db_unset_mprintf(0, "http-auth:%s", g.url.canonical);
360
}else{
361
g.zHttpAuth = get_httpauth();
362
}
363
}else if( g.zHttpAuth==0 && zUrl==0 ){
364
g.zHttpAuth = get_httpauth();
365
}
366
}
367
368
/*
369
** Get the HTTP Authorization preference from db.
370
*/
371
char *get_httpauth(void){
372
char *zKey = mprintf("http-auth:%s", g.url.canonical);
373
char * rc = unobscure(db_get(zKey, 0));
374
free(zKey);
375
return rc;
376
}
377
378
/*
379
** Set the HTTP Authorization preference in db.
380
*/
381
void set_httpauth(const char *zHttpAuth){
382
db_set_mprintf(obscure(zHttpAuth), 0, "http-auth:%s", g.url.canonical);
383
}
384
385
/*
386
** Look for SSH clone command line options and setup in globals.
387
*/
388
void clone_ssh_find_options(void){
389
const char *zSshCmd; /* SSH command string */
390
391
zSshCmd = find_option("ssh-command","c",1);
392
if( zSshCmd && zSshCmd[0] ){
393
g.zSshCmd = fossil_strdup(zSshCmd);
394
}
395
}
396
397
/*
398
** Set SSH options discovered in global variables (set from command line
399
** options).
400
*/
401
void clone_ssh_db_set_options(void){
402
if( g.zSshCmd && g.zSshCmd[0] ){
403
db_unprotect(PROTECT_ALL);
404
db_set("ssh-command", g.zSshCmd, 0);
405
db_protect_pop();
406
}
407
}
408
409
/*
410
** WEBPAGE: howtoclone
411
**
412
** Provide instructions on how to clone this repository.
413
*/
414
void howtoclone_page(void){
415
login_check_credentials();
416
cgi_check_for_malice();
417
style_header("How To Clone This Repository");
418
if( !g.perm.Clone ){
419
@ <p>You are not authorized to clone this repository.
420
if( g.zLogin==0 || g.zLogin[0]==0 ){
421
@ Maybe you would be able to clone if you
422
@ %z(href("%R/login"))logged in</a>.
423
}else{
424
@ Contact the site administrator and ask them to give
425
@ you "Clone" privileges in order to clone.
426
}
427
}else{
428
const char *zNm = db_get("short-project-name","clone");
429
@ <p>Clone this repository by running a command like the following:
430
@ <blockquote><pre>
431
@ fossil clone %s(g.zBaseURL) %h(zNm).fossil
432
@ </pre></blockquote>
433
@ <p>Do a web search for "fossil clone" or similar to find additional
434
@ information about using a cloned Fossil repository.
435
}
436
style_finish_page();
437
}
438

Keyboard Shortcuts

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