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/allrepo.c | |
| download | relic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz | |
Diffstat (limited to 'src/allrepo.c')
| -rw-r--r-- | src/allrepo.c | 546 |
1 files changed, 546 insertions, 0 deletions
diff --git a/src/allrepo.c b/src/allrepo.c new file mode 100644 index 0000000..689e7b4 --- /dev/null +++ b/src/allrepo.c @@ -0,0 +1,546 @@ +/* +** Copyright (c) 2008 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 to implement the "all" command-line method. +*/ +#include "config.h" +#include "allrepo.h" +#include <assert.h> + +/* +** Build a string that contains all of the command-line options +** specified as arguments. collect_argument() is used for stand-alone +** options and collect_argument_value() is used for options that are +** followed by an argument value. +*/ +static void collect_argument(Blob *pExtra,const char *zArg,const char *zShort){ + const char *z = find_option(zArg, zShort, 0); + if( z!=0 ){ + blob_appendf(pExtra, " %s", z); + } +} +static void collect_argument_value( + Blob *pExtra, const char *zArg, const char *zShort +){ + const char *zValue = find_option(zArg, zShort, 1); + if( zValue ){ + if( zValue[0] ){ + blob_appendf(pExtra, " --%s %$", zArg, zValue); + }else{ + blob_appendf(pExtra, " --%s \"\"", zArg); + } + } +} +static void collect_argv(Blob *pExtra, int iStart){ + int i; + for(i=iStart; i<g.argc; i++){ + blob_appendf(pExtra, " %s", g.argv[i]); + } +} + +/* +** COMMAND: all abbrv-subcom +** +** Usage: %fossil all SUBCOMMAND ... +** +** The ~/.fossil file records the location of all repositories for a +** user. This command performs certain operations on all repositories +** that can be useful before or after a period of disconnected operation. +** +** On Win32 systems, the file is named "_fossil" and is located in +** %LOCALAPPDATA%, %APPDATA% or %HOMEPATH%. +** +** Available operations are: +** +** backup Backup all repositories. The argument must be the name of +** a directory into which all backup repositories are written. +** +** cache Manages the cache used for potentially expensive web +** pages. Any additional arguments are passed on verbatim +** to the cache command. +** +** changes Shows all local check-outs that have uncommitted changes. +** This operation has no additional options. +** +** clean Delete all "extra" files in all local check-outs. Extreme +** caution should be exercised with this command because its +** effects cannot be undone. Use of the --dry-run option to +** carefully review the local check-outs to be operated upon +** and the --whatif option to carefully review the files to +** be deleted beforehand is highly recommended. The command +** line options supported by the clean command itself, if any +** are present, are passed along verbatim. +** +** config Only the "config pull AREA" command works. +** +** dbstat Run the "dbstat" command on all repositories. +** +** extras Shows "extra" files from all local check-outs. The command +** line options supported by the extra command itself, if any +** are present, are passed along verbatim. +** +** fts-config Run the "fts-config" command on all repositories. +** +** git CMD Do the "git export" or "git status" command (whichever +** is specified by CMD) on all repositories for which +** a Git mirror has been previously established. +** +** info Run the "info" command on all repositories. +** +** pull Run a "pull" operation on all repositories. Only the +** --verbose and --share-links options are supported. +** +** push Run a "push" on all repositories. Only the --verbose +** option is supported. +** +** rebuild Rebuild on all repositories. The command line options +** supported by the rebuild command itself, if any are +** present, are passed along verbatim. The --force option +** is not supported. +** +** remote Show remote hosts for all repositories. +** +** repack Look for extra compression in all repositories. +** +** sync Run a "sync" on all repositories. Only the --verbose +** and --unversioned and --share-links options are supported. +** +** set[tings] Run the "settings" command on all repositories. +** This command is useful for settings like "max-loadavg" which +** you usually want to be the same across all repositories +** on a server. +** +** unset Run the "unset" command on all repositories +** +** server Run the "server" commands on all repositories. +** The root URI gives a listing of all repos. +** +** ui Run the "ui" command on all repositories. Like "server" +** but bind to the loopback TCP address only, enable +** the --localauth option and automatically launch a +** web-browser +** +** whatis Run the "whatis" command on all repositories. Only +** show output for repositories that have a match. +** +** +** In addition, the following maintenance operations are supported: +** +** add Add all the repositories named to the set of repositories +** tracked by Fossil. Normally Fossil is able to keep up with +** this list by itself, but sometimes it can benefit from this +** hint if you rename repositories. +** +** ignore Arguments are repositories that should be ignored by +** subsequent clean, extras, list, pull, push, rebuild, and +** sync operations. The -c|--ckout option causes the listed +** local check-outs to be ignored instead. +** +** list | ls Display the location of all repositories. The -c|--ckout +** option causes all local check-outs to be listed instead. +** +** Repositories are automatically added to the set of known repositories +** when one of the following commands are run against the repository: +** clone, info, pull, push, or sync. Even previously ignored repositories +** are added back to the list of repositories by these commands. +** +** Options: +** --dry-run Just display commands that would have run +** --showfile Show the repository or check-out being operated upon +** --stop-on-error Halt immediately if any subprocess fails +** -s|--stop Shorthand for "--stop-on-error" +*/ +void all_cmd(void){ + Stmt q; + const char *zCmd; + char *zSyscmd = 0; + Blob extra; + int bHalted = 0; + int rc = 0; + int useCheckouts = 0; + int quiet = 0; + int dryRunFlag = 0; + int showFile = find_option("showfile",0,0)!=0; + int stopOnError; + int nToDel = 0; + int showLabel = 0; + + (void)find_option("dontstop",0,0); /* Legacy. Now the default */ + stopOnError = find_option("stop-on-error",0,0)!=0; + if( find_option("stop","s",0)!=0 ) stopOnError = 1; + dryRunFlag = find_option("dry-run","n",0)!=0; + if( !dryRunFlag ){ + dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ + } + + if( g.argc<3 ){ + usage("SUBCOMMAND ..."); + } + db_open_config(1, 0); + blob_zero(&extra); + zCmd = g.argv[2]; + if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin); + if( fossil_strcmp(zCmd, "ui")==0 + || fossil_strcmp(zCmd, "server")==0 ){ + g.argv[1] = g.argv[2]; + g.argv[2] = "/"; + cmd_webserver(); + return; + } + if( fossil_strcmp(zCmd, "list")==0 || fossil_strcmp(zCmd,"ls")==0 ){ + zCmd = "list"; + useCheckouts = find_option("ckout","c",0)!=0; + }else if( fossil_strcmp(zCmd, "backup")==0 ){ + char *zDest; + zCmd = "backup -R"; + collect_argument(&extra, "overwrite",0); + if( g.argc!=4 ) usage("backup DIRECTORY"); + zDest = g.argv[3]; + if( file_isdir(zDest, ExtFILE)!=1 ){ + fossil_fatal("argument to \"fossil all backup\" must be a directory"); + } + blob_appendf(&extra, " %$", zDest); + }else if( fossil_strcmp(zCmd, "clean")==0 ){ + zCmd = "clean --chdir"; + collect_argument(&extra, "allckouts",0); + collect_argument_value(&extra, "case-sensitive", 0); + collect_argument_value(&extra, "clean", 0); + collect_argument(&extra, "dirsonly",0); + collect_argument(&extra, "disable-undo",0); + collect_argument(&extra, "dotfiles",0); + collect_argument(&extra, "emptydirs",0); + collect_argument(&extra, "force","f"); + collect_argument_value(&extra, "ignore", 0); + collect_argument_value(&extra, "keep", 0); + collect_argument(&extra, "no-prompt",0); + collect_argument(&extra, "temp",0); + collect_argument(&extra, "verbose","v"); + collect_argument(&extra, "whatif",0); + useCheckouts = 1; + }else if( fossil_strcmp(zCmd, "config")==0 ){ + zCmd = "config -R"; + collect_argv(&extra, 3); + (void)find_option("legacy",0,0); + (void)find_option("overwrite",0,0); + verify_all_options(); + if( g.argc!=5 || fossil_strcmp(g.argv[3],"pull")!=0 ){ + usage("configure pull AREA ?OPTIONS?"); + } + }else if( fossil_strcmp(zCmd, "dbstat")==0 ){ + zCmd = "dbstat --omit-version-info -R"; + showLabel = 1; + quiet = 1; + collect_argument(&extra, "brief", "b"); + collect_argument(&extra, "db-check", 0); + collect_argument(&extra, "db-verify", 0); + }else if( fossil_strcmp(zCmd, "extras")==0 ){ + if( showFile ){ + zCmd = "extras --chdir"; + }else{ + zCmd = "extras --header --chdir"; + } + collect_argument(&extra, "abs-paths",0); + collect_argument_value(&extra, "case-sensitive", 0); + collect_argument(&extra, "dotfiles",0); + collect_argument_value(&extra, "ignore", 0); + collect_argument(&extra, "rel-paths",0); + useCheckouts = 1; + stopOnError = 0; + quiet = 1; + }else if( fossil_strcmp(zCmd, "git")==0 ){ + if( g.argc<4 ){ + usage("git (export|status)"); + }else{ + if( fossil_strcmp(g.argv[3], "export")==0 ){ + zCmd = "git export --if-mirrored -R"; + }else if( fossil_strcmp(g.argv[3], "status")==0 ){ + zCmd = "git status --by-all -q -R"; + quiet = 1; + }else{ + usage("git (export|status)"); + } + } + }else if( fossil_strcmp(zCmd, "push")==0 ){ + zCmd = "push -autourl -R"; + collect_argument(&extra, "verbose","v"); + }else if( fossil_strcmp(zCmd, "pull")==0 ){ + zCmd = "pull -autourl -R"; + collect_argument(&extra, "verbose","v"); + collect_argument(&extra, "share-links",0); + }else if( fossil_strcmp(zCmd, "rebuild")==0 ){ + zCmd = "rebuild"; + collect_argument(&extra, "analyze",0); + collect_argument(&extra, "cluster",0); + collect_argument(&extra, "compress",0); + collect_argument(&extra, "compress-only",0); + collect_argument(&extra, "noverify",0); + collect_argument_value(&extra, "pagesize", 0); + collect_argument(&extra, "vacuum",0); + collect_argument(&extra, "deanalyze",0); /* Deprecated */ + collect_argument(&extra, "analyze",0); + collect_argument(&extra, "wal",0); + collect_argument(&extra, "stats",0); + collect_argument(&extra, "index",0); + collect_argument(&extra, "noindex",0); + collect_argument(&extra, "ifneeded", 0); + }else if( fossil_strcmp(zCmd, "remote")==0 ){ + showLabel = 1; + quiet = 1; + collect_argument(&extra, "show-passwords", 0); + if( g.argc==3 ){ + zCmd = "remote -R"; + }else if( g.argc!=4 ){ + usage("remote ?config-data|list|ls?"); + }else if( fossil_strcmp(g.argv[3],"ls")==0 + || fossil_strcmp(g.argv[3],"list")==0 ){ + zCmd = "remote ls -R"; + }else if( fossil_strcmp(g.argv[3],"ls")==0 + || fossil_strcmp(g.argv[3],"list")==0 ){ + zCmd = "remote ls -R"; + }else if( fossil_strcmp(g.argv[3],"config-data")==0 ){ + zCmd = "remote config-data -R"; + }else{ + usage("remote ?config-data|list|ls?"); + } + }else if( fossil_strcmp(zCmd, "repack")==0 ){ + zCmd = "repack"; + }else if( fossil_strcmp(zCmd, "set")==0 + || fossil_strcmp(zCmd, "setting")==0 + || fossil_strcmp(zCmd, "settings")==0 ){ + zCmd = "settings -R"; + collect_argument(&extra, "changed", 0); + collect_argv(&extra, 3); + }else if( fossil_strcmp(zCmd, "unset")==0 ){ + zCmd = "unset -R"; + collect_argv(&extra, 3); + }else if( fossil_strcmp(zCmd, "fts-config")==0 ){ + zCmd = "fts-config -R"; + collect_argv(&extra, 3); + }else if( fossil_strcmp(zCmd, "sync")==0 ){ + zCmd = "sync -autourl -R"; + collect_argument(&extra, "share-links",0); + collect_argument(&extra, "verbose","v"); + collect_argument(&extra, "unversioned","u"); + collect_argument(&extra, "all",0); + collect_argument(&extra, "quiet","q"); + collect_argument(&extra, "ping",0); + }else if( fossil_strcmp(zCmd, "test-integrity")==0 ){ + collect_argument(&extra, "db-only", "d"); + collect_argument(&extra, "parse", 0); + collect_argument(&extra, "quick", "q"); + zCmd = "test-integrity"; + }else if( fossil_strcmp(zCmd, "test-orphans")==0 ){ + zCmd = "test-orphans -R"; + }else if( fossil_strcmp(zCmd, "test-missing")==0 ){ + zCmd = "test-missing -q -R"; + collect_argument(&extra, "notshunned",0); + }else if( fossil_strcmp(zCmd, "changes")==0 ){ + zCmd = "changes --quiet --header --chdir"; + useCheckouts = 1; + stopOnError = 0; + quiet = 1; + }else if( fossil_strcmp(zCmd, "ignore")==0 ){ + int j; + Blob fn = BLOB_INITIALIZER; + Blob sql = BLOB_INITIALIZER; + useCheckouts = find_option("ckout","c",0)!=0; + verify_all_options(); + db_begin_transaction(); + for(j=3; j<g.argc; j++, blob_reset(&sql), blob_reset(&fn)){ + file_canonical_name(g.argv[j], &fn, useCheckouts?1:0); + blob_append_sql(&sql, + "DELETE FROM global_config WHERE name GLOB '%s:%q'", + useCheckouts?"ckout":"repo", blob_str(&fn) + ); + if( dryRunFlag ){ + fossil_print("%s\n", blob_sql_text(&sql)); + }else{ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", blob_sql_text(&sql)); + db_protect_pop(); + } + } + db_end_transaction(0); + blob_reset(&sql); + blob_reset(&fn); + blob_reset(&extra); + return; + }else if( fossil_strcmp(zCmd, "add")==0 ){ + int j; + Blob fn = BLOB_INITIALIZER; + Blob sql = BLOB_INITIALIZER; + verify_all_options(); + db_begin_transaction(); + for(j=3; j<g.argc; j++, blob_reset(&fn), blob_reset(&sql)){ + sqlite3 *db; + int rc; + const char *z; + file_canonical_name(g.argv[j], &fn, 0); + z = blob_str(&fn); + if( !file_isfile(z, ExtFILE) ) continue; + g.dbIgnoreErrors++; + rc = sqlite3_open(z, &db); + if( rc!=SQLITE_OK ){ sqlite3_close(db); g.dbIgnoreErrors--; continue; } + rc = sqlite3_exec(db, "SELECT rcvid FROM blob, delta LIMIT 1", 0, 0, 0); + sqlite3_close(db); + g.dbIgnoreErrors--; + if( rc!=SQLITE_OK ) continue; + blob_append_sql(&sql, + "INSERT OR IGNORE INTO global_config(name,value)" + "VALUES('repo:%q',1)", z + ); + if( dryRunFlag ){ + fossil_print("%s\n", blob_sql_text(&sql)); + }else{ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", blob_sql_text(&sql)); + db_protect_pop(); + } + } + db_end_transaction(0); + blob_reset(&sql); + blob_reset(&fn); + blob_reset(&extra); + return; + }else if( fossil_strcmp(zCmd, "info")==0 ){ + zCmd = "info"; + showLabel = 1; + quiet = 1; + }else if( fossil_strcmp(zCmd, "cache")==0 ){ + zCmd = "cache -R"; + showLabel = 1; + collect_argv(&extra, 3); + }else if( fossil_strcmp(zCmd, "whatis")==0 ){ + zCmd = "whatis -q -R"; + quiet = 1; + collect_argument(&extra, "file", "f"); + collect_argument_value(&extra, "type", 0); + collect_argv(&extra, 3); + }else{ + fossil_fatal("\"all\" subcommand should be one of: " + "add cache changes clean dbstat extras fts-config git ignore " + "info list ls pull push rebuild remote " + "server settings sync ui unset whatis"); + } + verify_all_options(); + db_multi_exec( + "CREATE TEMP TABLE repolist(\n" + " name TEXT, -- Filename\n" + " tag TEXT, -- Key for the GLOBAL_CONFIG table entry\n" + " inode TEXT -- Unique identifier for this file\n" + ");\n" + + /* The seenFile() table holds inode names for entries that have + ** already been processed. */ + "CREATE TEMP TABLE seenFile(x TEXT COLLATE nocase);\n" + + /* The toDel() table holds the "tag" for entries that need to be + ** deleted because they are redundant or no longer exist */ + "CREATE TEMP TABLE toDel(x TEXT);\n" + ); + sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, + file_inode_sql_func, 0, 0); + if( useCheckouts ){ + db_multi_exec( + "INSERT INTO repolist " + "SELECT substr(name, 7), name, inode(substr(name,7))" + " FROM global_config" + " WHERE substr(name, 1, 6)=='ckout:'" + " ORDER BY 1" + ); + }else{ + db_multi_exec( + "INSERT INTO repolist " + "SELECT substr(name, 6), name, inode(substr(name,6))" + " FROM global_config" + " WHERE substr(name, 1, 5)=='repo:'" + " ORDER BY 1" + ); + } + db_prepare(&q,"SELECT name, tag, inode FROM repolist ORDER BY 1"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFilename = db_column_text(&q, 0); + const char *zInode = db_column_text(&q,2); +#if !USE_SEE + if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue; +#endif + if( file_access(zFilename, F_OK) + || !file_is_canonical(zFilename) + || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1) + || db_exists("SELECT 1 FROM temp.seenFile where x=%Q", zInode) + ){ + db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1)); + nToDel++; + continue; + } + db_multi_exec("INSERT INTO seenFile(x) VALUES(%Q)", zInode); + if( zCmd[0]=='l' ){ + fossil_print("%s\n", zFilename); + continue; + }else if( showFile ){ + fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository", + zFilename); + } + zSyscmd = mprintf("%$ %s %$%s", + g.nameOfExe, zCmd, zFilename, blob_str(&extra)); + if( showLabel ){ + int len = (int)strlen(zFilename); + int nStar = 80 - (len + 15); + if( nStar<2 ) nStar = 1; + fossil_print("%.13c %s %.*c\n", '*', zFilename, nStar, '*'); + fflush(stdout); + } + if( !quiet || dryRunFlag ){ + fossil_print("%s\n", zSyscmd); + fflush(stdout); + } + rc = dryRunFlag ? 0 : fossil_system(zSyscmd); + if( rc ){ + if( stopOnError ){ + bHalted = 1; + break; + } + /* If there is an error, pause briefly, but do not stop. The brief + ** pause is so that if the prior command failed with Ctrl-C then there + ** will be time to stop the whole thing with a second Ctrl-C. */ + sqlite3_sleep(330); + } + fossil_free(zSyscmd); + } + db_finalize(&q); + + blob_reset(&extra); + + /* If any repositories whose names appear in the ~/.fossil file could not + ** be found, remove those names from the ~/.fossil file. + */ + if( nToDel>0 ){ + const char *zSql = "DELETE FROM global_config WHERE name IN toDel"; + if( dryRunFlag ){ + fossil_print("%s\n", zSql); + }else{ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", zSql /*safe-for-%s*/ ); + db_protect_pop(); + } + } + + if( stopOnError && bHalted ){ + fossil_fatal("STOPPED: non-zero result code (%d) from\nSTOPPED: %s", + rc, zSyscmd); + } +} |
