diff options
| author | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
| commit | 4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch) | |
| tree | c296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/clone.c | |
| download | relic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz | |
Diffstat (limited to 'src/clone.c')
| -rw-r--r-- | src/clone.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/src/clone.c b/src/clone.c new file mode 100644 index 0000000..24e6117 --- /dev/null +++ b/src/clone.c @@ -0,0 +1,437 @@ +/* +** Copyright (c) 2007 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to clone a repository +*/ +#include "config.h" +#include "clone.h" +#include <assert.h> + +/* +** If there are public BLOBs that deltas from private BLOBs, then +** undeltify the public BLOBs so that the private BLOBs may be safely +** deleted. +*/ +void fix_private_blob_dependencies(int showWarning){ + Bag toUndelta; + Stmt q; + int rid; + + /* Careful: We are about to delete all BLOB entries that are private. + ** So make sure that any no public BLOBs are deltas from a private BLOB. + ** Otherwise after the deletion, we won't be able to recreate the public + ** BLOBs. + */ + db_prepare(&q, + "SELECT " + " rid, (SELECT uuid FROM blob WHERE rid=delta.rid)," + " srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)" + " FROM delta" + " WHERE srcid in private AND rid NOT IN private" + ); + bag_init(&toUndelta); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char *zId = db_column_text(&q, 1); + int srcid = db_column_int(&q, 2); + const char *zSrc = db_column_text(&q, 3); + if( showWarning ){ + fossil_warning( + "public artifact %S (%d) is a delta from private artifact %S (%d)", + zId, rid, zSrc, srcid + ); + } + bag_insert(&toUndelta, rid); + } + db_finalize(&q); + while( (rid = bag_first(&toUndelta))>0 ){ + content_undelta(rid); + bag_remove(&toUndelta, rid); + } + bag_clear(&toUndelta); +} + +/* +** Delete all private content from a repository. +*/ +void delete_private_content(void){ + fix_private_blob_dependencies(1); + db_multi_exec( + "DELETE FROM blob WHERE rid IN private;" + "DELETE FROM delta WHERE rid IN private;" + "DELETE FROM private;" + "DROP TABLE IF EXISTS modreq;" + ); +} + + +/* +** COMMAND: clone +** +** Usage: %fossil clone ?OPTIONS? URI ?FILENAME? +** +** Make a clone of a repository specified by URI in the local +** file named FILENAME. If FILENAME is omitted, then an appropriate +** filename is deduced from last element of the path in the URL. +** +** URI may be one of the following forms ([...] denotes optional elements): +** +** * HTTP/HTTPS protocol: +** +** http[s]://[userid[:password]@]host[:port][/path] +** +** * SSH protocol: +** +** ssh://[userid@]host[:port]/path/to/repo.fossil[?fossil=path/fossil.exe] +** +** * Filesystem: +** +** [file://]path/to/repo.fossil +** +** For ssh and filesystem, path must have an extra leading +** '/' to use an absolute path. +** +** Use %HH escapes for special characters in the userid and +** password. For example "%40" in place of "@", "%2f" in place +** of "/", and "%3a" in place of ":". +** +** Note that in Fossil (in contrast to some other DVCSes) a repository +** is distinct from a check-out. Cloning a repository is not the same thing +** as opening a repository. This command always clones the repository. This +** command might also open the repository, but only if the --no-open option +** is omitted and either the --workdir option is included or the FILENAME +** argument is omitted. Use the separate [[open]] command to open a +** repository that was previously cloned and already exists on the +** local machine. +** +** By default, the current login name is used to create the default +** admin user for the new clone. This can be overridden using +** the -A|--admin-user parameter. +** +** Options: +** -A|--admin-user USERNAME Make USERNAME the administrator +** -B|--httpauth USER:PASS Add HTTP Basic Authorization to requests +** --nested Allow opening a repository inside an opened +** check-out +** --nocompress Omit extra delta compression +** --no-open Clone only. Do not open a check-out. +** --once Don't remember the URI. +** --private Also clone private branches +** --proxy PROXY Use the specified HTTP proxy +** --save-http-password Remember the HTTP password without asking +** -c|--ssh-command SSH Use SSH as the "ssh" command +** --ssl-identity FILENAME Use the SSL identity if requested by the server +** --transport-command CMD Use CMD to move messages to the server and back +** -u|--unversioned Also sync unversioned content +** -v|--verbose Show more statistics in output +** --workdir DIR Also open a check-out in DIR +** --xverbose Extra debugging output +** +** See also: [[init]], [[open]] +*/ +void clone_cmd(void){ + char *zPassword; + const char *zDefaultUser; /* Optional name of the default user */ + const char *zHttpAuth; /* HTTP Authorization user:pass information */ + int nErr = 0; + int urlFlags = URL_PROMPT_PW | URL_REMEMBER; + int syncFlags = SYNC_CLONE; + int noCompress = find_option("nocompress",0,0)!=0; + int noOpen = find_option("no-open",0,0)!=0; + int allowNested = find_option("nested",0,0)!=0; /* Used by open */ + const char *zRepo = 0; /* Name of the new local repository file */ + const char *zWorkDir = 0; /* Open in this directory, if not zero */ + + + /* Also clone private branches */ + if( find_option("private",0,0)!=0 ) syncFlags |= SYNC_PRIVATE; + if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER; + if( find_option("save-http-password",0,0)!=0 ){ + urlFlags &= ~URL_PROMPT_PW; + urlFlags |= URL_REMEMBER_PW; + } + if( find_option("verbose","v",0)!=0) syncFlags |= SYNC_VERBOSE; + if( find_option("xverbose",0,0)!=0) syncFlags |= SYNC_XVERBOSE; + if( find_option("unversioned","u",0)!=0 ){ + syncFlags |= SYNC_UNVERSIONED; + if( syncFlags & SYNC_VERBOSE ){ + syncFlags |= SYNC_UV_TRACE; + } + } + zHttpAuth = find_option("httpauth","B",1); + zDefaultUser = find_option("admin-user","A",1); + zWorkDir = find_option("workdir", 0, 1); + clone_ssh_find_options(); + url_proxy_options(); + g.zHttpCmd = find_option("transport-command",0,1); + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc < 3 ){ + usage("?OPTIONS? FILE-OR-URL ?NEW-REPOSITORY?"); + } + db_open_config(0, 0); + if( g.argc==4 ){ + zRepo = g.argv[3]; + }else{ + char *zBase = url_to_repo_basename(g.argv[2]); + if( zBase==0 ){ + fossil_fatal( + "unable to guess a repository name from the url \"%s\".\n" + "give the repository filename as an additional argument.", + g.argv[2]); + } + zRepo = mprintf("./%s.fossil", zBase); + if( zWorkDir==0 ){ + zWorkDir = mprintf("./%s", zBase); + } + fossil_free(zBase); + } + if( -1 != file_size(zRepo, ExtFILE) ){ + fossil_fatal("file already exists: %s", zRepo); + } + /* Fail before clone if open will fail because inside an open check-out */ + if( zWorkDir!=0 && zWorkDir[0]!=0 && !noOpen ){ + if( db_open_local_v2(0, allowNested) ){ + fossil_fatal("there is already an open tree at %s", g.zLocalRoot); + } + } + url_parse(g.argv[2], urlFlags); + if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user; + if( g.url.isFile ){ + file_copy(g.url.name, zRepo); + db_close(1); + db_open_repository(zRepo); + db_open_config(1,0); + db_record_repository_filename(zRepo); + url_remember(); + if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content(); + shun_artifacts(); + db_create_default_users(1, zDefaultUser); + if( zDefaultUser ){ + g.zLogin = zDefaultUser; + }else{ + g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'"); + } + fossil_print("Repository cloned into %s\n", zRepo); + }else{ + db_close_config(); + db_create_repository(zRepo); + db_open_repository(zRepo); + db_open_config(0,0); + db_begin_transaction(); + db_record_repository_filename(zRepo); + db_initial_setup(0, 0, zDefaultUser); + user_select(); + db_set("content-schema", CONTENT_SCHEMA, 0); + db_set("aux-schema", AUX_SCHEMA_MAX, 0); + db_set("rebuilt", get_version(), 0); + db_unset("hash-policy", 0); + remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, g.argv[2]); + url_remember(); + if( g.zSSLIdentity!=0 ){ + /* If the --ssl-identity option was specified, store it as a setting */ + Blob fn; + blob_zero(&fn); + file_canonical_name(g.zSSLIdentity, &fn, 0); + db_unprotect(PROTECT_ALL); + db_set("ssl-identity", blob_str(&fn), 0); + db_protect_pop(); + blob_reset(&fn); + } + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "REPLACE INTO config(name,value,mtime)" + " VALUES('server-code', lower(hex(randomblob(20))), now());" + "DELETE FROM config WHERE name='project-code';" + ); + db_protect_pop(); + url_enable_proxy(0); + clone_ssh_db_set_options(); + url_get_password_if_needed(); + g.xlinkClusterOnly = 1; + nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0,0); + g.xlinkClusterOnly = 0; + verify_cancel(); + db_end_transaction(0); + db_close(1); + if( nErr ){ + file_delete(zRepo); + if( g.fHttpTrace ){ + fossil_fatal( + "server returned an error - clone aborted\n\n%s", + http_last_trace_reply() + ); + }else{ + fossil_fatal( + "server returned an error - clone aborted\n" + "Rerun using --httptrace for more detail" + ); + } + } + db_open_repository(zRepo); + } + db_begin_transaction(); + if( db_exists("SELECT 1 FROM delta WHERE srcId IN phantom") ){ + fossil_fatal("there are unresolved deltas -" + " the clone is probably incomplete and unusable."); + } + fossil_print("Rebuilding repository meta-data...\n"); + rebuild_db(1, 0); + if( !noCompress ){ + int nDelta = 0; + i64 nByte; + fossil_print("Extra delta compression... "); fflush(stdout); + nByte = extra_deltification(&nDelta); + if( nDelta==1 ){ + fossil_print("1 delta saves %,lld bytes\n", nByte); + }else if( nDelta>1 ){ + fossil_print("%d deltas save %,lld bytes\n", nDelta, nByte); + }else{ + fossil_print("none found\n"); + } + } + db_end_transaction(0); + fossil_print("Vacuuming the database... "); fflush(stdout); + if( db_int(0, "PRAGMA page_count")>1000 + && db_int(0, "PRAGMA page_size")<8192 ){ + db_multi_exec("PRAGMA page_size=8192;"); + } + db_unprotect(PROTECT_ALL); + db_multi_exec("VACUUM"); + db_protect_pop(); + fossil_print("\nproject-id: %s\n", db_get("project-code", 0)); + fossil_print("server-id: %s\n", db_get("server-code", 0)); + zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); + fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword); + hash_user_password(g.zLogin); + if( zWorkDir!=0 && zWorkDir[0]!=0 && !noOpen ){ + Blob cmd; + fossil_print("opening the new %s repository in directory %s...\n", + zRepo, zWorkDir); + blob_init(&cmd, 0, 0); + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_append(&cmd, " open ", -1); + blob_append_escaped_arg(&cmd, zRepo, 1); + blob_append(&cmd, " --nosync --workdir ", -1); + blob_append_escaped_arg(&cmd, zWorkDir, 1); + if( allowNested ){ + blob_append(&cmd, " --nested", -1); + } + fossil_system(blob_str(&cmd)); + blob_reset(&cmd); + } +} + +/* +** If user chooses to use HTTP Authentication over unencrypted HTTP, +** remember decision. Otherwise, if the URL is being changed and no +** preference has been indicated, err on the safe side and revert the +** decision. Set the global preference if the URL is not being changed. +*/ +void remember_or_get_http_auth( + const char *zHttpAuth, /* Credentials in the form "user:password" */ + int fRemember, /* True to remember credentials for later reuse */ + const char *zUrl /* URL for which these credentials apply */ +){ + if( zHttpAuth && zHttpAuth[0] ){ + g.zHttpAuth = fossil_strdup(zHttpAuth); + } + if( fRemember ){ + if( g.zHttpAuth && g.zHttpAuth[0] ){ + set_httpauth(g.zHttpAuth); + }else if( zUrl && zUrl[0] ){ + db_unset_mprintf(0, "http-auth:%s", g.url.canonical); + }else{ + g.zHttpAuth = get_httpauth(); + } + }else if( g.zHttpAuth==0 && zUrl==0 ){ + g.zHttpAuth = get_httpauth(); + } +} + +/* +** Get the HTTP Authorization preference from db. +*/ +char *get_httpauth(void){ + char *zKey = mprintf("http-auth:%s", g.url.canonical); + char * rc = unobscure(db_get(zKey, 0)); + free(zKey); + return rc; +} + +/* +** Set the HTTP Authorization preference in db. +*/ +void set_httpauth(const char *zHttpAuth){ + db_set_mprintf(obscure(zHttpAuth), 0, "http-auth:%s", g.url.canonical); +} + +/* +** Look for SSH clone command line options and setup in globals. +*/ +void clone_ssh_find_options(void){ + const char *zSshCmd; /* SSH command string */ + + zSshCmd = find_option("ssh-command","c",1); + if( zSshCmd && zSshCmd[0] ){ + g.zSshCmd = fossil_strdup(zSshCmd); + } +} + +/* +** Set SSH options discovered in global variables (set from command line +** options). +*/ +void clone_ssh_db_set_options(void){ + if( g.zSshCmd && g.zSshCmd[0] ){ + db_unprotect(PROTECT_ALL); + db_set("ssh-command", g.zSshCmd, 0); + db_protect_pop(); + } +} + +/* +** WEBPAGE: howtoclone +** +** Provide instructions on how to clone this repository. +*/ +void howtoclone_page(void){ + login_check_credentials(); + cgi_check_for_malice(); + style_header("How To Clone This Repository"); + if( !g.perm.Clone ){ + @ <p>You are not authorized to clone this repository. + if( g.zLogin==0 || g.zLogin[0]==0 ){ + @ Maybe you would be able to clone if you + @ %z(href("%R/login"))logged in</a>. + }else{ + @ Contact the site administrator and ask them to give + @ you "Clone" privileges in order to clone. + } + }else{ + const char *zNm = db_get("short-project-name","clone"); + @ <p>Clone this repository by running a command like the following: + @ <blockquote><pre> + @ fossil clone %s(g.zBaseURL) %h(zNm).fossil + @ </pre></blockquote> + @ <p>Do a web search for "fossil clone" or similar to find additional + @ information about using a cloned Fossil repository. + } + style_finish_page(); +} |
