{"thread":{"id":"53164","subject":"[PATCH 01/15] run-job: create barebones builtin","startedAt":"2020-04-03T20:48:20Z","lastAt":"2020-05-28T21:13:48Z","messageCount":55,"participants":["Derrick Stolee via GitGitGadget","Junio C Hamano","Derrick Stolee","Phillip Wood","Danh Doan","brian m. carlson","Johannes Schindelin","Josh Steadmon","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":15},"messages":[{"id":"394701","messageId":"665da239774419074a9bae49b9c92b340885bfa3.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 01/15] run-job: create barebones builtin","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:00Z","receivedAt":"2020-04-03T20:48:20Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git run-job' command will be used to execute a short-lived set\nof maintenance activities by a background job manager. The intention\nis to perform small batches of work that reduce the foreground time\ntaken by repository maintenance such as 'git gc --auto'.\n\nThis change does the absolute minimum to create the builtin and show\nthe usage output.\n\nProvide an explicit warning that this command is experimental. The\nset of jobs may change, and each job could alter its behavior in\nfuture versions.\n\nRFC QUESTION: This builtin is based on the background maintenance in\nScalar. Specifically, this builtin is based on the \"scalar run <job>\"\ncommand [1] [2]. My default thought was to make this a \"git run <job>\"\ncommand to maximize similarity. However, it seems like \"git run\" is\ntoo generic. Or, am I being overly verbose for no reason?\n\n[1] https://github.com/microsoft/scalar/blob/master/docs/advanced.md#run-maintenance-in-the-foreground\n[2] https://github.com/microsoft/scalar/blob/master/Scalar/CommandLine/RunVerb.cs\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n .gitignore                    |  1 +\n Documentation/git-run-job.txt | 36 +++++++++++++++++++++++++++++++++++\n Makefile                      |  1 +\n builtin.h                     |  1 +\n builtin/run-job.c             | 28 +++++++++++++++++++++++++++\n command-list.txt              |  1 +\n git.c                         |  1 +\n t/t7900-run-job.sh            | 15 +++++++++++++++\n 8 files changed, 84 insertions(+)\n create mode 100644 Documentation/git-run-job.txt\n create mode 100644 builtin/run-job.c\n create mode 100755 t/t7900-run-job.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 188bd1c3de1..5dea9d3b96b 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -144,6 +144,7 @@\n /git-rev-parse\n /git-revert\n /git-rm\n+/git-run-job\n /git-send-email\n /git-send-pack\n /git-serve\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nnew file mode 100644\nindex 00000000000..0627b3ed259\n--- /dev/null\n+++ b/Documentation/git-run-job.txt\n@@ -0,0 +1,36 @@\n+git-run-job(1)\n+==============\n+\n+NAME\n+----\n+git-run-job - Run a maintenance job. Intended for background operation.\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git run-job <job-name>'\n+\n+\n+DESCRIPTION\n+-----------\n+\n+Run a maintenance job on the current repository. This is available as a\n+command for a few reasons. First, the background job feature can launch\n+these commands on a schedule and each process will completely clear its\n+memory when complete. Second, an expert user could create their own job\n+schedule by running these jobs themselves.\n+\n+THIS COMMAND IS EXPERIMENTAL. THE SET OF AVAILABLE JOBS OR THEIR EXACT\n+BEHAVIOR MAY BE ALTERED IN THE FUTURE.\n+\n+\n+JOBS\n+----\n+\n+TBD\n+\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f0..f5f9c4d9e94 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1125,6 +1125,7 @@ BUILTIN_OBJS += builtin/rev-list.o\n BUILTIN_OBJS += builtin/rev-parse.o\n BUILTIN_OBJS += builtin/revert.o\n BUILTIN_OBJS += builtin/rm.o\n+BUILTIN_OBJS += builtin/run-job.o\n BUILTIN_OBJS += builtin/send-pack.o\n BUILTIN_OBJS += builtin/shortlog.o\n BUILTIN_OBJS += builtin/show-branch.o\ndiff --git a/builtin.h b/builtin.h\nindex 2b25a80cde3..3e0ddaaf67f 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -220,6 +220,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix);\n int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n int cmd_revert(int argc, const char **argv, const char *prefix);\n int cmd_rm(int argc, const char **argv, const char *prefix);\n+int cmd_run_job(int argc, const char **argv, const char *prefix);\n int cmd_send_pack(int argc, const char **argv, const char *prefix);\n int cmd_shortlog(int argc, const char **argv, const char *prefix);\n int cmd_show(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nnew file mode 100644\nindex 00000000000..2c78d053aa4\n--- /dev/null\n+++ b/builtin/run-job.c\n@@ -0,0 +1,28 @@\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"parse-options.h\"\n+\n+static char const * const builtin_run_job_usage[] = {\n+\tN_(\"git run-job\"),\n+\tNULL\n+};\n+\n+int cmd_run_job(int argc, const char **argv, const char *prefix)\n+{\n+\tstatic struct option builtin_run_job_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(builtin_run_job_usage,\n+\t\t\t\t   builtin_run_job_options);\n+\n+\tgit_config(git_default_config, NULL);\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_run_job_options,\n+\t\t\t     builtin_run_job_usage,\n+\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tusage_with_options(builtin_run_job_usage,\n+\t\t\t   builtin_run_job_options);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex 20878946558..1cd2b415e46 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -156,6 +156,7 @@ git-revert                              mainporcelain\n git-rev-list                            plumbinginterrogators\n git-rev-parse                           plumbinginterrogators\n git-rm                                  mainporcelain           worktree\n+git-run-job                             plumbingmanipulators\n git-send-email                          foreignscminterface             complete\n git-send-pack                           synchingrepositories\n git-shell                               synchelpers\ndiff --git a/git.c b/git.c\nindex b07198fe036..db5a43c8687 100644\n--- a/git.c\n+++ b/git.c\n@@ -566,6 +566,7 @@ static struct cmd_struct commands[] = {\n \t{ \"rev-parse\", cmd_rev_parse, NO_PARSEOPT },\n \t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"rm\", cmd_rm, RUN_SETUP },\n+\t{ \"run-job\", cmd_run_job, RUN_SETUP },\n \t{ \"send-pack\", cmd_send_pack, RUN_SETUP },\n \t{ \"shortlog\", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },\n \t{ \"show\", cmd_show, RUN_SETUP },\ndiff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\nnew file mode 100755\nindex 00000000000..1eac80b7ed3\n--- /dev/null\n+++ b/t/t7900-run-job.sh\n@@ -0,0 +1,15 @@\n+#!/bin/sh\n+\n+test_description='git run-job\n+\n+Testing the background jobs, in the foreground\n+'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'help text' '\n+\ttest_must_fail git run-job -h 2>err &&\n+\ttest_i18ngrep \"usage: git run-job\" err\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"394702","messageId":"pull.597.git.1585946894.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":null,"subject":"[PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:47:59Z","receivedAt":"2020-04-03T20:48:20Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"Background maintenance. I've blogged about it [1]. I've talked about it [2].\nI brought it up at the contributor summit [3]. I think it's important. It's\na critical feature in Scalar and VFS for Git. It's the main reason we\ncreated the \"scalar register\" feature for existing repos (so the Scalar\nbackground maintenance would apply to a \"vanilla\" Git repo).\n\n[1] https://devblogs.microsoft.com/devops/introducing-scalar/[2] \nhttps://stolee.dev/docs/git-merge-2020.pdf[3] \nhttps://lore.kernel.org/git/35FDF767-CE0C-4C51-88A8-12965CD2D4FF@jramsay.com.au/\n\nThis RFC does the \"maintenance\" half of \"background maintenance\". It creates\ntwo new builtins to assist users in scheduling their own background\nmaintenance:\n\n * git run-job <job-name>: This builtin will run a single instance of a\n   maintenance job.\n   \n   \n * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n   that executes git run-job as a subcommand.\n   \n   \n\nI believe that I could replace most maintenance steps in Scalar with the git\nrun-job command and all that we would lose is some logging. (The only one\nthat would remain is the one that sets recommended config settings, but that\ncould be changed to a one-time operation at service start-up.) I haven't\ntested this yet, but I plan to before sending a non-RFC option.\n\nOf course, just because we think these maintenance operations work for our\nscenario does not mean that they immediately work as the \"canonical\"\nmaintenance activities. I attempt to demonstrate the value of these jobs as\nthey are implemented. My perspective is skewed towards very large\nrepositories (2000+ full-time contributors), but even medium-size\nrepositories (100-200 full-time contributors) can benefit from these jobs.\n\nI've tried to split the series into logical commits. This includes each\nspecific maintenance job being completely described in its own commit (plus\nmaybe an extra one to allow a custom option).\n\nMost commit messages have \"RFC QUESTION(S)\" for things I was unsure about.\n\nI'm currently testing this locally by setting job.repo in my global config\nto be a few important repos on my Linux VM then running \nGIT_TRACE2_PERF=<path> git job-runner --daemonize to launch a background\nprocess that logs the subcommands to <path>.\n\nHere I will call out things that could definitely be improved:\n\n 1. The git job-runner process is not tested AT ALL. It's a bit tricky to\n    test it since it shouldn't exit when things work as expected. I expect\n    to create a --no-loop option to stop after one iteration of the job\n    loop. But I would like a bit more feedback on the concept before jumping\n    into those tests. (I do test the git run-job builtin for each job, but\n    not the customization through arguments and config.) Perhaps we could do\n    some funny business about mocking git using --exec-path to check that it\n    is calling git run-job in the correct order (and more importantly, not \n    calling it when certain config settings are present).\n    \n    \n 2. The --daemonize option at the end is shamelessly stolen from git gc\n    --daemonize and git daemon, but has limited abilities on some platforms\n    (I've tested on Linux and macOS). I have not done my research on how far\n    this gets us to allowing users to launch this at startup or something. \n    \n    \n 3. As I said, this is the \"maintenance\" \"half\" of \"background maintenance\".\n    The \"background\" part is harder in my opinion because it involves\n    creating platform-specific ways to consistently launch background\n    processes. For example, Unix systems should have one way to service\n    start X while Windows has another. macOS has launchd to launch processes\n    as users log in, which should be a good way forward. Scalar implements a\n    Windows Service that runs as root but impersonates the latest user to\n    log in, and it implements a macOS \"service\" that is running only with\n    the current user. I expect to need to create these services myself as a\n    follow-up, but I lack the expertise to do it (currently). If someone\n    else has experience creating these things and wants to take over or\n    advise that half then I would appreciate the help!\n    \n    \n 4. I noticed late in the RFC process that I'm not clearing my argv_arrays\n    carefully in the job-runner. This will need to be rectified and\n    carefully checked with valgrind before merging this code. While it leaks\n    memory very slowly, it will be important that we don't leak any memory\n    at all since this is a long-lived process. There's also some places\n    where I was careful to not include too much of libgit.a to help keep the\n    memory footprint low.\n    \n    \n\nThanks, -Stolee\n\nDerrick Stolee (15):\n  run-job: create barebones builtin\n  run-job: implement commit-graph job\n  run-job: implement fetch job\n  run-job: implement loose-objects job\n  run-job: implement pack-files job\n  run-job: auto-size or use custom pack-files batch\n  config: add job.pack-files.batchSize option\n  job-runner: create builtin for job loop\n  job-runner: load repos from config by default\n  job-runner: use config to limit job frequency\n  job-runner: use config for loop interval\n  job-runner: add --interval=<span> option\n  job-runner: skip a job if job.<job-name>.enabled is false\n  job-runner: add --daemonize option\n  runjob: customize the loose-objects batch size\n\n .gitignore                       |   2 +\n Documentation/config.txt         |   2 +\n Documentation/config/job.txt     |  37 +++\n Documentation/git-job-runner.txt |  63 +++++\n Documentation/git-run-job.txt    | 102 +++++++\n Makefile                         |   2 +\n builtin.h                        |   2 +\n builtin/job-runner.c             | 347 +++++++++++++++++++++++\n builtin/run-job.c                | 458 +++++++++++++++++++++++++++++++\n cache.h                          |   4 +-\n command-list.txt                 |   2 +\n commit-graph.c                   |   2 +-\n commit-graph.h                   |   1 +\n daemon.h                         |   7 +\n git.c                            |   2 +\n midx.c                           |   2 +-\n midx.h                           |   1 +\n t/t7900-run-job.sh               | 137 +++++++++\n 18 files changed, 1168 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/config/job.txt\n create mode 100644 Documentation/git-job-runner.txt\n create mode 100644 Documentation/git-run-job.txt\n create mode 100644 builtin/job-runner.c\n create mode 100644 builtin/run-job.c\n create mode 100644 daemon.h\n create mode 100755 t/t7900-run-job.sh\n\n\nbase-commit: 9fadedd637b312089337d73c3ed8447e9f0aa775\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-597%2Fderrickstolee%2Fjobs-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-597/derrickstolee/jobs-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/597\n-- \ngitgitgadget\n"},{"id":"394703","messageId":"cd3facd1e03923204bd2259ff0635a5bbf68d700.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 02/15] run-job: implement commit-graph job","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:01Z","receivedAt":"2020-04-03T20:48:21Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe first job to implement in the 'git run-job' command is the\n'commit-graph' job. It is based on the sequence of events in the\n'commit-graph' job in Scalar [1]. This sequence is as follows:\n\n1. git commit-graph write --reachable --split\n2. git commit-graph verify --shallow\n3. If the verify succeeds, stop.\n4. Delete the commit-graph-chain file.\n5. git commit-graph write --reachable --split\n\nBy writing an incremental commit-graph file using the \"--split\"\noption we minimize the disruption from this operation. The default\nbehavior is to merge layers until the new \"top\" layer is less than\nhalf the size of the layer below. This provides quick writes \"most\"\nof the time, with the longer writes following a power law\ndistribution.\n\nMost importantly, concurrent Git processes only look at the\ncommit-graph-chain file for a very short amount of time, so they\nwill verly likely not be holding a handle to the file when we try\nto replace it. (This only matters on Windows.)\n\nIf a concurrent process reads the old commit-graph-chain file, but\nour job expires some of the .graph files before they can be read,\nthen those processes will see a warning message (but not fail).\nThis could be avoided by a future update to use the --expire-time\nargument when writing the commit-graph.\n\nBy using 'git commit-graph verify --shallow' we can ensure that\nthe file we just wrote is valid. This is an extra safety precaution\nthat is faster than our 'write' subcommand. In the rare situation\nthat the newest layer of the commit-graph is corrupt, we can \"fix\"\nthe corruption by deleting the commit-graph-chain file and rewrite\nthe full commit-graph as a new one-layer commit graph. This does\nnot completely prevent _that_ file from being corrupt, but it does\nrecompute the commit-graph by parsing commits from the object\ndatabase. In our use of this step in Scalar and VFS for Git, we\nhave only seen this issue arise because our microsoft/git fork\nreverted 43d3561 (\"commit-graph write: don't die if the existing\ngraph is corrupt\" 2019-03-25) for a while to keep commit-graph\nwrites very fast. We dropped the revert when updating to v2.23.0.\nThe verify still has potential for catching corrupt data across\nthe layer boundary: if the new file has commit X with parent Y\nin an old file but the commit ID for Y in the old file had a\nbitswap, then we will notice that in the 'verify' command.\n\n[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/CommitGraphStep.cs\n\nRFC QUESTIONS:\n\n1. Are links like [1] helpful?\n\n2. Can anyone think of a way to test the rewrite fallback?\n   It requires corrupting the latest file between two steps of\n   this one command, so that is a tricky spot to inject a fault.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-run-job.txt | 21 ++++++++++--\n builtin/run-job.c             | 60 ++++++++++++++++++++++++++++++++++-\n commit-graph.c                |  2 +-\n commit-graph.h                |  1 +\n t/t7900-run-job.sh            | 32 +++++++++++++++++++\n 5 files changed, 112 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nindex 0627b3ed259..8bf2762d650 100644\n--- a/Documentation/git-run-job.txt\n+++ b/Documentation/git-run-job.txt\n@@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n SYNOPSIS\n --------\n [verse]\n-'git run-job <job-name>'\n+'git run-job commit-graph'\n \n \n DESCRIPTION\n@@ -28,7 +28,24 @@ BEHAVIOR MAY BE ALTERED IN THE FUTURE.\n JOBS\n ----\n \n-TBD\n+'commit-graph'::\n+\n+The `commit-graph` job updates the `commit-graph` files incrementally,\n+then verifies that the written data is correct. If the new layer has an\n+issue, then the chain file is removed and the `commit-graph` is\n+rewritten from scratch.\n++\n+The verification only checks the top layer of the `commit-graph` chain.\n+If the incremental write merged the new commits with at least one\n+existing layer, then there is potential for on-disk corruption being\n+carried forward into the new file. This will be noticed and the new\n+commit-graph file will be clean as Git reparses the commit data from\n+the object database.\n++\n+The incremental write is safe to run alongside concurrent Git processes\n+since it will not expire `.graph` files that were in the previous\n+`commit-graph-chain` file. They will be deleted by a later run based on\n+the expiration delay.\n \n \n GIT\ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nindex 2c78d053aa4..dd7709952d3 100644\n--- a/builtin/run-job.c\n+++ b/builtin/run-job.c\n@@ -1,12 +1,65 @@\n #include \"builtin.h\"\n #include \"config.h\"\n+#include \"commit-graph.h\"\n+#include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"repository.h\"\n+#include \"run-command.h\"\n \n static char const * const builtin_run_job_usage[] = {\n-\tN_(\"git run-job\"),\n+\tN_(\"git run-job commit-graph\"),\n \tNULL\n };\n \n+static int run_write_commit_graph(void)\n+{\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\n+\targv_array_pushl(&cmd, \"commit-graph\", \"write\",\n+\t\t\t \"--split\", \"--reachable\", \"--no-progress\",\n+\t\t\t NULL);\n+\n+\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+}\n+\n+static int run_verify_commit_graph(void)\n+{\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\n+\targv_array_pushl(&cmd, \"commit-graph\", \"verify\",\n+\t\t\t \"--shallow\", \"--no-progress\", NULL);\n+\n+\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+}\n+\n+static int run_commit_graph_job(void)\n+{\n+\tchar *chain_path;\n+\n+\tif (run_write_commit_graph()) {\n+\t\terror(_(\"failed to write commit-graph\"));\n+\t\treturn 1;\n+\t}\n+\n+\tif (!run_verify_commit_graph())\n+\t\treturn 0;\n+\n+\twarning(_(\"commit-graph verify caught error, rewriting\"));\n+\n+\tchain_path = get_chain_filename(the_repository->objects->odb);\n+\tif (unlink(chain_path)) {\n+\t\tUNLEAK(chain_path);\n+\t\tdie(_(\"failed to remove commit-graph at %s\"), chain_path);\n+\t}\n+\tfree(chain_path);\n+\n+\tif (!run_write_commit_graph())\n+\t\treturn 0;\n+\n+\terror(_(\"failed to rewrite commit-graph\"));\n+\treturn 1;\n+}\n+\n int cmd_run_job(int argc, const char **argv, const char *prefix)\n {\n \tstatic struct option builtin_run_job_options[] = {\n@@ -23,6 +76,11 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n \t\t\t     builtin_run_job_usage,\n \t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n \n+\tif (argc > 0) {\n+\t\tif (!strcmp(argv[0], \"commit-graph\"))\n+\t\t\treturn run_commit_graph_job();\n+\t}\n+\n \tusage_with_options(builtin_run_job_usage,\n \t\t\t   builtin_run_job_options);\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex f013a84e294..6867f92d04a 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -56,7 +56,7 @@ static char *get_split_graph_filename(struct object_directory *odb,\n \t\t       oid_hex);\n }\n \n-static char *get_chain_filename(struct object_directory *odb)\n+char *get_chain_filename(struct object_directory *odb)\n {\n \treturn xstrfmt(\"%s/info/commit-graphs/commit-graph-chain\", odb->path);\n }\ndiff --git a/commit-graph.h b/commit-graph.h\nindex e87a6f63600..8b7bd5a6cb1 100644\n--- a/commit-graph.h\n+++ b/commit-graph.h\n@@ -13,6 +13,7 @@\n struct commit;\n \n char *get_commit_graph_filename(struct object_directory *odb);\n+char *get_chain_filename(struct object_directory *odb);\n int open_commit_graph(const char *graph_file, int *fd, struct stat *st);\n \n /*\ndiff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\nindex 1eac80b7ed3..18b9bd26b3a 100755\n--- a/t/t7900-run-job.sh\n+++ b/t/t7900-run-job.sh\n@@ -5,6 +5,8 @@ test_description='git run-job\n Testing the background jobs, in the foreground\n '\n \n+GIT_TEST_COMMIT_GRAPH=0\n+\n . ./test-lib.sh\n \n test_expect_success 'help text' '\n@@ -12,4 +14,34 @@ test_expect_success 'help text' '\n \ttest_i18ngrep \"usage: git run-job\" err\n '\n \n+test_expect_success 'commit-graph job' '\n+\tgit init server &&\n+\t(\n+\t\tcd server &&\n+\t\tchain=.git/objects/info/commit-graphs/commit-graph-chain &&\n+\t\tgit checkout -b master &&\n+\t\tfor i in $(test_seq 1 15)\n+\t\tdo\n+\t\t\ttest_commit $i || return 1\n+\t\tdone &&\n+\t\tgit run-job commit-graph 2>../err &&\n+\t\ttest_must_be_empty ../err &&\n+\t\ttest_line_count = 1 $chain &&\n+\t\tfor i in $(test_seq 16 19)\n+\t\tdo\n+\t\t\ttest_commit $i || return 1\n+\t\tdone &&\n+\t\tgit run-job commit-graph 2>../err &&\n+\t\ttest_must_be_empty ../err &&\n+\t\ttest_line_count = 2 $chain &&\n+\t\tfor i in $(test_seq 20 23)\n+\t\tdo\n+\t\t\ttest_commit $i || return 1\n+\t\tdone &&\n+\t\tgit run-job commit-graph 2>../err &&\n+\t\ttest_must_be_empty ../err &&\n+\t\ttest_line_count = 1 $chain\n+\t)\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"394704","messageId":"5277398f189dd6e1456b0afdd6d85dbac7d98d07.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 05/15] run-job: implement pack-files job","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:04Z","receivedAt":"2020-04-03T20:48:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe previous change cleaned up loose objects using the\n'loose-objects' that can be run safely in the background. Add a\nsimilar job that performs similar cleanups for pack-files.\n\nOne issue with running 'git repack' is that it is designed to\nrepack all pack-files into a single pack-file. While this is the\nmost space-efficient way to store object data, it is not time or\nmemory efficient. This becomes extremely important if the repo is\nso large that a user struggles to store two copies of the pack on\ntheir disk.\n\nInstead, perform an \"incremental\" repack by collecting a few small\npack-files into a new pack-file. The multi-pack-index facilitates\nthis process ever since 'git multi-pack-index expire' was added in\n19575c7 (multi-pack-index: implement 'expire' subcommand,\n2019-06-10) and 'git multi-pack-index repack' was added in ce1e4a1\n(midx: implement midx_repack(), 2019-06-10).\n\nThe 'pack-files' job runs the following steps:\n\n1. 'git multi-pack-index write' creates a multi-pack-index file if\n   one did not exist, and otherwise will update the multi-pack-index\n   with any new pack-files that appeared since the last write. This\n   is particularly relevant with the background fetch job.\n\n   When the multi-pack-index sees two copies of the same object, it\n   stores the offset data into the newer pack-file. This means that\n   some old pack-files could become \"unreferenced\" which I will use\n   to mean \"a pack-file that is in the pack-file list of the\n   multi-pack-index but none of the objects in the multi-pack-index\n   reference a location inside that pack-file.\"\n\n2. 'git multi-pack-index expire' deletes any unreferenced pack-files\n   and updaes the multi-pack-index to drop those pack-files from the\n   list. This is safe to do as concurrent Git processes will see the\n   multi-pack-index and not open those packs when looking for object\n   contents. (Similar to the 'loose-objects' job, there are some Git\n   commands that open pack-files regardless of the multi-pack-index,\n   but they are rarely used. Further, a user that self-selects to\n   use background operations would likely refrain from using those\n   commands.)\n\n3. 'git multi-pack-index repack --bacth-size=<size>' collects a set\n   of pack-files that are listed in the multi-pack-index and creates\n   a new pack-file containing the objects whose offsets are listed\n   by the multi-pack-index to be in those objects. The set of pack-\n   files is selected greedily by sorting the pack-files by modified\n   time and adding a pack-file to the set if its \"expected size\" is\n   smaller than the batch size until the total expected size of the\n   selected pack-files is at least the batch size. The \"expected\n   size\" is calculated by taking the size of the pack-file divided\n   by the number of objects in the pack-file and multiplied by the\n   number of objects from the multi-pack-index with offset in that\n   pack-file. The expected size approximats how much data from that\n   pack-file will contribute to the resulting pack-file size. The\n   intention is that the resulting pack-file will be close in size\n   to the provided batch size.\n\n   The next run of the pack-files job will delete these repacked\n   pack-files during the 'expire' step.\n\n   In this version, the batch size is set to \"0\" which ignores the\n   size restrictions when selecting the pack-files. It instead\n   selects all pack-files and repacks all packed objects into a\n   single pack-file. This will be updated in the next change, but\n   it requires doing some calculations that are better isolated to\n   a separate change.\n\nEach of the above steps update the multi-pack-index file. After\neach step, we verify the new multi-pack-index. If the new\nmulti-pack-index is corrupt, then delete the multi-pack-index,\nrewrite it from scratch, and stop doing the later steps of the\njob. This is intended to be an extra-safe check without leaving\na repo with many pack-files without a multi-pack-index.\n\nThese steps are based on a similar background maintenance step in\nScalar (and VFS for Git) [1]. This was incredibly effective for\nusers of the Windows OS repository. After using the same VFS for Git\nrepository for over a year, some users had _thousands_ of pack-files\nthat combined to up to 250 GB of data. We noticed a few users were\nrunning into the open file descriptor limits (due in part to a bug\nin the multi-pack-index fixed by af96fe3392 (midx: add packs to\npacked_git linked list, 2019-04-29).\n\nThese pack-files were mostly small since they contained the commits\nand trees that were pushed to the origin in a given hour. The GVFS\nprotocol includes a \"prefetch\" step that asks for pre-computed pack-\nfiles containing commits and trees by timestamp. These pack-files\nwere grouped into \"daily\" pack-files once a day for up to 30 days.\nIf a user did not request prefetch packs for over 30 days, then they\nwould get the entire history of commits and trees in a new, large\npack-file. This led to a large number of pack-files that had poor\ndelta compression.\n\nBy running this pack-file maintenance step once per day, these repos\nwith thousands of packs spanning 200+ GB dropped to dozens of pack-\nfiles spanning 30-50 GB. This was done all without removing objects\nfrom the system and using a constant batch size of two gigabytes.\nOnce the work was done to reduce the pack-files to small sizes, the\nbatch size of two gigabytes means that not every run triggers a\nrepack operation, so the following run will not expire a pack-file.\nThis has kept these repos in a \"clean\" state.\n\n[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/PackfileMaintenanceStep.cs\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-run-job.txt | 18 ++++++-\n builtin/run-job.c             | 90 ++++++++++++++++++++++++++++++++++-\n midx.c                        |  2 +-\n midx.h                        |  1 +\n t/t7900-run-job.sh            | 39 +++++++++++++++\n 5 files changed, 147 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nindex 43ca1160b5a..108ed25b8bd 100644\n--- a/Documentation/git-run-job.txt\n+++ b/Documentation/git-run-job.txt\n@@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n SYNOPSIS\n --------\n [verse]\n-'git run-job (commit-graph|fetch|loose-objects)'\n+'git run-job (commit-graph|fetch|loose-objects|pack-files)'\n \n \n DESCRIPTION\n@@ -71,6 +71,22 @@ a batch of loose objects. The batch size is limited to 50 thousand\n objects to prevent the job from taking too long on a repository with\n many loose objects.\n \n+'pack-files'::\n+\n+The `pack-files` job incrementally repacks the object directory using\n+the `multi-pack-index` feature. In order to prevent race conditions with\n+concurrent Git commands, it follows a two-step process. First, it\n+deletes any pack-files included in the `multi-pack-index` where none of\n+the objects in the `multi-pack-index` reference those pack-files; this\n+only happens if all objects in the pack-file are also stored in a newer\n+pack-file. Second, it selects a group of pack-files whose \"expected\n+size\" is below the batch size until the group has total expected size at\n+least the batch size; see the `--batch-size` option for the `repack`\n+subcommand in linkgit:git-multi-pack-index[1]. The default batch-size is\n+zero, which is a special case that attempts to repack all pack-files\n+into a single pack-file.\n+\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nindex cecf9058c51..d3543f7ccb9 100644\n--- a/builtin/run-job.c\n+++ b/builtin/run-job.c\n@@ -1,13 +1,14 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"commit-graph.h\"\n+#include \"midx.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n #include \"run-command.h\"\n \n static char const * const builtin_run_job_usage[] = {\n-\tN_(\"git run-job (commit-graph|fetch|loose-objects)\"),\n+\tN_(\"git run-job (commit-graph|fetch|loose-objects|pack-files)\"),\n \tNULL\n };\n \n@@ -238,6 +239,91 @@ static int run_loose_objects_job(void)\n \treturn prune_packed() || pack_loose();\n }\n \n+static int multi_pack_index_write(void)\n+{\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\targv_array_pushl(&cmd, \"multi-pack-index\", \"write\",\n+\t\t\t \"--no-progress\", NULL);\n+\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+}\n+\n+static int rewrite_multi_pack_index(void)\n+{\n+\tchar *midx_name = get_midx_filename(the_repository->objects->odb->path);\n+\n+\tunlink(midx_name);\n+\tfree(midx_name);\n+\n+\tif (multi_pack_index_write()) {\n+\t\terror(_(\"failed to rewrite multi-pack-index\"));\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int multi_pack_index_verify(void)\n+{\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\targv_array_pushl(&cmd, \"multi-pack-index\", \"verify\",\n+\t\t\t \"--no-progress\", NULL);\n+\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+}\n+\n+static int multi_pack_index_expire(void)\n+{\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\targv_array_pushl(&cmd, \"multi-pack-index\", \"expire\",\n+\t\t\t \"--no-progress\", NULL);\n+\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+}\n+\n+static int multi_pack_index_repack(void)\n+{\n+\tint result;\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\targv_array_pushl(&cmd, \"multi-pack-index\", \"repack\",\n+\t\t\t \"--no-progress\", \"--batch-size=0\", NULL);\n+\tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+\n+\tif (result && multi_pack_index_verify()) {\n+\t\twarning(_(\"multi-pack-index verify failed after repack\"));\n+\t\tresult = rewrite_multi_pack_index();\n+\t}\n+\n+\treturn result;\n+}\n+\n+static int run_pack_files_job(void)\n+{\n+\tif (multi_pack_index_write()) {\n+\t\terror(_(\"failed to write multi-pack-index\"));\n+\t\treturn 1;\n+\t}\n+\n+\tif (multi_pack_index_verify()) {\n+\t\twarning(_(\"multi-pack-index verify failed after initial write\"));\n+\t\treturn rewrite_multi_pack_index();\n+\t}\n+\n+\tif (multi_pack_index_expire()) {\n+\t\terror(_(\"multi-pack-index expire failed\"));\n+\t\treturn 1;\n+\t}\n+\n+\tif (multi_pack_index_verify()) {\n+\t\twarning(_(\"multi-pack-index verify failed after expire\"));\n+\t\treturn rewrite_multi_pack_index();\n+\t}\n+\n+\tif (multi_pack_index_repack()) {\n+\t\terror(_(\"multi-pack-index repack failed\"));\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n int cmd_run_job(int argc, const char **argv, const char *prefix)\n {\n \tstatic struct option builtin_run_job_options[] = {\n@@ -261,6 +347,8 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n \t\t\treturn run_fetch_job();\n \t\tif (!strcmp(argv[0], \"loose-objects\"))\n \t\t\treturn run_loose_objects_job();\n+\t\tif (!strcmp(argv[0], \"pack-files\"))\n+\t\t\treturn run_pack_files_job();\n \t}\n \n \tusage_with_options(builtin_run_job_usage,\ndiff --git a/midx.c b/midx.c\nindex 1527e464a7b..0f0d0a38812 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -36,7 +36,7 @@\n \n #define PACK_EXPIRED UINT_MAX\n \n-static char *get_midx_filename(const char *object_dir)\n+char *get_midx_filename(const char *object_dir)\n {\n \treturn xstrfmt(\"%s/pack/multi-pack-index\", object_dir);\n }\ndiff --git a/midx.h b/midx.h\nindex e6fa356b5ca..cf2c09dffc2 100644\n--- a/midx.h\n+++ b/midx.h\n@@ -39,6 +39,7 @@ struct multi_pack_index {\n \n #define MIDX_PROGRESS     (1 << 0)\n \n+char *get_midx_filename(const char *object_dir);\n struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local);\n int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id);\n int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result);\ndiff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\nindex 41da083257b..416ba04989d 100755\n--- a/t/t7900-run-job.sh\n+++ b/t/t7900-run-job.sh\n@@ -6,6 +6,7 @@ Testing the background jobs, in the foreground\n '\n \n GIT_TEST_COMMIT_GRAPH=0\n+GIT_TEST_MULTI_PACK_INDEX=0\n \n . ./test-lib.sh\n \n@@ -93,4 +94,42 @@ test_expect_success 'loose-objects job' '\n \ttest_cmp packs-between packs-after\n '\n \n+test_expect_success 'pack-files job' '\n+\tpackDir=.git/objects/pack &&\n+\n+\t# Create three disjoint pack-files with size BIG, small, small.\n+\n+\techo HEAD~2 | git -C client pack-objects --revs $packDir/test-1 &&\n+\n+\ttest_tick &&\n+\tgit -C client pack-objects --revs $packDir/test-2 <<-\\EOF &&\n+\tHEAD~1\n+\t^HEAD~2\n+\tEOF\n+\n+\ttest_tick &&\n+\tgit -C client pack-objects --revs $packDir/test-3 <<-\\EOF &&\n+\tHEAD\n+\t^HEAD~1\n+\tEOF\n+\n+\trm -f client/$packDir/pack-* &&\n+\trm -f client/$packDir/loose-* &&\n+\n+\tls client/$packDir/*.pack >packs-before &&\n+\ttest_line_count = 3 packs-before &&\n+\n+\t# the job repacks the two into a new pack, but does not\n+\t# delete the old ones.\n+\tgit -C client run-job pack-files &&\n+\tls client/$packDir/*.pack >packs-between &&\n+\ttest_line_count = 4 packs-between &&\n+\n+\t# the job deletes the two old packs, and does not write\n+\t# a new one because only one pack remains.\n+\tgit -C client run-job pack-files &&\n+\tls client/.git/objects/pack/*.pack >packs-after &&\n+\ttest_line_count = 1 packs-after\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"394705","messageId":"77b1da5d3063a2404cd750adfe3bb8be9b6c497d.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 03/15] run-job: implement fetch job","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:02Z","receivedAt":"2020-04-03T20:48:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen working with very large repositories, an incremental 'git fetch'\ncommand can download a large amount of data. If there are many other\nusers pushing to a common repo, then this data can rival the initial\npack-file size of a 'git clone' of a medium-size repo.\n\nUsers may want to keep the data on their local repos as close as\npossible to the data on the remote repos by fetching periodically in\nthe background. This can break up a large daily fetch into several\nsmaller hourly fetches.\n\nHowever, if we simply ran 'git fetch <remote>' in the background,\nthen the user running a foregroudn 'git fetch <remote>' would lose\nsome important feedback when a new branch appears or an existing\nbranch updates. This is especially true if a remote branch is\nforce-updated and this isn't noticed by the user because it occurred\nin the background. Further, the functionality of 'git push\n--force-with-lease' becomes suspect.\n\nWhen running 'git fetch <remote> <options>' in the background, use\nthe following options for careful updating:\n\n1. --no-tags prevents getting a new tag when a user wants to see\n   the new tags appear in their foreground fetches.\n\n2. --refmap= removes the configured refspec which usually updates\n   refs/remotes/<remote>/* with the refs advertised by the remote.\n\n3. By adding a new refspec \"+refs/heads/*:refs/hidden/<remote>/*\"\n   we can ensure that we actually load the new values somewhere in\n   our refspace while not updating refs/heads or refs/remotes. By\n   storing these refs here, the commit-graph job will update the\n   commit-graph with the commits from these hidden refs.\n\n4. --prune will delete the refs/hidden/<remote> refs that no\n   longer appear on the remote.\n\nWe've been using this step as a critical background job in Scalar\n[1] (and VFS for Git). This solved a pain point that was showing up\nin user reports: fetching was a pain! Users do not like waiting to\ndownload the data that was created while they were away from their\nmachines. After implementing background fetch, the foreground fetch\ncommands sped up significantly because they mostly just update refs\nand download a small amount of new data. The effect is especially\ndramatic when paried with --no-show-forced-udpates (through\nfetch.showForcedUpdates=false).\n\n[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/FetchStep.cs\n\nRFC QUESTIONS:\n\n1. One downside of the refs/hidden pattern is that 'git log' will\n   decorate commits with twice as many refs if they appear at a\n   remote ref (<remote>/<ref> _and_ refs/hidden/<remote>/<ref>). Is\n   there an easy way to exclude a refspace from decorations? Should\n   we make refs/hidden/* a \"special\" refspace that is excluded from\n   decorations?\n\n2. This feature is designed for a desktop machine or equivalent\n   that has a permanent wired network connection, and the machine\n   stays on while the user is not present. For things like laptops\n   with inconsistent WiFi connections (that may be metered) the\n   feature can use the less stable connection more than the user\n   wants. Of course, this feature is opt-in for Git, but in Scalar\n   we have a \"scalar pause\" command [2] that pauses all maintenance\n   for some amount of time. We should consider a similar mechanism\n   for Git, but for the point of this series the user needs to set\n   up the \"background\" part of these jobs manually.\n\n[2] https://github.com/microsoft/scalar/blob/master/Scalar/CommandLine/PauseVerb.cs\n[3] https://github.com/microsoft/scalar/blob/master/docs/advanced.md#controlling-background-maintenance\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-run-job.txt | 13 ++++-\n builtin/run-job.c             | 89 ++++++++++++++++++++++++++++++++++-\n t/t7900-run-job.sh            | 22 +++++++++\n 3 files changed, 122 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nindex 8bf2762d650..eb92e891915 100644\n--- a/Documentation/git-run-job.txt\n+++ b/Documentation/git-run-job.txt\n@@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n SYNOPSIS\n --------\n [verse]\n-'git run-job commit-graph'\n+'git run-job (commit-graph|fetch)'\n \n \n DESCRIPTION\n@@ -47,6 +47,17 @@ since it will not expire `.graph` files that were in the previous\n `commit-graph-chain` file. They will be deleted by a later run based on\n the expiration delay.\n \n+'fetch'::\n+\n+The `fetch` job updates the object directory with the latest objects\n+from all registered remotes. For each remote, a `git fetch` command is\n+run. The refmap is custom to avoid updating local or remote branches\n+(those in `refs/heads` or `refs/remotes`). Instead, the remote refs are\n+stored in `refs/hidden/<remote>/`. Also, no tags are updated.\n++\n+This means that foreground fetches are still required to update the\n+remote refs, but the users is notified when the branches and tags are\n+updated on the remote.\n \n GIT\n ---\ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nindex dd7709952d3..e59056b2918 100644\n--- a/builtin/run-job.c\n+++ b/builtin/run-job.c\n@@ -7,7 +7,7 @@\n #include \"run-command.h\"\n \n static char const * const builtin_run_job_usage[] = {\n-\tN_(\"git run-job commit-graph\"),\n+\tN_(\"git run-job (commit-graph|fetch)\"),\n \tNULL\n };\n \n@@ -60,6 +60,91 @@ static int run_commit_graph_job(void)\n \treturn 1;\n }\n \n+static int fetch_remote(const char *remote)\n+{\n+\tint result;\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\tstruct strbuf refmap = STRBUF_INIT;\n+\n+\targv_array_pushl(&cmd, \"fetch\", remote, \"--quiet\", \"--prune\",\n+\t\t\t \"--no-tags\", \"--refmap=\", NULL);\n+\n+\tstrbuf_addf(&refmap, \"+refs/heads/*:refs/hidden/%s/*\", remote);\n+\targv_array_push(&cmd, refmap.buf);\n+\n+\tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+\n+\tstrbuf_release(&refmap);\n+\treturn result;\n+}\n+\n+static int fill_remotes(struct string_list *remotes)\n+{\n+\tint result = 0;\n+\tFILE *proc_out;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tstruct child_process *remote_proc = xmalloc(sizeof(*remote_proc));\n+\n+\tchild_process_init(remote_proc);\n+\n+\targv_array_pushl(&remote_proc->args, \"git\", \"remote\", NULL);\n+\n+\tremote_proc->out = -1;\n+\n+\tif (start_command(remote_proc)) {\n+\t\terror(_(\"failed to start 'git remote' process\"));\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tproc_out = xfdopen(remote_proc->out, \"r\");\n+\n+\t/* if there is no line, leave the value as given */\n+\twhile (!strbuf_getline(&line, proc_out))\n+\t\tstring_list_append(remotes, line.buf);\n+\n+\tstrbuf_release(&line);\n+\n+\tfclose(proc_out);\n+\n+\tif (finish_command(remote_proc)) {\n+\t\terror(_(\"failed to finish 'git remote' process\"));\n+\t\tresult = 1;\n+\t}\n+\n+cleanup:\n+\tfree(remote_proc);\n+\treturn result;\n+}\n+\n+static int run_fetch_job(void)\n+{\n+\tint result = 0;\n+\tstruct string_list_item *item;\n+\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n+\n+\tif (fill_remotes(&remotes)) {\n+\t\terror(_(\"failed to fill remotes\"));\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/*\n+\t * Do not modify the result based on the success of the 'fetch'\n+\t * operation, as a loss of network could cause 'fetch' to fail\n+\t * quickly. We do not want that to stop the rest of our\n+\t * background operations.\n+\t */\n+\tfor (item = remotes.items;\n+\t     item && item < remotes.items + remotes.nr;\n+\t     item++)\n+\t\tfetch_remote(item->string);\n+\n+cleanup:\n+\tstring_list_clear(&remotes, 0);\n+\treturn result;\n+}\n+\n int cmd_run_job(int argc, const char **argv, const char *prefix)\n {\n \tstatic struct option builtin_run_job_options[] = {\n@@ -79,6 +164,8 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n \tif (argc > 0) {\n \t\tif (!strcmp(argv[0], \"commit-graph\"))\n \t\t\treturn run_commit_graph_job();\n+\t\tif (!strcmp(argv[0], \"fetch\"))\n+\t\t\treturn run_fetch_job();\n \t}\n \n \tusage_with_options(builtin_run_job_usage,\ndiff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\nindex 18b9bd26b3a..d3faeba135b 100755\n--- a/t/t7900-run-job.sh\n+++ b/t/t7900-run-job.sh\n@@ -44,4 +44,26 @@ test_expect_success 'commit-graph job' '\n \t)\n '\n \n+test_expect_success 'fetch job' '\n+\tgit clone \"file://$(pwd)/server\" client &&\n+\n+\t# Before fetching, build a client commit-graph\n+\tgit -C client run-job commit-graph &&\n+\tchain=client/.git/objects/info/commit-graphs/commit-graph-chain &&\n+\ttest_line_count = 1 $chain &&\n+\n+\tgit -C client branch -v --remotes >before-refs &&\n+\ttest_commit -C server 24 &&\n+\n+\tgit -C client run-job fetch &&\n+\tgit -C client branch -v --remotes >after-refs &&\n+\ttest_cmp before-refs after-refs &&\n+\ttest_cmp server/.git/refs/heads/master \\\n+\t\t client/.git/refs/hidden/origin/master &&\n+\n+\t# the hidden ref should trigger a new layer in the commit-graph\n+\tgit -C client run-job commit-graph &&\n+\ttest_line_count = 2 $chain\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"394707","messageId":"c39a35a959e485623aacdbbecc1fce7d16a956c9.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 06/15] run-job: auto-size or use custom pack-files batch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:05Z","receivedAt":"2020-04-03T20:48:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen repacking during the 'pack-files' job, we use the --batch-size\noption in 'git multi-pack-index repack'. The initial setting used\n--batch-size=0 to repack everything into a single pack-file. This\nis not sustaintable for a large repository. The amount of work\nrequired is also likely to use too many system resources for a\nbackground job.\n\nUpdate the 'git run-job pack-files' command by allowing a direct\n--batch-size option that can change the value provided. Update the\ndefault of \"0\" to a computed size based on the existing pack-files.\nWhile computing that new size, count the existing pack-files and\nskip the repack step if there are at most two pack-files.\n\nThe dynamic default size is computed with this idea in mind for\na client repository that was cloned from a very large remote: there\nis likely one \"big\" pack-file that was created at clone time. Thus,\ndo not try repacking it as it is likely packed efficiently by the\nserver. Instead, try packing the other pack-files into a single\npack-file.\n\nThe size is then computed as follows:\n\n\tbatch size = total size - max pack size\n\nThe batch size is then also limited to be at most two gigabytes.\nThis serves two purposes. First, having a limit prevents doing too\nmuch work when the repository is extremely large. Pack-files\nlarger than two gigabytes are likely to either contain large blobs\nor have been carefully repacked by a previous repack operation.\nSecond, two gigabytes is the size limit for a signed 32-bit int.\nIt's a good limit to consider, and to keep it far away from the\nunsigned 32-bit int limit. This limit comes to mind because on\nWindows an \"unsigned long\" is 32 bits and OPT_MAGNITUDE() uses\nunsigned longs for its parsing logic.\n\nThis calculation mimics a similar calculation in Scalar [1],\nexcept for a 3% drop in the calculated batch size due to the\nround-off error that can happen with the \"expected size\"\ncalculation for a pack-file.\n\n[1] https://github.com/microsoft/scalar/blob/616e9b16dd120b8fdb652d6d5a55618c731a8aea/Scalar.Common/Maintenance/PackfileMaintenanceStep.cs#L141-L143\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-run-job.txt | 13 +++--\n builtin/run-job.c             | 90 ++++++++++++++++++++++++++++++++---\n t/t7900-run-job.sh            |  6 ++-\n 3 files changed, 96 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nindex 108ed25b8bd..cdd6417f7c9 100644\n--- a/Documentation/git-run-job.txt\n+++ b/Documentation/git-run-job.txt\n@@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n SYNOPSIS\n --------\n [verse]\n-'git run-job (commit-graph|fetch|loose-objects|pack-files)'\n+'git run-job (commit-graph|fetch|loose-objects|pack-files) [<options>]'\n \n \n DESCRIPTION\n@@ -82,9 +82,14 @@ only happens if all objects in the pack-file are also stored in a newer\n pack-file. Second, it selects a group of pack-files whose \"expected\n size\" is below the batch size until the group has total expected size at\n least the batch size; see the `--batch-size` option for the `repack`\n-subcommand in linkgit:git-multi-pack-index[1]. The default batch-size is\n-zero, which is a special case that attempts to repack all pack-files\n-into a single pack-file.\n+subcommand in linkgit:git-multi-pack-index[1].\n++\n+The default batch size is computed to optimize for having a single large\n+pack-file and many small pack-files. When there are two or fewer\n+pack-files, the job does not attempt to repack. Otherwise, the batch\n+size is the sum of all pack-file sizes minus the largest pack-file size.\n+The batch size is capped at two gigabytes. This intends to pack all\n+small pack-files into a single pack-file.\n \n \n GIT\ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nindex d3543f7ccb9..2ccc3bbae2d 100644\n--- a/builtin/run-job.c\n+++ b/builtin/run-job.c\n@@ -3,12 +3,18 @@\n #include \"commit-graph.h\"\n #include \"midx.h\"\n #include \"object-store.h\"\n+#include \"packfile.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n #include \"run-command.h\"\n \n static char const * const builtin_run_job_usage[] = {\n-\tN_(\"git run-job (commit-graph|fetch|loose-objects|pack-files)\"),\n+\tN_(\"git run-job (commit-graph|fetch|loose-objects|pack-files) [<options>]\"),\n+\tNULL\n+};\n+\n+static char const * const builtin_run_job_pack_file_usage[] = {\n+\tN_(\"git run-job pack-files [--batch-size=<size>]\"),\n \tNULL\n };\n \n@@ -278,15 +284,74 @@ static int multi_pack_index_expire(void)\n \treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n }\n \n-static int multi_pack_index_repack(void)\n+#define TWO_GIGABYTES (2147483647)\n+\n+static off_t get_auto_pack_size(int *count)\n+{\n+\t/*\n+\t * The \"auto\" value is special: we optimize for\n+\t * one large pack-file (i.e. from a clone) and\n+\t * expect the rest to be small and they can be\n+\t * repacked quickly. Find the sum of the sizes\n+\t * other than the largest pack-file, then use\n+\t * that as the batch size.\n+\t */\n+\toff_t total_size = 0;\n+\toff_t max_size = 0;\n+\toff_t result_size;\n+\tstruct packed_git *p;\n+\n+\t*count = 0;\n+\n+\treprepare_packed_git(the_repository);\n+\tfor (p = get_all_packs(the_repository); p; p = p->next) {\n+\t\t(*count)++;\n+\t\ttotal_size += p->pack_size;\n+\n+\t\tif (p->pack_size > max_size)\n+\t\t\tmax_size = p->pack_size;\n+\t}\n+\n+\tresult_size = total_size - max_size;\n+\n+\t/* But limit ourselves to a batch size of 2g */\n+\tif (result_size > TWO_GIGABYTES)\n+\t\tresult_size = TWO_GIGABYTES;\n+\n+\treturn result_size;\n+}\n+\n+#define UNSET_BATCH_SIZE ((unsigned long)-1)\n+static int multi_pack_index_repack(unsigned long batch_size)\n {\n \tint result;\n \tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\tstruct strbuf batch_arg = STRBUF_INIT;\n+\tint count;\n+\toff_t default_size = get_auto_pack_size(&count);\n+\n+\tif (count <= 2)\n+\t\treturn 0;\n+\n+\tstrbuf_addstr(&batch_arg, \"--batch-size=\");\n+\n+\tif (batch_size != UNSET_BATCH_SIZE)\n+\t\tstrbuf_addf(&batch_arg, \"\\\"%\"PRIuMAX\"\\\"\", (uintmax_t)batch_size);\n+\telse\n+\t\tstrbuf_addf(&batch_arg, \"%\"PRIuMAX,\n+\t\t\t    (uintmax_t)default_size);\n+\n \targv_array_pushl(&cmd, \"multi-pack-index\", \"repack\",\n-\t\t\t \"--no-progress\", \"--batch-size=0\", NULL);\n+\t\t\t \"--no-progress\", batch_arg.buf, NULL);\n \tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n \n-\tif (result && multi_pack_index_verify()) {\n+\tstrbuf_release(&batch_arg);\n+\n+\t/*\n+\t * Verify here to avoid verifying again when there are two\n+\t * or fewer pack-files.\n+\t */\n+\tif (!result && multi_pack_index_verify()) {\n \t\twarning(_(\"multi-pack-index verify failed after repack\"));\n \t\tresult = rewrite_multi_pack_index();\n \t}\n@@ -294,8 +359,19 @@ static int multi_pack_index_repack(void)\n \treturn result;\n }\n \n-static int run_pack_files_job(void)\n+static int run_pack_files_job(int argc, const char **argv)\n {\n+\tstatic unsigned long batch_size = UNSET_BATCH_SIZE;\n+\tstatic struct option builtin_run_job_pack_file_options[] = {\n+\t\tOPT_MAGNITUDE(0, \"batch-size\", &batch_size,\n+\t\t\t      N_(\"specify a batch-size for the incremental repack\")),\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL,\n+\t\t\t     builtin_run_job_pack_file_options,\n+\t\t\t     builtin_run_job_pack_file_usage, 0);\n+\n \tif (multi_pack_index_write()) {\n \t\terror(_(\"failed to write multi-pack-index\"));\n \t\treturn 1;\n@@ -316,7 +392,7 @@ static int run_pack_files_job(void)\n \t\treturn rewrite_multi_pack_index();\n \t}\n \n-\tif (multi_pack_index_repack()) {\n+\tif (multi_pack_index_repack(batch_size)) {\n \t\terror(_(\"multi-pack-index repack failed\"));\n \t\treturn 1;\n \t}\n@@ -348,7 +424,7 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n \t\tif (!strcmp(argv[0], \"loose-objects\"))\n \t\t\treturn run_loose_objects_job();\n \t\tif (!strcmp(argv[0], \"pack-files\"))\n-\t\t\treturn run_pack_files_job();\n+\t\t\treturn run_pack_files_job(argc, argv);\n \t}\n \n \tusage_with_options(builtin_run_job_usage,\ndiff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\nindex 416ba04989d..2d9f6cdf328 100755\n--- a/t/t7900-run-job.sh\n+++ b/t/t7900-run-job.sh\n@@ -128,8 +128,10 @@ test_expect_success 'pack-files job' '\n \t# the job deletes the two old packs, and does not write\n \t# a new one because only one pack remains.\n \tgit -C client run-job pack-files &&\n-\tls client/.git/objects/pack/*.pack >packs-after &&\n-\ttest_line_count = 1 packs-after\n+\tls client/$packDir/*.pack >packs-after &&\n+\ttest_line_count = 2 packs-after &&\n+\tcat packs-after | grep \"pack/test-1-\" &&\n+\tcat packs-after | grep \"pack/pack-\"\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"394706","messageId":"4c4aeb55dea9dd7b97bd7f08c8a767b498886109.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 04/15] run-job: implement loose-objects job","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:03Z","receivedAt":"2020-04-03T20:48:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nOne goal of background maintenance jobs is to allow a user to\ndisable auto-gc (gc.auto=0) but keep their repository in a clean\nstate. Without any cleanup, loose objects will clutter the object\ndatabase and slow operations. In addition, the loose objects will\ntake up extra space because they are not stored with deltas against\nsimilar objects.\n\nCreate a 'loose-objects' job for the 'git run-job' command. This\nhelps clean up loose objects without disrupting concurrent Git\ncommands using the following sequence of events:\n\n1. Run 'git prune-packed' to delete any loose objects that exist\n   in a pack-file. Concurrent commands will prefer the packed\n   version of the object to the loose version. (Of course, there\n   are exceptions for commands that specifically care about the\n   location of an object. These are rare for a user to run on\n   purpose, and we hope a user that has selected background\n   maintenance will not be trying to do foreground maintenance.)\n\n2. Run 'git pack-objects' on a batch of loose objects. These\n   objects are grouped by scanning the loose object directories in\n   lexicographic order until listing all loose objects -or-\n   reaching 50,000 objects. This is more than enough if the loose\n   objects are created only by a user doing normal development.\n   We noticed users with _millions_ of loose objects because VFS\n   for Git downloads blobs on-demand when a file read operation\n   requires populating a virtual file. This has potential of\n   happening in partial clones if someone runs 'git grep' or\n   otherwise evades the batch-download feature for requesting\n   promisor objects.\n\nThis step is based on a similar step in Scalar [1] and VFS for Git.\n[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/LooseObjectsStep.cs\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-run-job.txt | 14 ++++-\n builtin/run-job.c             | 97 ++++++++++++++++++++++++++++++++++-\n t/t7900-run-job.sh            | 27 ++++++++++\n 3 files changed, 136 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nindex eb92e891915..43ca1160b5a 100644\n--- a/Documentation/git-run-job.txt\n+++ b/Documentation/git-run-job.txt\n@@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n SYNOPSIS\n --------\n [verse]\n-'git run-job (commit-graph|fetch)'\n+'git run-job (commit-graph|fetch|loose-objects)'\n \n \n DESCRIPTION\n@@ -59,6 +59,18 @@ This means that foreground fetches are still required to update the\n remote refs, but the users is notified when the branches and tags are\n updated on the remote.\n \n+'loose-objects'::\n+\n+The `loose-objects` job cleans up loose objects and places them into\n+pack-files. In order to prevent race conditions with concurrent Git\n+commands, it follows a two-step process. First, it deletes any loose\n+objects that already exist in a pack-file; concurrent Git processes will\n+examine the pack-file for the object data instead of the loose object.\n+Second, it creates a new pack-file (starting with \"loose-\") containing\n+a batch of loose objects. The batch size is limited to 50 thousand\n+objects to prevent the job from taking too long on a repository with\n+many loose objects.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nindex e59056b2918..cecf9058c51 100644\n--- a/builtin/run-job.c\n+++ b/builtin/run-job.c\n@@ -7,7 +7,7 @@\n #include \"run-command.h\"\n \n static char const * const builtin_run_job_usage[] = {\n-\tN_(\"git run-job (commit-graph|fetch)\"),\n+\tN_(\"git run-job (commit-graph|fetch|loose-objects)\"),\n \tNULL\n };\n \n@@ -145,6 +145,99 @@ static int run_fetch_job(void)\n \treturn result;\n }\n \n+static int prune_packed(void)\n+{\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\targv_array_pushl(&cmd, \"prune-packed\", NULL);\n+\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+}\n+\n+struct write_loose_object_data {\n+\tFILE *in;\n+\tint count;\n+\tint batch_size;\n+};\n+\n+static int loose_object_exists(const struct object_id *oid,\n+\t\t\t       const char *path,\n+\t\t\t       void *data)\n+{\n+\treturn 1;\n+}\n+\n+static int write_loose_object_to_stdin(const struct object_id *oid,\n+\t\t\t\t       const char *path,\n+\t\t\t\t       void *data)\n+{\n+\tstruct write_loose_object_data *d = (struct write_loose_object_data *)data;\n+\n+\tfprintf(d->in, \"%s\\n\", oid_to_hex(oid));\n+\n+\treturn ++(d->count) > d->batch_size;\n+}\n+\n+static int pack_loose(void)\n+{\n+\tint result = 0;\n+\tstruct write_loose_object_data data;\n+\tstruct strbuf prefix = STRBUF_INIT;\n+\tstruct child_process *pack_proc;\n+\n+\t/*\n+\t * Do not start pack-objects process\n+\t * if there are no loose objects.\n+\t */\n+\tif (!for_each_loose_file_in_objdir(the_repository->objects->odb->path,\n+\t\t\t\t\t   loose_object_exists,\n+\t\t\t\t\t   NULL, NULL, NULL))\n+\t\treturn 0;\n+\n+\tpack_proc = xmalloc(sizeof(*pack_proc));\n+\n+\tchild_process_init(pack_proc);\n+\n+\tstrbuf_addstr(&prefix, the_repository->objects->odb->path);\n+\tstrbuf_addstr(&prefix, \"/pack/loose\");\n+\n+\targv_array_pushl(&pack_proc->args, \"git\", \"pack-objects\",\n+\t\t\t \"--quiet\", prefix.buf, NULL);\n+\n+\tpack_proc->in = -1;\n+\n+\tif (start_command(pack_proc)) {\n+\t\terror(_(\"failed to start 'git pack-objects' process\"));\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tdata.in = xfdopen(pack_proc->in, \"w\");\n+\tdata.count = 0;\n+\tdata.batch_size = 50000;\n+\n+\tfor_each_loose_file_in_objdir(the_repository->objects->odb->path,\n+\t\t\t\t      write_loose_object_to_stdin,\n+\t\t\t\t      NULL,\n+\t\t\t\t      NULL,\n+\t\t\t\t      &data);\n+\n+\tfclose(data.in);\n+\n+\tif (finish_command(pack_proc)) {\n+\t\terror(_(\"failed to finish 'git pack-objects' process\"));\n+\t\tresult = 1;\n+\t}\n+\n+cleanup:\n+\tstrbuf_release(&prefix);\n+\tfree(pack_proc);\n+\treturn result;\n+}\n+\n+static int run_loose_objects_job(void)\n+{\n+\treturn prune_packed() || pack_loose();\n+}\n+\n int cmd_run_job(int argc, const char **argv, const char *prefix)\n {\n \tstatic struct option builtin_run_job_options[] = {\n@@ -166,6 +259,8 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n \t\t\treturn run_commit_graph_job();\n \t\tif (!strcmp(argv[0], \"fetch\"))\n \t\t\treturn run_fetch_job();\n+\t\tif (!strcmp(argv[0], \"loose-objects\"))\n+\t\t\treturn run_loose_objects_job();\n \t}\n \n \tusage_with_options(builtin_run_job_usage,\ndiff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\nindex d3faeba135b..41da083257b 100755\n--- a/t/t7900-run-job.sh\n+++ b/t/t7900-run-job.sh\n@@ -66,4 +66,31 @@ test_expect_success 'fetch job' '\n \ttest_line_count = 2 $chain\n '\n \n+test_expect_success 'loose-objects job' '\n+\tls client/.git/objects >obj-dir-before &&\n+\ttest_file_not_empty obj-dir-before &&\n+\tls client/.git/objects/pack/*.pack >packs-before &&\n+\ttest_line_count = 1 packs-before &&\n+\n+\t# The first run creates a pack-file\n+\t# but does not delete loose objects.\n+\tgit -C client run-job loose-objects &&\n+\tls client/.git/objects >obj-dir-between &&\n+\ttest_cmp obj-dir-before obj-dir-between &&\n+\tls client/.git/objects/pack/*.pack >packs-between &&\n+\ttest_line_count = 2 packs-between &&\n+\n+\t# The second run deletes loose objects\n+\t# but does not create a pack-file.\n+\tgit -C client run-job loose-objects &&\n+\tls client/.git/objects >obj-dir-after &&\n+\tcat >expect <<-\\EOF &&\n+\tinfo\n+\tpack\n+\tEOF\n+\ttest_cmp expect obj-dir-after &&\n+\tls client/.git/objects/pack/*.pack >packs-after &&\n+\ttest_cmp packs-between packs-after\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"394711","messageId":"9104ae46371a08967805f6ce27586ccade37972a.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 09/15] job-runner: load repos from config by default","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:08Z","receivedAt":"2020-04-03T20:48:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git job-runner' command was introduced with a '--repo=<path>'\noption so a user could start a process with an explicit list of\nrepos. However, it is more likely that we will want 'git\njob-runner' to start without any arguments and discover the set of\nrepos from another source.\n\nA natural source is the Git config. The 'git job-runner' does not\nneed to run in a Git repository, but the config could be located in\nthe global or system config.\n\nCreate the job.repo config setting as a multi-valued config option.\n\nRead all values for job.repo whenever triggering an iteration of\nthe job loop. This allows a user to add or remove repos dynamically\nwithout restarting the job-runner.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/job.txt |  7 +++++++\n builtin/job-runner.c         | 20 ++++++++++++++++++++\n 2 files changed, 27 insertions(+)\n\ndiff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\nindex efdb76afad3..2dfed3935fa 100644\n--- a/Documentation/config/job.txt\n+++ b/Documentation/config/job.txt\n@@ -4,3 +4,10 @@ job.pack-files.batchSize::\n \tpart of `git run-job pack-files`. If not specified, then a\n \tdynamic size calculation is run. See linkgit:git-run-job[1]\n \tfor more details.\n+\n+job.repo::\n+\tStore an absolute path to a repository that wants background\n+\tjobs run by `git job-runner`. This is a multi-valued config\n+\toption, but it must be stored in a config file seen by the\n+\tbackground runner. This may be the global or system config\n+\tdepending on how `git job-runner` is launched on your system.\ndiff --git a/builtin/job-runner.c b/builtin/job-runner.c\nindex 135288bcaae..bed401f94bf 100644\n--- a/builtin/job-runner.c\n+++ b/builtin/job-runner.c\n@@ -20,6 +20,9 @@ static int arg_repos_append(const struct option *opt,\n \n static int load_active_repos(struct string_list *repos)\n {\n+\tstruct string_list_item *item;\n+\tconst struct string_list *config_repos;\n+\n \tif (arg_repos.nr) {\n \t\tstruct string_list_item *item;\n \t\tfor (item = arg_repos.items;\n@@ -29,6 +32,23 @@ static int load_active_repos(struct string_list *repos)\n \t\treturn 0;\n \t}\n \n+\tconfig_repos = git_config_get_value_multi(\"job.repo\");\n+\n+\tfor (item = config_repos->items;\n+\t     item && item < config_repos->items + config_repos->nr;\n+\t     item++) {\n+\t\tDIR *dir = opendir(item->string);\n+\n+\t\tif (!dir)\n+\t\t\tcontinue;\n+\n+\t\tclosedir(dir);\n+\t\tstring_list_append(repos, item->string);\n+\t}\n+\n+\tstring_list_sort(repos);\n+\tstring_list_remove_duplicates(repos, 0);\n+\n \treturn 0;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"394715","messageId":"90379bad303e77cc82ef79fc2ba505805a571483.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 08/15] job-runner: create builtin for job loop","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:07Z","receivedAt":"2020-04-03T20:48:27Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nGiven the existing 'git run-job <job-name>' builtin, users _could_\nconstruct their own scheduling mechanism for running jobs in the\nbackground. However, it is much easier to have a dedicated process\nthat manages all jobs across multiple repos.\n\nThe 'git job-runner' builtin is specifically built to handle this\nscenario. It will be customized further in later changes, but for\nnow it does the following:\n\n* Given a list of '--repo=<path>' arguments, construct a list of\n  repositories to manage with jobs.\n\n* Every 30 minutes, iterate over all jobs and all repos to run\n\n\tgit -C <repo> run-job <job-name>\n\nThis builtin needs to be careful about how much of the Git internals\nit consumes. The intention is that this is a long-lived process that\ncould be launched upon login and only closed on logout. For that\nreason, we will avoid instantiating any object store or index data.\n\nRun the maintenance jobs by running subcommands. We will update how\nwe enable or disable these jobs and separate their runs in a later\nchange.\n\nRFC QUESTIONS:\n\n1. The hardest part of this builtin is \"how do we test it?\" In\n   Scalar, we can unit test the scheduler with mocks. What is the\n   equivalent here for \"make sure 'git job-runner' runs 'git run-job\n   pack-files' on repo X? I expect to add a \"--no-loop\" option that\n   ensures the logic only runs one iteration of the loop.\n\n2. The difference between 'git run-job' and 'git job-runner' is\n   subtle and probably confusing. Are there better names?\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n .gitignore                       |   1 +\n Documentation/git-job-runner.txt |  52 +++++++++++++++\n Makefile                         |   1 +\n builtin.h                        |   1 +\n builtin/job-runner.c             | 111 +++++++++++++++++++++++++++++++\n command-list.txt                 |   1 +\n git.c                            |   1 +\n 7 files changed, 168 insertions(+)\n create mode 100644 Documentation/git-job-runner.txt\n create mode 100644 builtin/job-runner.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 5dea9d3b96b..24c377b2883 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -83,6 +83,7 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n+/git-job-runner\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Documentation/git-job-runner.txt b/Documentation/git-job-runner.txt\nnew file mode 100644\nindex 00000000000..010b2d05f9b\n--- /dev/null\n+++ b/Documentation/git-job-runner.txt\n@@ -0,0 +1,52 @@\n+git-job-runner(1)\n+=================\n+\n+NAME\n+----\n+git-job-runner - run jobs on multiple repos according to a schedlue.\n+Intended for background operation.\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git job-runner [--repo=<path>]'\n+\n+\n+DESCRIPTION\n+-----------\n+\n+Run jobs in a loop with some frequency. Intended for running in the\n+background.\n+\n+The `git run-job <job-name>` command runs a specific maintenance task.\n+The `job-runner` command is a long-running process that calls the\n+`run-job` command on a set of repositories at some frequency. The\n+`job.*` config values customize the frequencies of these jobs.\n+\n+\n+OPTIONS\n+-------\n+\n+--repo=<dir>::\n+\tIf at least one `--repo` option is provided, the runner only\n+\tattempts running jobs on repositories located at the provided\n+\t`<dir>` values. This option can be specified multiple times.\n+\n+\n+CONFIGURATION\n+-------------\n+\n+The `git job-runner` command is intended to run as a long-running\n+process. The following config options are checked periodically during\n+the process and will modify its behavior:\n+\n+The below documentation is the same as what's found in\n+linkgit:git-config[1]:\n+\n+include::config/job.txt[]\n+\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex f5f9c4d9e94..ee98a91af0f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1082,6 +1082,7 @@ BUILTIN_OBJS += builtin/help.o\n BUILTIN_OBJS += builtin/index-pack.o\n BUILTIN_OBJS += builtin/init-db.o\n BUILTIN_OBJS += builtin/interpret-trailers.o\n+BUILTIN_OBJS += builtin/job-runner.o\n BUILTIN_OBJS += builtin/log.o\n BUILTIN_OBJS += builtin/ls-files.o\n BUILTIN_OBJS += builtin/ls-remote.o\ndiff --git a/builtin.h b/builtin.h\nindex 3e0ddaaf67f..5008c7096b3 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -176,6 +176,7 @@ int cmd_help(int argc, const char **argv, const char *prefix);\n int cmd_index_pack(int argc, const char **argv, const char *prefix);\n int cmd_init_db(int argc, const char **argv, const char *prefix);\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);\n+int cmd_job_runner(int argc, const char **argv, const char *prefix);\n int cmd_log(int argc, const char **argv, const char *prefix);\n int cmd_log_reflog(int argc, const char **argv, const char *prefix);\n int cmd_ls_files(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/job-runner.c b/builtin/job-runner.c\nnew file mode 100644\nindex 00000000000..135288bcaae\n--- /dev/null\n+++ b/builtin/job-runner.c\n@@ -0,0 +1,111 @@\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"parse-options.h\"\n+#include \"run-command.h\"\n+#include \"string-list.h\"\n+\n+static char const * const builtin_job_runner_usage[] = {\n+\tN_(\"git job-runner [<options>]\"),\n+\tNULL\n+};\n+\n+static struct string_list arg_repos = STRING_LIST_INIT_DUP;\n+\n+static int arg_repos_append(const struct option *opt,\n+\t\t\t    const char *arg, int unset)\n+{\n+\tstring_list_append(&arg_repos, arg);\n+\treturn 0;\n+}\n+\n+static int load_active_repos(struct string_list *repos)\n+{\n+\tif (arg_repos.nr) {\n+\t\tstruct string_list_item *item;\n+\t\tfor (item = arg_repos.items;\n+\t\t     item && item < arg_repos.items + arg_repos.nr;\n+\t\t     item++)\n+\t\t\tstring_list_append(repos, item->string);\n+\t\treturn 0;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int run_job(const char *job, const char *repo)\n+{\n+\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\targv_array_pushl(&cmd, \"-C\", repo, \"run-job\", job, NULL);\n+\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+}\n+\n+static int run_job_loop_step(struct string_list *list)\n+{\n+\tint result = 0;\n+\tstruct string_list_item *job;\n+\tstruct string_list repos = STRING_LIST_INIT_DUP;\n+\n+\tif ((result = load_active_repos(&repos)))\n+\t\treturn result;\n+\n+\tfor (job = list->items;\n+\t     !result && job && job < list->items + list->nr;\n+\t     job++) {\n+\t\tstruct string_list_item *repo;\n+\t\tfor (repo = repos.items;\n+\t\t     !result && repo && repo < repos.items + repos.nr;\n+\t\t     repo++)\n+\t\t\tresult = run_job(job->string,\n+\t\t\t\t\t repo->string);\n+\t}\n+\n+\tstring_list_clear(&repos, 0);\n+\treturn result;\n+}\n+\n+static unsigned int get_loop_interval(void)\n+{\n+\t/* Default: 30 minutes */\n+\treturn 30 * 60;\n+}\n+\n+static int initialize_jobs(struct string_list *list)\n+{\n+\tstring_list_append(list, \"commit-graph\");\n+\tstring_list_append(list, \"fetch\");\n+\tstring_list_append(list, \"loose-objects\");\n+\tstring_list_append(list, \"pack-files\");\n+\treturn 0;\n+}\n+\n+int cmd_job_runner(int argc, const char **argv, const char *prefix)\n+{\n+\tint result;\n+\tstruct string_list job_list = STRING_LIST_INIT_DUP;\n+\tstatic struct option builtin_job_runner_options[] = {\n+\t\tOPT_CALLBACK_F(0, \"repo\",\n+\t\t\t       NULL,\n+\t\t\t       N_(\"<path>\"),\n+\t\t\t       N_(\"run jobs on the repository at <path>\"),\n+\t\t\t       PARSE_OPT_NONEG, arg_repos_append),\n+\t\tOPT_END(),\n+\t};\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(builtin_job_runner_usage,\n+\t\t\t\t   builtin_job_runner_options);\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_job_runner_options,\n+\t\t\t     builtin_job_runner_usage,\n+\t\t\t     0);\n+\n+\tresult = initialize_jobs(&job_list);\n+\n+\twhile (!(result = run_job_loop_step(&job_list))) {\n+\t\tunsigned int interval = get_loop_interval();\n+\t\tsleep(interval);\n+\t}\n+\n+\treturn result;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex 1cd2b415e46..cb835c74693 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -110,6 +110,7 @@ git-init                                mainporcelain           init\n git-instaweb                            ancillaryinterrogators          complete\n git-interpret-trailers                  purehelpers\n gitk                                    mainporcelain\n+git-job-runner                          plumbingmanipulators\n git-log                                 mainporcelain           info\n git-ls-files                            plumbinginterrogators\n git-ls-remote                           plumbinginterrogators\ndiff --git a/git.c b/git.c\nindex db5a43c8687..3a2da2c232f 100644\n--- a/git.c\n+++ b/git.c\n@@ -517,6 +517,7 @@ static struct cmd_struct commands[] = {\n \t{ \"init\", cmd_init_db },\n \t{ \"init-db\", cmd_init_db },\n \t{ \"interpret-trailers\", cmd_interpret_trailers, RUN_SETUP_GENTLY },\n+\t{ \"job-runner\", cmd_job_runner, RUN_SETUP_GENTLY },\n \t{ \"log\", cmd_log, RUN_SETUP },\n \t{ \"ls-files\", cmd_ls_files, RUN_SETUP },\n \t{ \"ls-remote\", cmd_ls_remote, RUN_SETUP_GENTLY },\n-- \ngitgitgadget\n\n"},{"id":"394708","messageId":"2c3c9d28a903e23c37e44f9783299f3c001f574e.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 11/15] job-runner: use config for loop interval","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:10Z","receivedAt":"2020-04-03T20:48:29Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git job-runner' process uses a sleep(X) call to pause its\noperation between iterations of the job loop. This defaults to 30\nminutes, but is now available to be done with longer or shorter\nintervals according to the job.loopInterval config option. For\nexample, a user may want the job loop to run once every five\nminutes while another wants the job loop to run once every six\nhours.\n\nThis config value is checked immediately before the sleep(X) call,\nwhich allows users to see the effect without restarting the\njob-runner process. However, the process will be paused until the\nprevious sleep(X) call returns and the job loop is executed.\n\nRFC QUESITON: Is this use of sleep(X) the best way to do this?\nIs there a better way to delay the process for a time interval, or\nuntil a specified time? This just seemed like the simplest option.\nThe job-runner is doing low-priority work on an unpredictable\nschedule by design, so sleep(X) seemd appropriate.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/job.txt | 4 ++++\n builtin/job-runner.c         | 6 +++++-\n 2 files changed, 9 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\nindex 7c799d66221..772001e6744 100644\n--- a/Documentation/config/job.txt\n+++ b/Documentation/config/job.txt\n@@ -1,3 +1,7 @@\n+job.loopInterval::\n+\tThe number of seconds to sleep between rounds of running\n+\tbackground jobs in `git job-runner`.\n+\n job.<job-name>.interval::\n \tThe minimum number of seconds between runs of\n \t`git run-job <job-name>` when running `git job-runner`.\ndiff --git a/builtin/job-runner.c b/builtin/job-runner.c\nindex aee55c106e8..7e37b122d99 100644\n--- a/builtin/job-runner.c\n+++ b/builtin/job-runner.c\n@@ -261,7 +261,11 @@ static int run_job_loop_step(struct string_list *list)\n static unsigned int get_loop_interval(void)\n {\n \t/* Default: 30 minutes */\n-\treturn 30 * 60;\n+\ttimestamp_t interval = 30 * 60;\n+\n+\ttry_get_timestamp(NULL, \".\", \"loopinterval\", &interval);\n+\n+\treturn interval;\n }\n \n static int initialize_jobs(struct string_list *list)\n-- \ngitgitgadget\n\n"},{"id":"394713","messageId":"18429182ffcf4cb56fcf3d8c76e41652a6c45adc.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 10/15] job-runner: use config to limit job frequency","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:09Z","receivedAt":"2020-04-03T20:48:29Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe want to run different maintenance tasks at different rates. That\nmeans we cannot rely only on the time between job-runner loop pauses\nto reduce the frequency of specific jobs. This means we need to\npersist a timestamp for the previous run somewhere. A natural place\nis the Git config file for that repo.\n\nCreate the job.<job-namme>.lastRun config option to store the\ntimestamp of the previous run of that job. Set this config option\nafter a successful run of 'git -C <repo> run-job <job-name>'.\n\nTo maximize code reuse, we dynamically construct the config key\nand parse the config value into a timestamp in a generic way. This\nmakes the introduction of another config option extremely trivial:\n\nThe job.<job-name>.interval option allows a user to specify a\nminimum number of seconds between two calls to 'git run-job\n<job-name>' on a given repo. This could be stored in the global\nor system config to provide an update on the default for all repos,\nor could be updated on a per-repo basis. This is checked on every\niteration of the job loop, so a user could update this and see the\neffect without restarting the job-runner process.\n\nRFC QUESTION: I'm using a 'git -C <repo> config <option>' process\nto test if enough time has elapsed before running the 'run-job'\nprocess. These 'config' processes are pretty light-weight, so\nhopefully they are not too noisy. An alternative would be to\nalways run 'git -C <repo> run-job <job-name>' and rely on that\nprocess to no-op based on the configured values and how recently\nthey were run. However, that will likely interrupt users who want\nto test the jobs in the foreground. Finally, that user disruption\nwould be mitigated by adding a \"--check-latest\" option. A user\nrunning a job manually would not include that argument by default\nand would succeed. However, any logging that we might do for the\njob-runner would make it look like we are running the run-job\ncommands all the time even if they are no-ops. Advice welcome!\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/job.txt  |  10 ++\n Documentation/git-run-job.txt |   3 +\n builtin/job-runner.c          | 177 +++++++++++++++++++++++++++++++++-\n 3 files changed, 189 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\nindex 2dfed3935fa..7c799d66221 100644\n--- a/Documentation/config/job.txt\n+++ b/Documentation/config/job.txt\n@@ -1,3 +1,13 @@\n+job.<job-name>.interval::\n+\tThe minimum number of seconds between runs of\n+\t`git run-job <job-name>` when running `git job-runner`.\n+\n+job.<job-name>.lastRun::\n+\tThe Unix epoch for the timestamp of the previous run of\n+\t`git run-job <job-name>` when running `git job-runner`. You\n+\tcan manually update this to a later time to delay a specific\n+\tjob on this repository.\n+\n job.pack-files.batchSize::\n \tThis string value `<size>` will be passed to the\n \t`git multi-pack-index repack --batch-size=<size>` command as\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nindex cdd6417f7c9..c6d5674d699 100644\n--- a/Documentation/git-run-job.txt\n+++ b/Documentation/git-run-job.txt\n@@ -90,6 +90,9 @@ pack-files, the job does not attempt to repack. Otherwise, the batch\n size is the sum of all pack-file sizes minus the largest pack-file size.\n The batch size is capped at two gigabytes. This intends to pack all\n small pack-files into a single pack-file.\n++\n+The `--batch-size=<size>` option will override the dynamic or configured\n+batch size.\n \n \n GIT\ndiff --git a/builtin/job-runner.c b/builtin/job-runner.c\nindex bed401f94bf..aee55c106e8 100644\n--- a/builtin/job-runner.c\n+++ b/builtin/job-runner.c\n@@ -4,11 +4,175 @@\n #include \"run-command.h\"\n #include \"string-list.h\"\n \n+#define MAX_TIMESTAMP ((timestamp_t)-1)\n+\n static char const * const builtin_job_runner_usage[] = {\n \tN_(\"git job-runner [<options>]\"),\n \tNULL\n };\n \n+static char *config_name(const char *prefix,\n+\t\t\t const char *job,\n+\t\t\t const char *postfix)\n+{\n+\tint postfix_dot = 0;\n+\tstruct strbuf name = STRBUF_INIT;\n+\n+\tif (prefix) {\n+\t\tstrbuf_addstr(&name, prefix);\n+\t\tstrbuf_addch(&name, '.');\n+\t}\n+\n+\tif (job) {\n+\t\tstrbuf_addstr(&name, job);\n+\t\tpostfix_dot = 1;\n+\t}\n+\n+\tif (postfix) {\n+\t\tif (postfix_dot)\n+\t\t\tstrbuf_addch(&name, '.');\n+\t\tstrbuf_addstr(&name, postfix);\n+\t}\n+\n+\treturn strbuf_detach(&name, NULL);\n+}\n+\n+static int try_get_config(const char *job,\n+\t\t\t  const char *repo,\n+\t\t\t  const char *postfix,\n+\t\t\t  char **value)\n+{\n+\tint result = 0;\n+\tFILE *proc_out;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tchar *cname = config_name(\"job\", job, postfix);\n+\tstruct child_process *config_proc = xmalloc(sizeof(*config_proc));\n+\n+\tchild_process_init(config_proc);\n+\n+\targv_array_push(&config_proc->args, \"git\");\n+\targv_array_push(&config_proc->args, \"-C\");\n+\targv_array_push(&config_proc->args, repo);\n+\targv_array_push(&config_proc->args, \"config\");\n+\targv_array_push(&config_proc->args, cname);\n+\tfree(cname);\n+\n+\tconfig_proc->out = -1;\n+\n+\tif (start_command(config_proc)) {\n+\t\twarning(_(\"failed to start process for repo '%s'\"),\n+\t\t\trepo);\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tproc_out = xfdopen(config_proc->out, \"r\");\n+\n+\t/* if there is no line, leave the value as given */\n+\tif (!strbuf_getline(&line, proc_out))\n+\t\t*value = strbuf_detach(&line, NULL);\n+\telse\n+\t\t*value = NULL;\n+\n+\tstrbuf_release(&line);\n+\n+\tfclose(proc_out);\n+\n+\tresult = finish_command(config_proc);\n+\n+cleanup:\n+\tfree(config_proc);\n+\treturn result;\n+}\n+\n+static int try_get_timestamp(const char *job,\n+\t\t\t     const char *repo,\n+\t\t\t     const char *postfix,\n+\t\t\t     timestamp_t *t)\n+{\n+\tchar *value;\n+\tint result = try_get_config(job, repo, postfix, &value);\n+\n+\tif (!result) {\n+\t\t*t = atol(value);\n+\t\tfree(value);\n+\t}\n+\n+\treturn result;\n+}\n+\n+static timestamp_t get_last_run(const char *job,\n+\t\t\t\tconst char *repo)\n+{\n+\ttimestamp_t last_run = 0;\n+\n+\t/* In an error state, do not run the job */\n+\tif (try_get_timestamp(job, repo, \"lastrun\", &last_run))\n+\t\treturn MAX_TIMESTAMP;\n+\n+\treturn last_run;\n+}\n+\n+static timestamp_t get_interval(const char *job,\n+\t\t\t\tconst char *repo)\n+{\n+\ttimestamp_t interval = MAX_TIMESTAMP;\n+\n+\t/* In an error state, do not run the job */\n+\tif (try_get_timestamp(job, repo, \"interval\", &interval))\n+\t\treturn MAX_TIMESTAMP;\n+\n+\tif (interval == MAX_TIMESTAMP) {\n+\t\t/* load defaults for each job */\n+\t\tif (!strcmp(job, \"commit-graph\") || !strcmp(job, \"fetch\"))\n+\t\t\tinterval = 60 * 60; /* 1 hour */\n+\t\telse\n+\t\t\tinterval = 24 * 60 * 60; /* 1 day */\n+\t}\n+\n+\treturn interval;\n+}\n+\n+static int set_last_run(const char *job,\n+\t\t\tconst char *repo,\n+\t\t\ttimestamp_t last_run)\n+{\n+\tint result = 0;\n+\tstruct strbuf last_run_string = STRBUF_INIT;\n+\tstruct child_process *config_proc = xmalloc(sizeof(*config_proc));\n+\tchar *cname = config_name(\"job\", job, \"lastrun\");\n+\n+\tstrbuf_addf(&last_run_string, \"%\"PRItime, last_run);\n+\n+\tchild_process_init(config_proc);\n+\n+\targv_array_push(&config_proc->args, \"git\");\n+\targv_array_push(&config_proc->args, \"-C\");\n+\targv_array_push(&config_proc->args, repo);\n+\targv_array_push(&config_proc->args, \"config\");\n+\targv_array_push(&config_proc->args, cname);\n+\targv_array_push(&config_proc->args, last_run_string.buf);\n+\tfree(cname);\n+\tstrbuf_release(&last_run_string);\n+\n+\tif (start_command(config_proc)) {\n+\t\twarning(_(\"failed to start process for repo '%s'\"),\n+\t\t\trepo);\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (finish_command(config_proc)) {\n+\t\twarning(_(\"failed to finish process for repo '%s'\"),\n+\t\t\trepo);\n+\t\tresult = 1;\n+\t}\n+\n+cleanup:\n+\tfree(config_proc);\n+\treturn result;\n+}\n+\n static struct string_list arg_repos = STRING_LIST_INIT_DUP;\n \n static int arg_repos_append(const struct option *opt,\n@@ -54,9 +218,20 @@ static int load_active_repos(struct string_list *repos)\n \n static int run_job(const char *job, const char *repo)\n {\n+\tint result;\n \tstruct argv_array cmd = ARGV_ARRAY_INIT;\n+\ttimestamp_t now = time(NULL);\n+\ttimestamp_t last_run = get_last_run(job, repo);\n+\ttimestamp_t interval = get_interval(job, repo);\n+\n+\tif (last_run + interval > now)\n+\t\treturn 0;\n+\n \targv_array_pushl(&cmd, \"-C\", repo, \"run-job\", job, NULL);\n-\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+\tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n+\n+\tset_last_run(job, repo, now);\n+\treturn result;\n }\n \n static int run_job_loop_step(struct string_list *list)\n-- \ngitgitgadget\n\n"},{"id":"394710","messageId":"78b0008d3f15fa16280ea1141d84edc3b4470c9e.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 13/15] job-runner: skip a job if job.<job-name>.enabled is false","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:12Z","receivedAt":"2020-04-03T20:48:31Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAllow a user to specify that a job should not be run on a given\nrepo by setting \"job.<job-name>.enabled = false\". The job-runner\nwill check this config before any other options and stop running\nthe job on that repo. If the config is disabled in the config\nread by the job-runner itself (i.e. in global config) then the\njob-runner will not attempt to run the job on any of the repos.\n\nSince this config is checked dynamically, the job-runner does will\nsee config changes without needing to restart the process.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/job.txt |  4 ++++\n builtin/job-runner.c         | 26 ++++++++++++++++++++++++--\n 2 files changed, 28 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\nindex 772001e6744..6c22a40dd36 100644\n--- a/Documentation/config/job.txt\n+++ b/Documentation/config/job.txt\n@@ -2,6 +2,10 @@ job.loopInterval::\n \tThe number of seconds to sleep between rounds of running\n \tbackground jobs in `git job-runner`.\n \n+job.<job-name>.enabled::\n+\tIf set to `false`, this option will disable the `<job-name>`\n+\twhen run by `git job-runner`.\n+\n job.<job-name>.interval::\n \tThe minimum number of seconds between runs of\n \t`git run-job <job-name>` when running `git job-runner`.\ndiff --git a/builtin/job-runner.c b/builtin/job-runner.c\nindex d1fca2c97b8..45f82a50d49 100644\n--- a/builtin/job-runner.c\n+++ b/builtin/job-runner.c\n@@ -216,13 +216,31 @@ static int load_active_repos(struct string_list *repos)\n \treturn 0;\n }\n \n+static int job_disabled(const char *job, const char *repo)\n+{\n+\tchar *enabled;\n+\n+\tif (!try_get_config(job, repo, \"enabled\", &enabled)) {\n+\t\tint enabled_int = git_parse_maybe_bool(enabled);\n+\t\tfree(enabled);\n+\t\treturn !enabled_int;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int run_job(const char *job, const char *repo)\n {\n \tint result;\n \tstruct argv_array cmd = ARGV_ARRAY_INIT;\n \ttimestamp_t now = time(NULL);\n-\ttimestamp_t last_run = get_last_run(job, repo);\n-\ttimestamp_t interval = get_interval(job, repo);\n+\ttimestamp_t last_run, interval;\n+\n+\tif (job_disabled(job, repo))\n+\t\treturn 0;\n+\n+\tlast_run = get_last_run(job, repo);\n+\tinterval = get_interval(job, repo);\n \n \tif (last_run + interval > now)\n \t\treturn 0;\n@@ -247,6 +265,10 @@ static int run_job_loop_step(struct string_list *list)\n \t     !result && job && job < list->items + list->nr;\n \t     job++) {\n \t\tstruct string_list_item *repo;\n+\n+\t\tif (job_disabled(job->string, \".\"))\n+\t\t\tcontinue;\n+\n \t\tfor (repo = repos.items;\n \t\t     !result && repo && repo < repos.items + repos.nr;\n \t\t     repo++)\n-- \ngitgitgadget\n\n"},{"id":"394709","messageId":"b22398b00443388ece4cd352ac347cd364fc11a3.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 14/15] job-runner: add --daemonize option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:13Z","receivedAt":"2020-04-03T20:48:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nGit has the ability to fork the process and launch a \"daemonized\"\ncopy of the current process. This is used to launch a background\n'git gc' command in some cases or to launch the 'git daemon'\nserver process.\n\nUpdate the 'git job-runner' command with a --daemonize option that\nlaunches this background process.\n\nThe implementation of daemonize() in setup.c is very clear that\nthis may no-op and return an error if NO_POSIX_GOODIES is not\ndefined. Include an error message to point out that this mechanism\nmay not be available on a platform-by-platform basis.\n\nInclude a clear error message that daemonize() might fail due to\nplatform incompatibilities.\n\nI have been running the current version of this series on my\nLinux VM using --daemonize to keep my copies of torvalds/linux and\ngit/git maintained. Using GIT_TRACE2_PERF set to a path, I can see\nthat the 'git run-job' processes are being created on the correct\nschedule according to my config for each.\n\nRFC QUESTION: I notice that 'git gc' does not document --daemonize.\nIs that intentional? Or is it an oversight?\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-job-runner.txt |  5 +++++\n builtin/job-runner.c             | 12 ++++++++++--\n cache.h                          |  4 +---\n daemon.h                         |  7 +++++++\n 4 files changed, 23 insertions(+), 5 deletions(-)\n create mode 100644 daemon.h\n\ndiff --git a/Documentation/git-job-runner.txt b/Documentation/git-job-runner.txt\nindex 0719113a008..f48d6bcd10b 100644\n--- a/Documentation/git-job-runner.txt\n+++ b/Documentation/git-job-runner.txt\n@@ -39,6 +39,11 @@ OPTIONS\n \tIf this is not specified, then the default will be found from\n \t`jobs.loopInterval` or the default value of 30 minutes.\n \n+--daemonize::\n+\tIf supported by your platform, launch an identical\n+\t`git job-runner` process in the background and close the\n+\tforeground process immediately.\n+\n \n CONFIGURATION\n -------------\ndiff --git a/builtin/job-runner.c b/builtin/job-runner.c\nindex 45f82a50d49..3b629428ef1 100644\n--- a/builtin/job-runner.c\n+++ b/builtin/job-runner.c\n@@ -1,5 +1,6 @@\n #include \"builtin.h\"\n #include \"config.h\"\n+#include \"daemon.h\"\n #include \"parse-options.h\"\n #include \"run-command.h\"\n #include \"string-list.h\"\n@@ -78,7 +79,8 @@ static int try_get_config(const char *job,\n \n \tfclose(proc_out);\n \n-\tresult = finish_command(config_proc);\n+\t/* ignore result as 'git config' fails on non-existent value */\n+\tfinish_command(config_proc);\n \n cleanup:\n \tfree(config_proc);\n@@ -93,7 +95,7 @@ static int try_get_timestamp(const char *job,\n \tchar *value;\n \tint result = try_get_config(job, repo, postfix, &value);\n \n-\tif (!result) {\n+\tif (!result && value) {\n \t\t*t = atol(value);\n \t\tfree(value);\n \t}\n@@ -304,6 +306,7 @@ static int initialize_jobs(struct string_list *list)\n \treturn 0;\n }\n \n+static int arg_daemonize = 0;\n int cmd_job_runner(int argc, const char **argv, const char *prefix)\n {\n \tint result;\n@@ -316,6 +319,8 @@ int cmd_job_runner(int argc, const char **argv, const char *prefix)\n \t\t\t       PARSE_OPT_NONEG, arg_repos_append),\n \t\tOPT_INTEGER(0, \"interval\", &arg_interval,\n \t\t\t    N_(\"seconds to pause between running any jobs\")),\n+\t\tOPT_BOOL(0, \"daemonize\", &arg_daemonize,\n+\t\t\t N_(\"request to spawn a background process\")),\n \t\tOPT_END(),\n \t};\n \n@@ -328,6 +333,9 @@ int cmd_job_runner(int argc, const char **argv, const char *prefix)\n \t\t\t     builtin_job_runner_usage,\n \t\t\t     0);\n \n+\tif (arg_daemonize && daemonize())\n+\t\tdie(_(\"failed to daemonize; this may not be available on your platform.\"));\n+\n \tresult = initialize_jobs(&job_list);\n \n \twhile (!(result = run_job_loop_step(&job_list))) {\ndiff --git a/cache.h b/cache.h\nindex c77b95870a5..34ef690faf6 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -17,6 +17,7 @@\n #include \"sha1-array.h\"\n #include \"repository.h\"\n #include \"mem-pool.h\"\n+#include \"daemon.h\"\n \n #include <zlib.h>\n typedef struct git_zstream {\n@@ -631,9 +632,6 @@ int init_db(const char *git_dir, const char *real_git_dir,\n \t    unsigned int flags);\n void initialize_repository_version(int hash_algo);\n \n-void sanitize_stdfds(void);\n-int daemonize(void);\n-\n #define alloc_nr(x) (((x)+16)*3/2)\n \n /**\ndiff --git a/daemon.h b/daemon.h\nnew file mode 100644\nindex 00000000000..c1e50a20354\n--- /dev/null\n+++ b/daemon.h\n@@ -0,0 +1,7 @@\n+#ifndef DAEMON_H__\n+#define DAEMON_H__\n+\n+void sanitize_stdfds(void);\n+int daemonize(void);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"394714","messageId":"ac7d624dc51823965482e6e8f0ef166767bafb78.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 12/15] job-runner: add --interval=<span> option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:11Z","receivedAt":"2020-04-03T20:48:34Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe previous change used the job.loopInterval config option to\nadjust the default sleep between iterations of the job loop in\n'git job-runner'. Now, add a command-line option so a user can\noverride the configured value with an explicit value.\n\nThe value specified by this command-line option cannot be\nchanged without restarting the job-runner process.\n\nRFC QUESTION: Are things like this useful, or should we only\nuse config options for this customization?\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-job-runner.txt | 6 ++++++\n builtin/job-runner.c             | 7 +++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/Documentation/git-job-runner.txt b/Documentation/git-job-runner.txt\nindex 010b2d05f9b..0719113a008 100644\n--- a/Documentation/git-job-runner.txt\n+++ b/Documentation/git-job-runner.txt\n@@ -33,6 +33,12 @@ OPTIONS\n \tattempts running jobs on repositories located at the provided\n \t`<dir>` values. This option can be specified multiple times.\n \n+--interval=<span>::\n+\tThe job runner pauses between each attempt to run jobs. Use the\n+\tspecified `<span>` as a number of seconds between each attempt.\n+\tIf this is not specified, then the default will be found from\n+\t`jobs.loopInterval` or the default value of 30 minutes.\n+\n \n CONFIGURATION\n -------------\ndiff --git a/builtin/job-runner.c b/builtin/job-runner.c\nindex 7e37b122d99..d1fca2c97b8 100644\n--- a/builtin/job-runner.c\n+++ b/builtin/job-runner.c\n@@ -258,11 +258,16 @@ static int run_job_loop_step(struct string_list *list)\n \treturn result;\n }\n \n+static unsigned int arg_interval = 0;\n+\n static unsigned int get_loop_interval(void)\n {\n \t/* Default: 30 minutes */\n \ttimestamp_t interval = 30 * 60;\n \n+\tif (arg_interval)\n+\t\treturn arg_interval;\n+\n \ttry_get_timestamp(NULL, \".\", \"loopinterval\", &interval);\n \n \treturn interval;\n@@ -287,6 +292,8 @@ int cmd_job_runner(int argc, const char **argv, const char *prefix)\n \t\t\t       N_(\"<path>\"),\n \t\t\t       N_(\"run jobs on the repository at <path>\"),\n \t\t\t       PARSE_OPT_NONEG, arg_repos_append),\n+\t\tOPT_INTEGER(0, \"interval\", &arg_interval,\n+\t\t\t    N_(\"seconds to pause between running any jobs\")),\n \t\tOPT_END(),\n \t};\n \n-- \ngitgitgadget\n\n"},{"id":"394712","messageId":"cd7b320216deaa2dd0a19e3da2bc9625e32a3fb4.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 07/15] config: add job.pack-files.batchSize option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:06Z","receivedAt":"2020-04-03T20:48:36Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe previous change allowed a user to specify a --batch-size=<size>\noption to 'git run-job pack-files'. However, when we eventually\nlaunch these jobs on a schedule, we want users to be able to change\nthis value through config options.\n\nThe new \"job.pack-files.batchSize\" option will override the default\ndynamic batch-size calculation, but will be overridden by the\n--batch-size=<size> argument.\n\nThis is the first config option of the type\n\"job.<job-name>.<option>\" but is not the last.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt     | 2 ++\n Documentation/config/job.txt | 6 ++++++\n builtin/run-job.c            | 7 ++++++-\n 3 files changed, 14 insertions(+), 1 deletion(-)\n create mode 100644 Documentation/config/job.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 2450589a0ed..c4c5fa99e6b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -389,6 +389,8 @@ include::config/instaweb.txt[]\n \n include::config/interactive.txt[]\n \n+include::config/job.txt[]\n+\n include::config/log.txt[]\n \n include::config/mailinfo.txt[]\ndiff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\nnew file mode 100644\nindex 00000000000..efdb76afad3\n--- /dev/null\n+++ b/Documentation/config/job.txt\n@@ -0,0 +1,6 @@\n+job.pack-files.batchSize::\n+\tThis string value `<size>` will be passed to the\n+\t`git multi-pack-index repack --batch-size=<size>` command as\n+\tpart of `git run-job pack-files`. If not specified, then a\n+\tdynamic size calculation is run. See linkgit:git-run-job[1]\n+\tfor more details.\ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nindex 2ccc3bbae2d..76765535e09 100644\n--- a/builtin/run-job.c\n+++ b/builtin/run-job.c\n@@ -327,6 +327,7 @@ static int multi_pack_index_repack(unsigned long batch_size)\n \tint result;\n \tstruct argv_array cmd = ARGV_ARRAY_INIT;\n \tstruct strbuf batch_arg = STRBUF_INIT;\n+\tconst char *config_value;\n \tint count;\n \toff_t default_size = get_auto_pack_size(&count);\n \n@@ -336,7 +337,11 @@ static int multi_pack_index_repack(unsigned long batch_size)\n \tstrbuf_addstr(&batch_arg, \"--batch-size=\");\n \n \tif (batch_size != UNSET_BATCH_SIZE)\n-\t\tstrbuf_addf(&batch_arg, \"\\\"%\"PRIuMAX\"\\\"\", (uintmax_t)batch_size);\n+\t\tstrbuf_addf(&batch_arg, \"\\\"%\"PRIuMAX\"\\\"\", (uintmax_t) batch_size);\n+\telse if (!repo_config_get_string_const(the_repository,\n+\t\t\t\t\t       \"job.pack-file.batchsize\",\n+\t\t\t\t\t       &config_value))\n+\t\tstrbuf_addf(&batch_arg, \"\\\"%s\\\"\", config_value);\n \telse\n \t\tstrbuf_addf(&batch_arg, \"%\"PRIuMAX,\n \t\t\t    (uintmax_t)default_size);\n-- \ngitgitgadget\n\n"},{"id":"394716","messageId":"84cab34e8f26cca7eedd07e58b99bd2152d90a7d.1585946894.git.gitgitgadget@gmail.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"[PATCH 15/15] runjob: customize the loose-objects batch size","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-04-03T20:48:14Z","receivedAt":"2020-04-03T20:48:39Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAllow a user to override the default number of loose objects to\nplace into a new pack-file as part of the loose-objects job. This\ncan be done via the job.loose-objects.batchSize config option or\nthe --batch-size=<count> option in the 'git run-job' command. The\nconfig value is checked once per run of 'git run-job loose-objects'\nso an instance started by 'git job-runner' will use new values\nautomatically without restarting the 'git job-runner' process.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/job.txt  |  6 ++++++\n Documentation/git-run-job.txt |  8 +++++---\n builtin/run-job.c             | 31 ++++++++++++++++++++++++++-----\n 3 files changed, 37 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\nindex 6c22a40dd36..baa5b927e14 100644\n--- a/Documentation/config/job.txt\n+++ b/Documentation/config/job.txt\n@@ -16,6 +16,12 @@ job.<job-name>.lastRun::\n \tcan manually update this to a later time to delay a specific\n \tjob on this repository.\n \n+job.loose-objects.batchSize::\n+\tThis string value `<count>` limits the number of loose-objects\n+\tcollected into a single pack-file during the `loose-objects`\n+\tjob. Default batch size is fifty thousand. See linkgit:git-run-job[1]\n+\tfor more details.\n+\n job.pack-files.batchSize::\n \tThis string value `<size>` will be passed to the\n \t`git multi-pack-index repack --batch-size=<size>` command as\ndiff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\nindex c6d5674d699..73210791533 100644\n--- a/Documentation/git-run-job.txt\n+++ b/Documentation/git-run-job.txt\n@@ -67,9 +67,11 @@ commands, it follows a two-step process. First, it deletes any loose\n objects that already exist in a pack-file; concurrent Git processes will\n examine the pack-file for the object data instead of the loose object.\n Second, it creates a new pack-file (starting with \"loose-\") containing\n-a batch of loose objects. The batch size is limited to 50 thousand\n-objects to prevent the job from taking too long on a repository with\n-many loose objects.\n+a batch of loose objects.\n++\n+By default, the batch size is limited to 50 thousand objects to prevent\n+the job from taking too long on a repository with many loose objects.\n+This can be overridden with the `--batch-size=<count>` option.\n \n 'pack-files'::\n \ndiff --git a/builtin/run-job.c b/builtin/run-job.c\nindex 76765535e09..b7c5a74cdbb 100644\n--- a/builtin/run-job.c\n+++ b/builtin/run-job.c\n@@ -13,6 +13,11 @@ static char const * const builtin_run_job_usage[] = {\n \tNULL\n };\n \n+static char const * const builtin_run_job_loose_objects_usage[] = {\n+\tN_(\"git run-job loose-objects [--batch-size=<count>]\"),\n+\tNULL\n+};\n+\n static char const * const builtin_run_job_pack_file_usage[] = {\n \tN_(\"git run-job pack-files [--batch-size=<size>]\"),\n \tNULL\n@@ -183,7 +188,7 @@ static int write_loose_object_to_stdin(const struct object_id *oid,\n \treturn ++(d->count) > d->batch_size;\n }\n \n-static int pack_loose(void)\n+static int pack_loose(int batch_size)\n {\n \tint result = 0;\n \tstruct write_loose_object_data data;\n@@ -219,7 +224,7 @@ static int pack_loose(void)\n \n \tdata.in = xfdopen(pack_proc->in, \"w\");\n \tdata.count = 0;\n-\tdata.batch_size = 50000;\n+\tdata.batch_size = batch_size;\n \n \tfor_each_loose_file_in_objdir(the_repository->objects->odb->path,\n \t\t\t\t      write_loose_object_to_stdin,\n@@ -240,9 +245,25 @@ static int pack_loose(void)\n \treturn result;\n }\n \n-static int run_loose_objects_job(void)\n+static int run_loose_objects_job(int argc, const char **argv)\n {\n-\treturn prune_packed() || pack_loose();\n+\tstatic int batch_size;\n+\tstatic struct option builtin_run_job_loose_objects_options[] = {\n+\t\tOPT_INTEGER(0, \"batch-size\", &batch_size,\n+\t\t\t    N_(\"specify the maximum number of loose objects to store in a pack-file\")),\n+\t\tOPT_END(),\n+\t};\n+\n+\tif (repo_config_get_int(the_repository,\n+\t\t\t\t\"job.loose-objects.batchsize\",\n+\t\t\t\t&batch_size))\n+\t\tbatch_size = 50000;\n+\n+\targc = parse_options(argc, argv, NULL,\n+\t\t\t     builtin_run_job_loose_objects_options,\n+\t\t\t     builtin_run_job_loose_objects_usage, 0);\n+\n+\treturn prune_packed() || pack_loose(batch_size);\n }\n \n static int multi_pack_index_write(void)\n@@ -427,7 +448,7 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n \t\tif (!strcmp(argv[0], \"fetch\"))\n \t\t\treturn run_fetch_job();\n \t\tif (!strcmp(argv[0], \"loose-objects\"))\n-\t\t\treturn run_loose_objects_job();\n+\t\t\treturn run_loose_objects_job(argc, argv);\n \t\tif (!strcmp(argv[0], \"pack-files\"))\n \t\t\treturn run_pack_files_job(argc, argv);\n \t}\n-- \ngitgitgadget\n"},{"id":"394721","messageId":"xmqqv9mgxn7u.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"pull.597.git.1585946894.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-03T21:40:05Z","receivedAt":"2020-04-03T21:40:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  * git run-job <job-name>: This builtin will run a single instance of a\n>    maintenance job.\n>    \n>  * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n>    that executes git run-job as a subcommand.\n\nWhat does this have to do with \"git\", though?  IOW, why does this\nhave to be part of Git, so that those who would benefit from having\na mechanism that makes it easy to run regular maintenance tasks but\nare not Git users (or those that want to do such maintenance tasks\nthat are not necessarily tied to \"git\") must use \"git\" to do so?\n\nI'll find out later why it is so after reading thru 15 patches\nmyself, so no need to give a quick answer to the above; it was just\nmy knee-jerk reaction.\n\nThanks.\n\n\n\n\n"},{"id":"394728","messageId":"cc9df614-2736-7cdd-006f-59878ee551c8@gmail.com","threadId":"53164","inReplyTo":"xmqqv9mgxn7u.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-04T00:16:21Z","receivedAt":"2020-04-04T00:16:31Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/3/2020 5:40 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>>  * git run-job <job-name>: This builtin will run a single instance of a\n>>    maintenance job.\n>>    \n>>  * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n>>    that executes git run-job as a subcommand.\n> \n> What does this have to do with \"git\", though?  IOW, why does this\n> have to be part of Git, so that those who would benefit from having\n> a mechanism that makes it easy to run regular maintenance tasks but\n> are not Git users (or those that want to do such maintenance tasks\n> that are not necessarily tied to \"git\") must use \"git\" to do so?\n> \n> I'll find out later why it is so after reading thru 15 patches\n> myself, so no need to give a quick answer to the above; it was just\n> my knee-jerk reaction.\n\nThat's a reasonable reaction. The short version of my reasoning is that\nmany many people _use_ Git but are not Git experts. While a Git expert\ncould find the right set of commands to run and at what frequency to\nkeep their repo clean, most users do not want to spend time learning\nthese commands. It's also worth our time as contributors to select what\na good set of non-intrusive maintenance tasks could be, and make them\neasily accessible to users.\n\nThis series gets us half of the way there: a user interested in doing\nbackground maintenance could figure out how to launch \"git run-job\" on\na schedule for their platform, or to launch \"git job-runner\" at start-\nup. That's a lot simpler than learning how the commit-graph,\nmulti-pack-index, prune-packed, pack-objects, and fetch builtins work\nwith the complicated sets of arguments.\n\nThe second half would be to create a command such as\n\n\tgit please-run-maintenance-on-this-repo\n\nthat initializes the background jobs and enables them on the repo they\nare using. This allows the most casual of Git user to work efficiently\non very large repositories.\n\nSometimes it is hard to remember that people use Git because it is an\nimportant tool for getting their work done. Time waiting for Git to do\na slow operation or being blocked on a triggered \"git gc --auto\" is\ntime they would rather be doing what they want to do. Background\nmaintenance is a way to reduce the time users spend blocked on Git and\nincrease their productivity on the more important things.\n\nOf course, I'm biased to using very large repositories where the\nexisting maintenance process is insufficient. The design of these jobs\nis taken directly from what we designed and built for VFS for Git and\nScalar over the winter of 2018-2019. These jobs were incredibly effective\nin cleaning up repositories that were accumulating cruft for over a year\nwithout any maintenance. Those repos have stayed clean and we haven't\nfound more maintenance tasks to be necesary.\n\nI still believe that there are plenty of repos of similar size to the\nLinux kernel that are in frequent use and could benefit from these\noperations.\n\nThanks,\n-Stolee\n"},{"id":"394788","messageId":"fc5e6823-ebb3-df58-d7fc-6fb2b58cffe8@gmail.com","threadId":"53164","inReplyTo":"665da239774419074a9bae49b9c92b340885bfa3.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/15] run-job: create barebones builtin","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-04-05T15:10:01Z","receivedAt":"2020-04-05T15:10:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> The 'git run-job' command will be used to execute a short-lived set\n> of maintenance activities by a background job manager. The intention\n> is to perform small batches of work that reduce the foreground time\n> taken by repository maintenance such as 'git gc --auto'.\n> \n> This change does the absolute minimum to create the builtin and show\n> the usage output.\n> \n> Provide an explicit warning that this command is experimental. The\n> set of jobs may change, and each job could alter its behavior in\n> future versions.\n> \n> RFC QUESTION: This builtin is based on the background maintenance in\n> Scalar. Specifically, this builtin is based on the \"scalar run <job>\"\n> command [1] [2]. My default thought was to make this a \"git run <job>\"\n> command to maximize similarity. However, it seems like \"git run\" is\n> too generic. Or, am I being overly verbose for no reason?\n\nHaving read through this series I wondered if we wanted a single git \ncommand such as 'git maintenance' (suggestions of better names welcome) \nand then 'git run-job' could become 'git maintenance run', 'git \njob-runner' would become another subcommand (run-jobs or schedule-jobs?) \nand the 'git please-run-maintenance-on-this-repo' you mentioned in you \nemail to Junio could become 'git maintenance init' (or maybe setup)\n\nBest Wishes\n\nPhillip\n\n> [1] https://github.com/microsoft/scalar/blob/master/docs/advanced.md#run-maintenance-in-the-foreground\n> [2] https://github.com/microsoft/scalar/blob/master/Scalar/CommandLine/RunVerb.cs\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>   .gitignore                    |  1 +\n>   Documentation/git-run-job.txt | 36 +++++++++++++++++++++++++++++++++++\n>   Makefile                      |  1 +\n>   builtin.h                     |  1 +\n>   builtin/run-job.c             | 28 +++++++++++++++++++++++++++\n>   command-list.txt              |  1 +\n>   git.c                         |  1 +\n>   t/t7900-run-job.sh            | 15 +++++++++++++++\n>   8 files changed, 84 insertions(+)\n>   create mode 100644 Documentation/git-run-job.txt\n>   create mode 100644 builtin/run-job.c\n>   create mode 100755 t/t7900-run-job.sh\n> \n> diff --git a/.gitignore b/.gitignore\n> index 188bd1c3de1..5dea9d3b96b 100644\n> --- a/.gitignore\n> +++ b/.gitignore\n> @@ -144,6 +144,7 @@\n>   /git-rev-parse\n>   /git-revert\n>   /git-rm\n> +/git-run-job\n>   /git-send-email\n>   /git-send-pack\n>   /git-serve\n> diff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\n> new file mode 100644\n> index 00000000000..0627b3ed259\n> --- /dev/null\n> +++ b/Documentation/git-run-job.txt\n> @@ -0,0 +1,36 @@\n> +git-run-job(1)\n> +==============\n> +\n> +NAME\n> +----\n> +git-run-job - Run a maintenance job. Intended for background operation.\n> +\n> +\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git run-job <job-name>'\n> +\n> +\n> +DESCRIPTION\n> +-----------\n> +\n> +Run a maintenance job on the current repository. This is available as a\n> +command for a few reasons. First, the background job feature can launch\n> +these commands on a schedule and each process will completely clear its\n> +memory when complete. Second, an expert user could create their own job\n> +schedule by running these jobs themselves.\n> +\n> +THIS COMMAND IS EXPERIMENTAL. THE SET OF AVAILABLE JOBS OR THEIR EXACT\n> +BEHAVIOR MAY BE ALTERED IN THE FUTURE.\n> +\n> +\n> +JOBS\n> +----\n> +\n> +TBD\n> +\n> +\n> +GIT\n> +---\n> +Part of the linkgit:git[1] suite\n> diff --git a/Makefile b/Makefile\n> index ef1ff2228f0..f5f9c4d9e94 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -1125,6 +1125,7 @@ BUILTIN_OBJS += builtin/rev-list.o\n>   BUILTIN_OBJS += builtin/rev-parse.o\n>   BUILTIN_OBJS += builtin/revert.o\n>   BUILTIN_OBJS += builtin/rm.o\n> +BUILTIN_OBJS += builtin/run-job.o\n>   BUILTIN_OBJS += builtin/send-pack.o\n>   BUILTIN_OBJS += builtin/shortlog.o\n>   BUILTIN_OBJS += builtin/show-branch.o\n> diff --git a/builtin.h b/builtin.h\n> index 2b25a80cde3..3e0ddaaf67f 100644\n> --- a/builtin.h\n> +++ b/builtin.h\n> @@ -220,6 +220,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix);\n>   int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n>   int cmd_revert(int argc, const char **argv, const char *prefix);\n>   int cmd_rm(int argc, const char **argv, const char *prefix);\n> +int cmd_run_job(int argc, const char **argv, const char *prefix);\n>   int cmd_send_pack(int argc, const char **argv, const char *prefix);\n>   int cmd_shortlog(int argc, const char **argv, const char *prefix);\n>   int cmd_show(int argc, const char **argv, const char *prefix);\n> diff --git a/builtin/run-job.c b/builtin/run-job.c\n> new file mode 100644\n> index 00000000000..2c78d053aa4\n> --- /dev/null\n> +++ b/builtin/run-job.c\n> @@ -0,0 +1,28 @@\n> +#include \"builtin.h\"\n> +#include \"config.h\"\n> +#include \"parse-options.h\"\n> +\n> +static char const * const builtin_run_job_usage[] = {\n> +\tN_(\"git run-job\"),\n> +\tNULL\n> +};\n> +\n> +int cmd_run_job(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstatic struct option builtin_run_job_options[] = {\n> +\t\tOPT_END(),\n> +\t};\n> +\n> +\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n> +\t\tusage_with_options(builtin_run_job_usage,\n> +\t\t\t\t   builtin_run_job_options);\n> +\n> +\tgit_config(git_default_config, NULL);\n> +\targc = parse_options(argc, argv, prefix,\n> +\t\t\t     builtin_run_job_options,\n> +\t\t\t     builtin_run_job_usage,\n> +\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n> +\n> +\tusage_with_options(builtin_run_job_usage,\n> +\t\t\t   builtin_run_job_options);\n> +}\n> diff --git a/command-list.txt b/command-list.txt\n> index 20878946558..1cd2b415e46 100644\n> --- a/command-list.txt\n> +++ b/command-list.txt\n> @@ -156,6 +156,7 @@ git-revert                              mainporcelain\n>   git-rev-list                            plumbinginterrogators\n>   git-rev-parse                           plumbinginterrogators\n>   git-rm                                  mainporcelain           worktree\n> +git-run-job                             plumbingmanipulators\n>   git-send-email                          foreignscminterface             complete\n>   git-send-pack                           synchingrepositories\n>   git-shell                               synchelpers\n> diff --git a/git.c b/git.c\n> index b07198fe036..db5a43c8687 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -566,6 +566,7 @@ static struct cmd_struct commands[] = {\n>   \t{ \"rev-parse\", cmd_rev_parse, NO_PARSEOPT },\n>   \t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\n>   \t{ \"rm\", cmd_rm, RUN_SETUP },\n> +\t{ \"run-job\", cmd_run_job, RUN_SETUP },\n>   \t{ \"send-pack\", cmd_send_pack, RUN_SETUP },\n>   \t{ \"shortlog\", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },\n>   \t{ \"show\", cmd_show, RUN_SETUP },\n> diff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\n> new file mode 100755\n> index 00000000000..1eac80b7ed3\n> --- /dev/null\n> +++ b/t/t7900-run-job.sh\n> @@ -0,0 +1,15 @@\n> +#!/bin/sh\n> +\n> +test_description='git run-job\n> +\n> +Testing the background jobs, in the foreground\n> +'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'help text' '\n> +\ttest_must_fail git run-job -h 2>err &&\n> +\ttest_i18ngrep \"usage: git run-job\" err\n> +'\n> +\n> +test_done\n> \n"},{"id":"394789","messageId":"0e924507-e77e-bff9-196a-e73f296a99d9@gmail.com","threadId":"53164","inReplyTo":"77b1da5d3063a2404cd750adfe3bb8be9b6c497d.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/15] run-job: implement fetch job","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-04-05T15:14:38Z","receivedAt":"2020-04-05T15:14:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> When working with very large repositories, an incremental 'git fetch'\n> command can download a large amount of data. If there are many other\n> users pushing to a common repo, then this data can rival the initial\n> pack-file size of a 'git clone' of a medium-size repo.\n> \n> Users may want to keep the data on their local repos as close as\n> possible to the data on the remote repos by fetching periodically in\n> the background. This can break up a large daily fetch into several\n> smaller hourly fetches.\n> \n> However, if we simply ran 'git fetch <remote>' in the background,\n> then the user running a foregroudn 'git fetch <remote>' would lose\n> some important feedback when a new branch appears or an existing\n> branch updates. This is especially true if a remote branch is\n> force-updated and this isn't noticed by the user because it occurred\n> in the background. Further, the functionality of 'git push\n> --force-with-lease' becomes suspect.\n> \n> When running 'git fetch <remote> <options>' in the background, use\n> the following options for careful updating:\n> \n> 1. --no-tags prevents getting a new tag when a user wants to see\n>     the new tags appear in their foreground fetches.\n> \n> 2. --refmap= removes the configured refspec which usually updates\n>     refs/remotes/<remote>/* with the refs advertised by the remote.\n> \n> 3. By adding a new refspec \"+refs/heads/*:refs/hidden/<remote>/*\"\n>     we can ensure that we actually load the new values somewhere in\n>     our refspace while not updating refs/heads or refs/remotes. By\n>     storing these refs here, the commit-graph job will update the\n>     commit-graph with the commits from these hidden refs.\n> \n> 4. --prune will delete the refs/hidden/<remote> refs that no\n>     longer appear on the remote.\n> \n> We've been using this step as a critical background job in Scalar\n> [1] (and VFS for Git). This solved a pain point that was showing up\n> in user reports: fetching was a pain! Users do not like waiting to\n> download the data that was created while they were away from their\n> machines. After implementing background fetch, the foreground fetch\n> commands sped up significantly because they mostly just update refs\n> and download a small amount of new data. The effect is especially\n> dramatic when paried with --no-show-forced-udpates (through\n> fetch.showForcedUpdates=false).\n> \n> [1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/FetchStep.cs\n> \n> RFC QUESTIONS:\n> \n> 1. One downside of the refs/hidden pattern is that 'git log' will\n>     decorate commits with twice as many refs if they appear at a\n>     remote ref (<remote>/<ref> _and_ refs/hidden/<remote>/<ref>). Is\n>     there an easy way to exclude a refspace from decorations? Should\n>     we make refs/hidden/* a \"special\" refspace that is excluded from\n>     decorations?\n\nHaving some way to specify which refs outside of \nrefs/{heads,remote,tags}/ to show or exclude from decorations would be \nuseful I think. Fetching to a hidden ref is a good idea (as are the \nother steps you outline above) but as you say we don't want it to show \nup in the output of 'git log' etc.\n\n> 2. This feature is designed for a desktop machine or equivalent\n>     that has a permanent wired network connection, and the machine\n>     stays on while the user is not present. For things like laptops\n>     with inconsistent WiFi connections (that may be metered) the\n>     feature can use the less stable connection more than the user\n>     wants. Of course, this feature is opt-in for Git, but in Scalar\n>     we have a \"scalar pause\" command [2] that pauses all maintenance\n>     for some amount of time. We should consider a similar mechanism\n>     for Git, but for the point of this series the user needs to set\n>     up the \"background\" part of these jobs manually.\n> \n> [2] https://github.com/microsoft/scalar/blob/master/Scalar/CommandLine/PauseVerb.cs\n> [3] https://github.com/microsoft/scalar/blob/master/docs/advanced.md#controlling-background-maintenance\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>   Documentation/git-run-job.txt | 13 ++++-\n>   builtin/run-job.c             | 89 ++++++++++++++++++++++++++++++++++-\n>   t/t7900-run-job.sh            | 22 +++++++++\n>   3 files changed, 122 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\n> index 8bf2762d650..eb92e891915 100644\n> --- a/Documentation/git-run-job.txt\n> +++ b/Documentation/git-run-job.txt\n> @@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n>   SYNOPSIS\n>   --------\n>   [verse]\n> -'git run-job commit-graph'\n> +'git run-job (commit-graph|fetch)'\n>   \n>   \n>   DESCRIPTION\n> @@ -47,6 +47,17 @@ since it will not expire `.graph` files that were in the previous\n>   `commit-graph-chain` file. They will be deleted by a later run based on\n>   the expiration delay.\n>   \n> +'fetch'::\n> +\n> +The `fetch` job updates the object directory with the latest objects\n> +from all registered remotes. For each remote, a `git fetch` command is\n> +run. The refmap is custom to avoid updating local or remote branches\n> +(those in `refs/heads` or `refs/remotes`). Instead, the remote refs are\n> +stored in `refs/hidden/<remote>/`. Also, no tags are updated.\n> ++\n> +This means that foreground fetches are still required to update the\n> +remote refs, but the users is notified when the branches and tags are\n> +updated on the remote.\n>   \n>   GIT\n>   ---\n> diff --git a/builtin/run-job.c b/builtin/run-job.c\n> index dd7709952d3..e59056b2918 100644\n> --- a/builtin/run-job.c\n> +++ b/builtin/run-job.c\n> @@ -7,7 +7,7 @@\n>   #include \"run-command.h\"\n>   \n>   static char const * const builtin_run_job_usage[] = {\n> -\tN_(\"git run-job commit-graph\"),\n> +\tN_(\"git run-job (commit-graph|fetch)\"),\n>   \tNULL\n>   };\n>   \n> @@ -60,6 +60,91 @@ static int run_commit_graph_job(void)\n>   \treturn 1;\n>   }\n>   \n> +static int fetch_remote(const char *remote)\n> +{\n> +\tint result;\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\tstruct strbuf refmap = STRBUF_INIT;\n> +\n> +\targv_array_pushl(&cmd, \"fetch\", remote, \"--quiet\", \"--prune\",\n> +\t\t\t \"--no-tags\", \"--refmap=\", NULL);\n> +\n> +\tstrbuf_addf(&refmap, \"+refs/heads/*:refs/hidden/%s/*\", remote);\n> +\targv_array_push(&cmd, refmap.buf);\n> +\n> +\tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +\n> +\tstrbuf_release(&refmap);\n> +\treturn result;\n> +}\n> +\n> +static int fill_remotes(struct string_list *remotes)\n\nIsn't there a easy way to get this using the config api rather than \nforking 'git remote'?\n\nBest Wishes\n\nPhillip\n\n> +{\n> +\tint result = 0;\n> +\tFILE *proc_out;\n> +\tstruct strbuf line = STRBUF_INIT;\n> +\tstruct child_process *remote_proc = xmalloc(sizeof(*remote_proc));\n> +\n> +\tchild_process_init(remote_proc);\n> +\n> +\targv_array_pushl(&remote_proc->args, \"git\", \"remote\", NULL);\n> +\n> +\tremote_proc->out = -1;\n> +\n> +\tif (start_command(remote_proc)) {\n> +\t\terror(_(\"failed to start 'git remote' process\"));\n> +\t\tresult = 1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tproc_out = xfdopen(remote_proc->out, \"r\");\n> +\n> +\t/* if there is no line, leave the value as given */\n> +\twhile (!strbuf_getline(&line, proc_out))\n> +\t\tstring_list_append(remotes, line.buf);\n> +\n> +\tstrbuf_release(&line);\n> +\n> +\tfclose(proc_out);\n> +\n> +\tif (finish_command(remote_proc)) {\n> +\t\terror(_(\"failed to finish 'git remote' process\"));\n> +\t\tresult = 1;\n> +\t}\n> +\n> +cleanup:\n> +\tfree(remote_proc);\n> +\treturn result;\n> +}\n> +\n> +static int run_fetch_job(void)\n> +{\n> +\tint result = 0;\n> +\tstruct string_list_item *item;\n> +\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n> +\n> +\tif (fill_remotes(&remotes)) {\n> +\t\terror(_(\"failed to fill remotes\"));\n> +\t\tresult = 1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\t/*\n> +\t * Do not modify the result based on the success of the 'fetch'\n> +\t * operation, as a loss of network could cause 'fetch' to fail\n> +\t * quickly. We do not want that to stop the rest of our\n> +\t * background operations.\n> +\t */\n> +\tfor (item = remotes.items;\n> +\t     item && item < remotes.items + remotes.nr;\n> +\t     item++)\n> +\t\tfetch_remote(item->string);\n> +\n> +cleanup:\n> +\tstring_list_clear(&remotes, 0);\n> +\treturn result;\n> +}\n> +\n>   int cmd_run_job(int argc, const char **argv, const char *prefix)\n>   {\n>   \tstatic struct option builtin_run_job_options[] = {\n> @@ -79,6 +164,8 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n>   \tif (argc > 0) {\n>   \t\tif (!strcmp(argv[0], \"commit-graph\"))\n>   \t\t\treturn run_commit_graph_job();\n> +\t\tif (!strcmp(argv[0], \"fetch\"))\n> +\t\t\treturn run_fetch_job();\n>   \t}\n>   \n>   \tusage_with_options(builtin_run_job_usage,\n> diff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\n> index 18b9bd26b3a..d3faeba135b 100755\n> --- a/t/t7900-run-job.sh\n> +++ b/t/t7900-run-job.sh\n> @@ -44,4 +44,26 @@ test_expect_success 'commit-graph job' '\n>   \t)\n>   '\n>   \n> +test_expect_success 'fetch job' '\n> +\tgit clone \"file://$(pwd)/server\" client &&\n> +\n> +\t# Before fetching, build a client commit-graph\n> +\tgit -C client run-job commit-graph &&\n> +\tchain=client/.git/objects/info/commit-graphs/commit-graph-chain &&\n> +\ttest_line_count = 1 $chain &&\n> +\n> +\tgit -C client branch -v --remotes >before-refs &&\n> +\ttest_commit -C server 24 &&\n> +\n> +\tgit -C client run-job fetch &&\n> +\tgit -C client branch -v --remotes >after-refs &&\n> +\ttest_cmp before-refs after-refs &&\n> +\ttest_cmp server/.git/refs/heads/master \\\n> +\t\t client/.git/refs/hidden/origin/master &&\n> +\n> +\t# the hidden ref should trigger a new layer in the commit-graph\n> +\tgit -C client run-job commit-graph &&\n> +\ttest_line_count = 2 $chain\n> +'\n> +\n>   test_done\n> \n"},{"id":"394790","messageId":"29e32e80-a546-6e43-8d03-bafca03681ec@gmail.com","threadId":"53164","inReplyTo":"9104ae46371a08967805f6ce27586ccade37972a.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 09/15] job-runner: load repos from config by default","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-04-05T15:18:53Z","receivedAt":"2020-04-05T15:19:02Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> The 'git job-runner' command was introduced with a '--repo=<path>'\n> option so a user could start a process with an explicit list of\n> repos. However, it is more likely that we will want 'git\n> job-runner' to start without any arguments and discover the set of\n> repos from another source.\n> \n> A natural source is the Git config. The 'git job-runner' does not\n> need to run in a Git repository, but the config could be located in\n> the global or system config.\n> \n> Create the job.repo config setting as a multi-valued config option.\n> \n> Read all values for job.repo whenever triggering an iteration of\n> the job loop. This allows a user to add or remove repos dynamically\n> without restarting the job-runner.\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>   Documentation/config/job.txt |  7 +++++++\n>   builtin/job-runner.c         | 20 ++++++++++++++++++++\n>   2 files changed, 27 insertions(+)\n> \n> diff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\n> index efdb76afad3..2dfed3935fa 100644\n> --- a/Documentation/config/job.txt\n> +++ b/Documentation/config/job.txt\n> @@ -4,3 +4,10 @@ job.pack-files.batchSize::\n>   \tpart of `git run-job pack-files`. If not specified, then a\n>   \tdynamic size calculation is run. See linkgit:git-run-job[1]\n>   \tfor more details.\n> +\n> +job.repo::\n> +\tStore an absolute path to a repository that wants background\n> +\tjobs run by `git job-runner`. This is a multi-valued config\n> +\toption, but it must be stored in a config file seen by the\n> +\tbackground runner. This may be the global or system config\n> +\tdepending on how `git job-runner` is launched on your system.\n> diff --git a/builtin/job-runner.c b/builtin/job-runner.c\n> index 135288bcaae..bed401f94bf 100644\n> --- a/builtin/job-runner.c\n> +++ b/builtin/job-runner.c\n> @@ -20,6 +20,9 @@ static int arg_repos_append(const struct option *opt,\n>   \n>   static int load_active_repos(struct string_list *repos)\n>   {\n> +\tstruct string_list_item *item;\n> +\tconst struct string_list *config_repos;\n> +\n>   \tif (arg_repos.nr) {\n>   \t\tstruct string_list_item *item;\n>   \t\tfor (item = arg_repos.items;\n> @@ -29,6 +32,23 @@ static int load_active_repos(struct string_list *repos)\n>   \t\treturn 0;\n>   \t}\n>   \n> +\tconfig_repos = git_config_get_value_multi(\"job.repo\");\n\nDoes this really re-read the config files as the commit message suggests \nor just return the cached values? Perhaps the runner could exec itself \nwith a --run-jobs option to actually run the jobs so that it sees any \nupdated config values.\n\nBest Wishes\n\nPhillip\n\n> +\n> +\tfor (item = config_repos->items;\n> +\t     item && item < config_repos->items + config_repos->nr;\n> +\t     item++) {\n> +\t\tDIR *dir = opendir(item->string);\n> +\n> +\t\tif (!dir)\n> +\t\t\tcontinue;\n> +\n> +\t\tclosedir(dir);\n> +\t\tstring_list_append(repos, item->string);\n> +\t}\n> +\n> +\tstring_list_sort(repos);\n> +\tstring_list_remove_duplicates(repos, 0);\n> +\n>   \treturn 0;\n>   }\n>   \n> \n"},{"id":"394791","messageId":"ca28ee06-aab1-a72b-7b7e-1a0c9a732cdb@gmail.com","threadId":"53164","inReplyTo":"18429182ffcf4cb56fcf3d8c76e41652a6c45adc.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 10/15] job-runner: use config to limit job frequency","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-04-05T15:24:40Z","receivedAt":"2020-04-05T15:28:48Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> We want to run different maintenance tasks at different rates. That\n> means we cannot rely only on the time between job-runner loop pauses\n> to reduce the frequency of specific jobs. This means we need to\n> persist a timestamp for the previous run somewhere. A natural place\n> is the Git config file for that repo.\n> \n> Create the job.<job-namme>.lastRun config option to store the\n> timestamp of the previous run of that job. Set this config option\n> after a successful run of 'git -C <repo> run-job <job-name>'.\n> \n> To maximize code reuse, we dynamically construct the config key\n> and parse the config value into a timestamp in a generic way. This\n> makes the introduction of another config option extremely trivial:\n> \n> The job.<job-name>.interval option allows a user to specify a\n> minimum number of seconds between two calls to 'git run-job\n> <job-name>' on a given repo. This could be stored in the global\n> or system config to provide an update on the default for all repos,\n> or could be updated on a per-repo basis. This is checked on every\n> iteration of the job loop, so a user could update this and see the\n> effect without restarting the job-runner process.\n> \n> RFC QUESTION: I'm using a 'git -C <repo> config <option>' process\n> to test if enough time has elapsed before running the 'run-job'\n> process. These 'config' processes are pretty light-weight, so\n> hopefully they are not too noisy. An alternative would be to\n> always run 'git -C <repo> run-job <job-name>' and rely on that\n> process to no-op based on the configured values and how recently\n> they were run.\n\nYou're still executing another process so it doesn't really save \nanything in the 'noop' case. In the case where something needs to be \ndone I think the extra config process is unlikely to be noticed \n(especially as 'git job' forks a lot anyway)\n\nBest Wishes\n\nPhillip\n\n> However, that will likely interrupt users who want\n> to test the jobs in the foreground. Finally, that user disruption\n> would be mitigated by adding a \"--check-latest\" option. A user\n> running a job manually would not include that argument by default\n> and would succeed. However, any logging that we might do for the\n> job-runner would make it look like we are running the run-job\n> commands all the time even if they are no-ops. Advice welcome!\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>   Documentation/config/job.txt  |  10 ++\n>   Documentation/git-run-job.txt |   3 +\n>   builtin/job-runner.c          | 177 +++++++++++++++++++++++++++++++++-\n>   3 files changed, 189 insertions(+), 1 deletion(-)\n> \n> diff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\n> index 2dfed3935fa..7c799d66221 100644\n> --- a/Documentation/config/job.txt\n> +++ b/Documentation/config/job.txt\n> @@ -1,3 +1,13 @@\n> +job.<job-name>.interval::\n> +\tThe minimum number of seconds between runs of\n> +\t`git run-job <job-name>` when running `git job-runner`.\n> +\n> +job.<job-name>.lastRun::\n> +\tThe Unix epoch for the timestamp of the previous run of\n> +\t`git run-job <job-name>` when running `git job-runner`. You\n> +\tcan manually update this to a later time to delay a specific\n> +\tjob on this repository.\n> +\n>   job.pack-files.batchSize::\n>   \tThis string value `<size>` will be passed to the\n>   \t`git multi-pack-index repack --batch-size=<size>` command as\n> diff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\n> index cdd6417f7c9..c6d5674d699 100644\n> --- a/Documentation/git-run-job.txt\n> +++ b/Documentation/git-run-job.txt\n> @@ -90,6 +90,9 @@ pack-files, the job does not attempt to repack. Otherwise, the batch\n>   size is the sum of all pack-file sizes minus the largest pack-file size.\n>   The batch size is capped at two gigabytes. This intends to pack all\n>   small pack-files into a single pack-file.\n> ++\n> +The `--batch-size=<size>` option will override the dynamic or configured\n> +batch size.\n>   \n>   \n>   GIT\n> diff --git a/builtin/job-runner.c b/builtin/job-runner.c\n> index bed401f94bf..aee55c106e8 100644\n> --- a/builtin/job-runner.c\n> +++ b/builtin/job-runner.c\n> @@ -4,11 +4,175 @@\n>   #include \"run-command.h\"\n>   #include \"string-list.h\"\n>   \n> +#define MAX_TIMESTAMP ((timestamp_t)-1)\n> +\n>   static char const * const builtin_job_runner_usage[] = {\n>   \tN_(\"git job-runner [<options>]\"),\n>   \tNULL\n>   };\n>   \n> +static char *config_name(const char *prefix,\n> +\t\t\t const char *job,\n> +\t\t\t const char *postfix)\n> +{\n> +\tint postfix_dot = 0;\n> +\tstruct strbuf name = STRBUF_INIT;\n> +\n> +\tif (prefix) {\n> +\t\tstrbuf_addstr(&name, prefix);\n> +\t\tstrbuf_addch(&name, '.');\n> +\t}\n> +\n> +\tif (job) {\n> +\t\tstrbuf_addstr(&name, job);\n> +\t\tpostfix_dot = 1;\n> +\t}\n> +\n> +\tif (postfix) {\n> +\t\tif (postfix_dot)\n> +\t\t\tstrbuf_addch(&name, '.');\n> +\t\tstrbuf_addstr(&name, postfix);\n> +\t}\n> +\n> +\treturn strbuf_detach(&name, NULL);\n> +}\n> +\n> +static int try_get_config(const char *job,\n> +\t\t\t  const char *repo,\n> +\t\t\t  const char *postfix,\n> +\t\t\t  char **value)\n> +{\n> +\tint result = 0;\n> +\tFILE *proc_out;\n> +\tstruct strbuf line = STRBUF_INIT;\n> +\tchar *cname = config_name(\"job\", job, postfix);\n> +\tstruct child_process *config_proc = xmalloc(sizeof(*config_proc));\n> +\n> +\tchild_process_init(config_proc);\n> +\n> +\targv_array_push(&config_proc->args, \"git\");\n> +\targv_array_push(&config_proc->args, \"-C\");\n> +\targv_array_push(&config_proc->args, repo);\n> +\targv_array_push(&config_proc->args, \"config\");\n> +\targv_array_push(&config_proc->args, cname);\n> +\tfree(cname);\n> +\n> +\tconfig_proc->out = -1;\n> +\n> +\tif (start_command(config_proc)) {\n> +\t\twarning(_(\"failed to start process for repo '%s'\"),\n> +\t\t\trepo);\n> +\t\tresult = 1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tproc_out = xfdopen(config_proc->out, \"r\");\n> +\n> +\t/* if there is no line, leave the value as given */\n> +\tif (!strbuf_getline(&line, proc_out))\n> +\t\t*value = strbuf_detach(&line, NULL);\n> +\telse\n> +\t\t*value = NULL;\n> +\n> +\tstrbuf_release(&line);\n> +\n> +\tfclose(proc_out);\n> +\n> +\tresult = finish_command(config_proc);\n> +\n> +cleanup:\n> +\tfree(config_proc);\n> +\treturn result;\n> +}\n> +\n> +static int try_get_timestamp(const char *job,\n> +\t\t\t     const char *repo,\n> +\t\t\t     const char *postfix,\n> +\t\t\t     timestamp_t *t)\n> +{\n> +\tchar *value;\n> +\tint result = try_get_config(job, repo, postfix, &value);\n> +\n> +\tif (!result) {\n> +\t\t*t = atol(value);\n> +\t\tfree(value);\n> +\t}\n> +\n> +\treturn result;\n> +}\n> +\n> +static timestamp_t get_last_run(const char *job,\n> +\t\t\t\tconst char *repo)\n> +{\n> +\ttimestamp_t last_run = 0;\n> +\n> +\t/* In an error state, do not run the job */\n> +\tif (try_get_timestamp(job, repo, \"lastrun\", &last_run))\n> +\t\treturn MAX_TIMESTAMP;\n> +\n> +\treturn last_run;\n> +}\n> +\n> +static timestamp_t get_interval(const char *job,\n> +\t\t\t\tconst char *repo)\n> +{\n> +\ttimestamp_t interval = MAX_TIMESTAMP;\n> +\n> +\t/* In an error state, do not run the job */\n> +\tif (try_get_timestamp(job, repo, \"interval\", &interval))\n> +\t\treturn MAX_TIMESTAMP;\n> +\n> +\tif (interval == MAX_TIMESTAMP) {\n> +\t\t/* load defaults for each job */\n> +\t\tif (!strcmp(job, \"commit-graph\") || !strcmp(job, \"fetch\"))\n> +\t\t\tinterval = 60 * 60; /* 1 hour */\n> +\t\telse\n> +\t\t\tinterval = 24 * 60 * 60; /* 1 day */\n> +\t}\n> +\n> +\treturn interval;\n> +}\n> +\n> +static int set_last_run(const char *job,\n> +\t\t\tconst char *repo,\n> +\t\t\ttimestamp_t last_run)\n> +{\n> +\tint result = 0;\n> +\tstruct strbuf last_run_string = STRBUF_INIT;\n> +\tstruct child_process *config_proc = xmalloc(sizeof(*config_proc));\n> +\tchar *cname = config_name(\"job\", job, \"lastrun\");\n> +\n> +\tstrbuf_addf(&last_run_string, \"%\"PRItime, last_run);\n> +\n> +\tchild_process_init(config_proc);\n> +\n> +\targv_array_push(&config_proc->args, \"git\");\n> +\targv_array_push(&config_proc->args, \"-C\");\n> +\targv_array_push(&config_proc->args, repo);\n> +\targv_array_push(&config_proc->args, \"config\");\n> +\targv_array_push(&config_proc->args, cname);\n> +\targv_array_push(&config_proc->args, last_run_string.buf);\n> +\tfree(cname);\n> +\tstrbuf_release(&last_run_string);\n> +\n> +\tif (start_command(config_proc)) {\n> +\t\twarning(_(\"failed to start process for repo '%s'\"),\n> +\t\t\trepo);\n> +\t\tresult = 1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tif (finish_command(config_proc)) {\n> +\t\twarning(_(\"failed to finish process for repo '%s'\"),\n> +\t\t\trepo);\n> +\t\tresult = 1;\n> +\t}\n> +\n> +cleanup:\n> +\tfree(config_proc);\n> +\treturn result;\n> +}\n> +\n>   static struct string_list arg_repos = STRING_LIST_INIT_DUP;\n>   \n>   static int arg_repos_append(const struct option *opt,\n> @@ -54,9 +218,20 @@ static int load_active_repos(struct string_list *repos)\n>   \n>   static int run_job(const char *job, const char *repo)\n>   {\n> +\tint result;\n>   \tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\ttimestamp_t now = time(NULL);\n> +\ttimestamp_t last_run = get_last_run(job, repo);\n> +\ttimestamp_t interval = get_interval(job, repo);\n> +\n> +\tif (last_run + interval > now)\n> +\t\treturn 0;\n> +\n>   \targv_array_pushl(&cmd, \"-C\", repo, \"run-job\", job, NULL);\n> -\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +\tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +\n> +\tset_last_run(job, repo, now);\n> +\treturn result;\n>   }\n>   \n>   static int run_job_loop_step(struct string_list *list)\n> \n"},{"id":"394792","messageId":"45ce32bc-8b47-76d6-47ed-03028df55e53@gmail.com","threadId":"53164","inReplyTo":"9104ae46371a08967805f6ce27586ccade37972a.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 09/15] job-runner: load repos from config by default","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-04-05T15:41:25Z","receivedAt":"2020-04-05T15:41:32Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Stolee\n\nOn 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> The 'git job-runner' command was introduced with a '--repo=<path>'\n> option so a user could start a process with an explicit list of\n> repos. However, it is more likely that we will want 'git\n> job-runner' to start without any arguments and discover the set of\n> repos from another source.\n\nOne thought that occurred to me was that it might be convenient to put \n'git job-runner $HOME &' in my .bashrc to have it start on login and \nfind all the repositories under $HOME without me having to list each one \n(and remember to update the list each time a clone a new repository). \nThere are a couple of issues with this\n  1 - We only want to run the jobs once per repo, not for each worktree. \nThat should be easy enough to implement by checking if we're in the main \nworktree.\n  2 - If I start several shells I only want one instance of 'git \njob-runner' to start. One way to handle that would be for the runner to \nhold a lock file in the directory it's given and quit if the lock is \nalready taken. It should probably walk up the directory hierarchy to \ncheck for lock files as well in case I try to start another job-runner \ninstance in a subdirectory.\n\nBest Wishes\n\nPhillip\n\n\n> A natural source is the Git config. The 'git job-runner' does not\n> need to run in a Git repository, but the config could be located in\n> the global or system config.\n> \n> Create the job.repo config setting as a multi-valued config option.\n> \n> Read all values for job.repo whenever triggering an iteration of\n> the job loop. This allows a user to add or remove repos dynamically\n> without restarting the job-runner.\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>   Documentation/config/job.txt |  7 +++++++\n>   builtin/job-runner.c         | 20 ++++++++++++++++++++\n>   2 files changed, 27 insertions(+)\n> \n> diff --git a/Documentation/config/job.txt b/Documentation/config/job.txt\n> index efdb76afad3..2dfed3935fa 100644\n> --- a/Documentation/config/job.txt\n> +++ b/Documentation/config/job.txt\n> @@ -4,3 +4,10 @@ job.pack-files.batchSize::\n>   \tpart of `git run-job pack-files`. If not specified, then a\n>   \tdynamic size calculation is run. See linkgit:git-run-job[1]\n>   \tfor more details.\n> +\n> +job.repo::\n> +\tStore an absolute path to a repository that wants background\n> +\tjobs run by `git job-runner`. This is a multi-valued config\n> +\toption, but it must be stored in a config file seen by the\n> +\tbackground runner. This may be the global or system config\n> +\tdepending on how `git job-runner` is launched on your system.\n> diff --git a/builtin/job-runner.c b/builtin/job-runner.c\n> index 135288bcaae..bed401f94bf 100644\n> --- a/builtin/job-runner.c\n> +++ b/builtin/job-runner.c\n> @@ -20,6 +20,9 @@ static int arg_repos_append(const struct option *opt,\n>   \n>   static int load_active_repos(struct string_list *repos)\n>   {\n> +\tstruct string_list_item *item;\n> +\tconst struct string_list *config_repos;\n> +\n>   \tif (arg_repos.nr) {\n>   \t\tstruct string_list_item *item;\n>   \t\tfor (item = arg_repos.items;\n> @@ -29,6 +32,23 @@ static int load_active_repos(struct string_list *repos)\n>   \t\treturn 0;\n>   \t}\n>   \n> +\tconfig_repos = git_config_get_value_multi(\"job.repo\");\n> +\n> +\tfor (item = config_repos->items;\n> +\t     item && item < config_repos->items + config_repos->nr;\n> +\t     item++) {\n> +\t\tDIR *dir = opendir(item->string);\n> +\n> +\t\tif (!dir)\n> +\t\t\tcontinue;\n> +\n> +\t\tclosedir(dir);\n> +\t\tstring_list_append(repos, item->string);\n> +\t}\n> +\n> +\tstring_list_sort(repos);\n> +\tstring_list_remove_duplicates(repos, 0);\n> +\n>   \treturn 0;\n>   }\n>   \n> \n"},{"id":"394799","messageId":"xmqqimidybzu.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"fc5e6823-ebb3-df58-d7fc-6fb2b58cffe8@gmail.com","subject":"Re: [PATCH 01/15] run-job: create barebones builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-05T19:21:41Z","receivedAt":"2020-04-05T19:21:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Stolee\n>\n> On 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> The 'git run-job' command will be used to execute a short-lived set\n>> of maintenance activities by a background job manager. The intention\n>> is to perform small batches of work that reduce the foreground time\n>> taken by repository maintenance such as 'git gc --auto'.\n>>\n>> This change does the absolute minimum to create the builtin and show\n>> the usage output.\n>>\n>> Provide an explicit warning that this command is experimental. The\n>> set of jobs may change, and each job could alter its behavior in\n>> future versions.\n>>\n>> RFC QUESTION: This builtin is based on the background maintenance in\n>> Scalar. Specifically, this builtin is based on the \"scalar run <job>\"\n>> command [1] [2]. My default thought was to make this a \"git run <job>\"\n>> command to maximize similarity. However, it seems like \"git run\" is\n>> too generic. Or, am I being overly verbose for no reason?\n>\n> Having read through this series I wondered if we wanted a single git\n> command such as 'git maintenance' (suggestions of better names\n> welcome) and then 'git run-job' could become 'git maintenance run',\n> 'git job-runner' would become another subcommand (run-jobs or\n> schedule-jobs?) and the 'git please-run-maintenance-on-this-repo' you\n> mentioned in you email to Junio could become 'git maintenance init'\n> (or maybe setup)\n\nI had a very similar impression.  In addition to what you already\nsaid, a few more were:\n\n - Why the existing \"git repack\" isn't such \"maintenance\" command?\n   IOW why do we even need [01/15]?  After all, \"repack\" may have\n   started its life as a tool to reorganize the PACKFILES, but it is\n   no longer limited to 'git/objects/pack/*.pack' files with its\n   knowledge about the loose object files and the \"--prune\" option.\n   Consolidating pieces of information spread across multiple .idx\n   files, reachability bitmaps and commit graph files, into a newer\n   and more performant forms can just be part of \"packing the pieces\n   of information in a repository for optimum performance\", which is\n   a better way to understand why \"repack\" has a word 'pack' in its\n   name.\n\n - Many of the \"maintenance\" operations this series proposes do make\n   sense, just like other \"maintenance\" operations we already have\n   in \"repack\", \"prune\", \"prune-packed\" etc., which are welcome\n   additions. \n\n - Like the individual steps that appear in e.g. \"repack\", however,\n   some of the individual steps in this series can be triggered by\n   calling underlying tools directly, allowing scripted maintenance\n   commands that suit individual needs better than the canned\n   invocation of \"run-job\", but I didn't get the impression that the\n   series strives to make sure that all knobs of these individual\n   steps are available to scripters who want to deviate from what\n   \"run-job\" prescribes.  If it is not doing so, we probably should.\n\n - Again, I do not think we want a reimplementation of cron, at or\n   inetd that is not specific to \"git\" at all.\n\nThanks.\n"},{"id":"394806","messageId":"xmqqeet1y8wy.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"77b1da5d3063a2404cd750adfe3bb8be9b6c497d.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/15] run-job: implement fetch job","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-05T20:28:13Z","receivedAt":"2020-04-05T20:28:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> 1. One downside of the refs/hidden pattern is that 'git log' will\n>    decorate commits with twice as many refs if they appear at a\n>    remote ref (<remote>/<ref> _and_ refs/hidden/<remote>/<ref>). Is\n>    there an easy way to exclude a refspace from decorations?\n\nI do not think there is, but it makes sense to teach the decoration\nmachinery to either use only certain refs hierarchies or use all\nhierarchies except for certain ones; if we want to make sure we\nwon't break existing workflows, we should by defautlt use all the\nrefs we currently use and nothing else, but over time we probably\nwould want to migrate the default to cover only the local and\nremote-tracking branches and tags (and at that point, refs/hidden\nwould be outside the decoration source).\n\nBy the way, I have a moderately strong opinion against the use of\n\"refs/hidden\" for the purpose of \"prefetch in advance, without\ndisrupting refs/remotes\".  There may be more than one reason why\nsome refs want to be \"hidden\", and depending on the purose, the\nexact refs that one workflow (e.g. \"decorate\") wants to hide may be\nthe ones that want to be exposed.\n\nIf we rename it to \"refs/prefetch/\", would it make the purpose of\nthe hierarchy clearer without squatting on a vague (because it does\nnot tell why it is hidden) name \"hidden\" that other people might\nwant to use to hide their stuff for different reasons?\n\n> Should\n>    we make refs/hidden/* a \"special\" refspace that is excluded from\n>    decorations?\n\nSee above.\n"},{"id":"394807","messageId":"xmqqa73py8ol.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"4c4aeb55dea9dd7b97bd7f08c8a767b498886109.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 04/15] run-job: implement loose-objects job","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-05T20:33:14Z","receivedAt":"2020-04-05T20:33:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Create a 'loose-objects' job for the 'git run-job' command. This\n> helps clean up loose objects without disrupting concurrent Git\n> commands using the following sequence of events:\n\nThis is exactly the kind of thing I would have expected you'd be\nadding to \"git repack\".\n"},{"id":"394825","messageId":"704d278d-205a-d11c-3454-73e06aa75dfa@gmail.com","threadId":"53164","inReplyTo":"xmqqeet1y8wy.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 03/15] run-job: implement fetch job","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-06T12:46:18Z","receivedAt":"2020-04-06T12:46:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/5/2020 4:28 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> 1. One downside of the refs/hidden pattern is that 'git log' will\n>>    decorate commits with twice as many refs if they appear at a\n>>    remote ref (<remote>/<ref> _and_ refs/hidden/<remote>/<ref>). Is\n>>    there an easy way to exclude a refspace from decorations?\n> \n> I do not think there is, but it makes sense to teach the decoration\n> machinery to either use only certain refs hierarchies or use all\n> hierarchies except for certain ones; if we want to make sure we\n> won't break existing workflows, we should by defautlt use all the\n> refs we currently use and nothing else, but over time we probably\n> would want to migrate the default to cover only the local and\n> remote-tracking branches and tags (and at that point, refs/hidden\n> would be outside the decoration source).\n\nI'll see what I can do about adding config to remove some refs from\ndecorations. That is immediately useful for Scalar users, too.\n\n> By the way, I have a moderately strong opinion against the use of\n> \"refs/hidden\" for the purpose of \"prefetch in advance, without\n> disrupting refs/remotes\".  There may be more than one reason why\n> some refs want to be \"hidden\", and depending on the purose, the\n> exact refs that one workflow (e.g. \"decorate\") wants to hide may be\n> the ones that want to be exposed.\n> \n> If we rename it to \"refs/prefetch/\", would it make the purpose of\n> the hierarchy clearer without squatting on a vague (because it does\n> not tell why it is hidden) name \"hidden\" that other people might\n> want to use to hide their stuff for different reasons?\n\nI like \"refs/prefetch\". That's more descriptive.\n\n>> Should\n>>    we make refs/hidden/* a \"special\" refspace that is excluded from\n>>    decorations?\n> \n> See above.\nThanks,\n-Stolee\n\n"},{"id":"394826","messageId":"e023974a-e1a0-31f6-e7ac-daf74312fb2c@gmail.com","threadId":"53164","inReplyTo":"0e924507-e77e-bff9-196a-e73f296a99d9@gmail.com","subject":"Re: [PATCH 03/15] run-job: implement fetch job","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-06T12:48:07Z","receivedAt":"2020-04-06T12:48:11Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/5/2020 11:14 AM, Phillip Wood wrote:\n> Hi Stolee\n> \n> On 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>> RFC QUESTIONS:\n>>\n>> 1. One downside of the refs/hidden pattern is that 'git log' will\n>>     decorate commits with twice as many refs if they appear at a\n>>     remote ref (<remote>/<ref> _and_ refs/hidden/<remote>/<ref>). Is\n>>     there an easy way to exclude a refspace from decorations? Should\n>>     we make refs/hidden/* a \"special\" refspace that is excluded from\n>>     decorations?\n> \n> Having some way to specify which refs outside of refs/{heads,remote,tags}/ to show or exclude from decorations would be useful I think. Fetching to a hidden ref is a good idea (as are the other steps you outline above) but as you say we don't want it to show up in the output of 'git log' etc.\n\nI'll work on this first. It seems less controversial.\n\n>> +static int fill_remotes(struct string_list *remotes)\n> \n> Isn't there a easy way to get this using the config api rather than forking 'git remote'?\n\nYou're right. I should have used the config here. I've been overly\nbiased to how we do it in the C# layer of Scalar _plus_ how I wanted\nthe job-runner to run \"outside\" a Git repository. This could be much\nsimpler with the config API.\n\nThanks,\n-Stolee\n"},{"id":"394827","messageId":"92e8290c-a8ff-fc4f-1da3-1ee8c2636bce@gmail.com","threadId":"53164","inReplyTo":"29e32e80-a546-6e43-8d03-bafca03681ec@gmail.com","subject":"Re: [PATCH 09/15] job-runner: load repos from config by default","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-06T12:49:34Z","receivedAt":"2020-04-06T12:49:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/5/2020 11:18 AM, Phillip Wood wrote:\n> Hi Stolee\n> \n> On 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n>>   +    config_repos = git_config_get_value_multi(\"job.repo\");\n> \n> Does this really re-read the config files as the commit message suggests or just return the cached values? Perhaps the runner could exec itself with a --run-jobs option to actually run the jobs so that it sees any updated config values.\n\nYou're right, I need to double-check that. I'm guessing that you are\nright and it uses the cached values. This should use the run_command()\npattern to ensure the config is as new as possible.\n\nThanks,\n-Stolee\n\n"},{"id":"394828","messageId":"c276e2a7-5dfc-ad3a-9b80-68c12eef02af@gmail.com","threadId":"53164","inReplyTo":"45ce32bc-8b47-76d6-47ed-03028df55e53@gmail.com","subject":"Re: [PATCH 09/15] job-runner: load repos from config by default","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-06T12:57:36Z","receivedAt":"2020-04-06T12:57:41Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/5/2020 11:41 AM, Phillip Wood wrote:\n> Hi Stolee\n> \n> On 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> The 'git job-runner' command was introduced with a '--repo=<path>'\n>> option so a user could start a process with an explicit list of\n>> repos. However, it is more likely that we will want 'git\n>> job-runner' to start without any arguments and discover the set of\n>> repos from another source.\n> \n> One thought that occurred to me was that it might be convenient to put\n> 'git job-runner $HOME &' in my .bashrc to have it start on login and\n> find all the repositories under $HOME without me having to list each\n> one (and remember to update the list each time a clone a new repository).\n> There are a couple of issues with this\n> 1 - We only want to run the jobs once per repo, not for each worktree.\n> That should be easy enough to implement by checking if we're in the\n> main worktree.\n\nThis idea of \"please check all (immediate) directories under <dir> for\nrepositories to run maintenance\" is an interesting one. It certainly\ncould be added later.\n\nYour concern about worktrees is actually not a big deal. Since we check\nthe config for the \"last run\" of a job on a repo, we will not run the\nsame job immediately on a worktree after running it on the original\n(unless the interval times are incredibly short).\n\n> 2 - If I start several shells I only want one instance of 'git\n> job-runner' to start. One way to handle that would be for the runner\n> to hold a lock file in the directory it's given and quit if the lock\n> is already taken. It should probably walk up the directory hierarchy\n> to check for lock files as well in case I try to start another\n> job-runner instance in a subdirectory.\n\nThis is an interesting idea. My gut reaction is that we don't want\nto prevent users from running multiple processes if they want to. But\nif they run the process twice in the same directory then they are\nlikely to be running on the same set of repos (barring \"-c jobs.repo\"\nor equivalent).\n\nAgain, my hope is that we would solve this problem by having a cross-\nplatform \"job service\" that makes the user setup of editing .bashrc\nirrelevant. Not only is that idea getting push back, we should allow\nexpert users the ability to customize these steps to their delight.\n\nThanks,\n-Stolee\n\n"},{"id":"394839","messageId":"208bdbc7-9c8e-5105-0627-7db86135db7b@gmail.com","threadId":"53164","inReplyTo":"xmqqimidybzu.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 01/15] run-job: create barebones builtin","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-06T14:42:23Z","receivedAt":"2020-04-06T14:42:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/5/2020 3:21 PM, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> Hi Stolee\n>>\n>> On 03/04/2020 21:48, Derrick Stolee via GitGitGadget wrote:\n>>> From: Derrick Stolee <dstolee@microsoft.com>\n>>>\n>>> The 'git run-job' command will be used to execute a short-lived set\n>>> of maintenance activities by a background job manager. The intention\n>>> is to perform small batches of work that reduce the foreground time\n>>> taken by repository maintenance such as 'git gc --auto'.\n>>>\n>>> This change does the absolute minimum to create the builtin and show\n>>> the usage output.\n>>>\n>>> Provide an explicit warning that this command is experimental. The\n>>> set of jobs may change, and each job could alter its behavior in\n>>> future versions.\n>>>\n>>> RFC QUESTION: This builtin is based on the background maintenance in\n>>> Scalar. Specifically, this builtin is based on the \"scalar run <job>\"\n>>> command [1] [2]. My default thought was to make this a \"git run <job>\"\n>>> command to maximize similarity. However, it seems like \"git run\" is\n>>> too generic. Or, am I being overly verbose for no reason?\n>>\n>> Having read through this series I wondered if we wanted a single git\n>> command such as 'git maintenance' (suggestions of better names\n>> welcome) and then 'git run-job' could become 'git maintenance run',\n>> 'git job-runner' would become another subcommand (run-jobs or\n>> schedule-jobs?) and the 'git please-run-maintenance-on-this-repo' you\n>> mentioned in you email to Junio could become 'git maintenance init'\n>> (or maybe setup)\n> \n> I had a very similar impression.  In addition to what you already\n> said, a few more were:\n> \n>  - Why the existing \"git repack\" isn't such \"maintenance\" command?\n>    IOW why do we even need [01/15]?  After all, \"repack\" may have\n>    started its life as a tool to reorganize the PACKFILES, but it is\n>    no longer limited to 'git/objects/pack/*.pack' files with its\n>    knowledge about the loose object files and the \"--prune\" option.\n>    Consolidating pieces of information spread across multiple .idx\n>    files, reachability bitmaps and commit graph files, into a newer\n>    and more performant forms can just be part of \"packing the pieces\n>    of information in a repository for optimum performance\", which is\n>    a better way to understand why \"repack\" has a word 'pack' in its\n>    name.\n\nTo me, \"git repack\" is a specific kind of maintenance. The end result\nis a pack-file. Now, \"git gc\" is a bit more general, because it will\ncreate a pack-file but also update the commit-graph file. Still, its\nname is still very specific: it \"collects garbage\". The goals of this\nseries are to replace \"git gc --auto\" with something less invasive.\n\nI'll include an alternate CLI proposal at the end of this message.\n\n>  - Many of the \"maintenance\" operations this series proposes do make\n>    sense, just like other \"maintenance\" operations we already have\n>    in \"repack\", \"prune\", \"prune-packed\" etc., which are welcome\n>    additions. \n\nThanks. I'm glad these steps make sense. They are definitely more\n\"incremental\" updates than a full repack or GC.\n \n>  - Like the individual steps that appear in e.g. \"repack\", however,\n>    some of the individual steps in this series can be triggered by\n>    calling underlying tools directly, allowing scripted maintenance\n>    commands that suit individual needs better than the canned\n>    invocation of \"run-job\", but I didn't get the impression that the\n>    series strives to make sure that all knobs of these individual\n>    steps are available to scripters who want to deviate from what\n>    \"run-job\" prescribes.  If it is not doing so, we probably should.\n> \n>  - Again, I do not think we want a reimplementation of cron, at or\n>    inetd that is not specific to \"git\" at all.\n\nI expected the job-runner to get some push-back. The design for it in\nthe current RFC matched how we do it in Scalar more than anything else.\nYou're probably right that it would be better to leave the \"background\"\npart to the platform.\n\nOf course, not every platform has \"cron\" but that just means we need a\ncross-platform way to launch Git processes on some schedule. That could\nbe a command that creates a cron job on platforms that have it, and on\nWindows it could create a scheduled task instead.\n\nBut what should we launch? It should probably be a Git command that\nchecks config for a list of repositories, then runs \"the maintenance\ncommand\" on each of those repos.\n\nI'm inserting a break here to draw the eye to a new proposed design:\n\n---\n\nCreate a \"git maintenance\" builtin. This has a few subcommands:\n\n1. \"run\" will run the configured maintenance on the current repo. This\n   should become the single entry point for users to say \"please clean\n   up my repo.\" What _exactly_ it does can be altered with config. I'll\n   list some possibilities after listing the subcommands.\n\n2. \"run-on-repos\" uses command-line arguments or config to launch \"git\n   -C <dir> maintenance run\" for all configured directories. The\n   intention is that this is launched on some schedule by a platform-\n   specific scheduling mechanism (i.e. cron).\n   (This subcommand could use a better name.)\n\n3. \"schedule\" adds the current repository to the configured list of\n   repositories for running with \"run-on-repos\". It will also initialize\n   the platform-specific scheduling mechanism. This may be to start the\n   schedule for the first time OR to update how frequent \"run-on-repos\"\n   is run, as appropriate.\n\n4. (OPTIONAL) \"mode <mode>\" adjusts the config for the current repo to\n   change the type of maintenance requested for this repo. For example,\n   \"simple\" could just run \"git gc --auto\" using a normal range.\n   \"incremental\" could run the maintenance tasks from this series.\n   Finally, \"server\" could run maintenance tasks as if we are serving\n   the repo to others, so we repack aggressively with full bitmaps, and\n   more frequently.\n\nHere are some possible maintenance tasks. Not all of them would\nbe appropriate to run on the same repo, or at least not with the\nsame frequency:\n\n* \"fetch\" : the background fetch from PATCH 3. Appropriate for all modes,\n  but perhaps would want users to opt-in to this in the  basic mode.\n\n* \"commit-graph\" : the incremental commit-graph writes from PATCH 2.\n  Appropriate whenever the \"fetch\" command is being run, but also\n  valuable for the \"server\" mode.\n\n* \"gc\" : Run \"git gc --auto\". This would be enabled by default, but\n  should be disabled for the \"incremental\" and \"server\" modes.\n\n* \"repack\" : Run \"git repack <options>\" with appropriate options based\n  on config. The \"server\" mode would include custom delta and bitmap\n  options. (I will leave the specifics to those who maintain servers to\n  recommend the best options for \"server\" mode.)\n\n* \"loose-objects\" : see PATCH 4. Appropriate for \"incremental\" mode.\n\n* \"multi-pack-index\" or \"incremental-repack\" : Run the \"pack-files\" job\n  from PATCH 5. Appropriate for \"incremental\" mode.\n\n* \"pack-refs\" : create a packed-refs file or repack the reftable as\n  appropriate for those features. (I have less familiarity with these.)\n\nNotice that with this new set of options we could do something rather\ndramatic: replace all calls to \"git gc --auto\" with \"git maintenance\nrun --auto\". By default, these would be equivalent. However, \"git\nmaintenance run --auto\" is more clear that the behavior is less specific\nthan \"git gc\" and could be configured to do something different.\n\nI used an \"--auto\" option in the suggestion above to help distinguish\nbetween the command being run as a foreground operation instead of a\nbackground operation. Part of setting up a schedule would include\ndisabling these \"foreground\" maintenance tasks and relying entirely on\nthe background tasks instead. The best situation would be to avoid\nlaunching the subprocess at all.\n\n---\n\nWhat do people think of this alternative? Does this get us closer to an\nappropriate level of work for Git to do?\n\nThanks,\n-Stolee\n"},{"id":"394897","messageId":"20200407005004.GB2568@danh.dev","threadId":"53164","inReplyTo":"cc9df614-2736-7cdd-006f-59878ee551c8@gmail.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Danh Doan","fromEmail":"congdanhqx@gmail.com","sentAt":"2020-04-07T00:50:04Z","receivedAt":"2020-04-07T00:50:09Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2020-04-03 20:16:21-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> On 4/3/2020 5:40 PM, Junio C Hamano wrote:\n> > \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > \n> >>  * git run-job <job-name>: This builtin will run a single instance of a\n> >>    maintenance job.\n> >>    \n> >>  * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n> >>    that executes git run-job as a subcommand.\n> > \n> > What does this have to do with \"git\", though?  IOW, why does this\n> > have to be part of Git, so that those who would benefit from having\n> > a mechanism that makes it easy to run regular maintenance tasks but\n> > are not Git users (or those that want to do such maintenance tasks\n> > that are not necessarily tied to \"git\") must use \"git\" to do so?\n\nI also agree with Junio,\nI don't think Git should be responsible to be a scheduler.\nIt's the job of either tranditional crontab, at on *nix, or scheduler\non Windows.\n\n> That's a reasonable reaction. The short version of my reasoning is that\n> many many people _use_ Git but are not Git experts. While a Git expert\n> could find the right set of commands to run and at what frequency to\n> keep their repo clean, most users do not want to spend time learning\n> these commands. It's also worth our time as contributors to select what\n\nAnd now, people will need to learn _both_ Git existing maintainance\ncommand, and new scheduler (Do I understand it right?, I haven't go\nthrough all patches)\n\nYes, it could be a setup it once and forget, but,\nif there's a problem with their local repo, they will scratch\ntheir head to understand what wrong with them.\n\nIt's easier to destroy their repo, and it's harder to know what\noperation is running in their computer.\n\n-- \nDanh\n"},{"id":"394898","messageId":"20200407005828.GC2568@danh.dev","threadId":"53164","inReplyTo":"208bdbc7-9c8e-5105-0627-7db86135db7b@gmail.com","subject":"Re: [PATCH 01/15] run-job: create barebones builtin","fromName":"Danh Doan","fromEmail":"congdanhqx@gmail.com","sentAt":"2020-04-07T00:58:28Z","receivedAt":"2020-04-07T00:58:34Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2020-04-06 10:42:23-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> Of course, not every platform has \"cron\" but that just means we need a\n> cross-platform way to launch Git processes on some schedule. That could\n> be a command that creates a cron job on platforms that have it, and on\n\nThere's Unix system that doesn't have cron.\nPeople could use other scheduler mechanism.\n\nA lot of systemd users uses systemd-timer.\nI'm using snooze.\nEach of those set of utilities have different grammar and\nconfiguration.\n\n> Windows it could create a scheduled task instead.\n> \n> But what should we launch? It should probably be a Git command that\n> checks config for a list of repositories, then runs \"the maintenance\n> command\" on each of those repos.\n> \n> I'm inserting a break here to draw the eye to a new proposed design:\n> \n> ---\n> \n> Create a \"git maintenance\" builtin. This has a few subcommands:\n> \n> 1. \"run\" will run the configured maintenance on the current repo. This\n>    should become the single entry point for users to say \"please clean\n>    up my repo.\" What _exactly_ it does can be altered with config. I'll\n>    list some possibilities after listing the subcommands.\n> \n> 2. \"run-on-repos\" uses command-line arguments or config to launch \"git\n>    -C <dir> maintenance run\" for all configured directories. The\n>    intention is that this is launched on some schedule by a platform-\n>    specific scheduling mechanism (i.e. cron).\n\nSo, IIUC, Git will have a _hard_ dependencies on cron on *nix?\nElse, we're gonna received a bug-report that some tools doesn't work?\n\nI've seen some bug report in our distro that \"git add -p\" doesn't work\nlike documented, because it's in \"git-perl\" packages.\nWhen we merge \"git-perl\" back to git, other people (who never use\n\"git add -p\" and git-sendemail) complain why does we add a hard dependencies\non perl to git.\n\n>    (This subcommand could use a better name.)\n> \n> 3. \"schedule\" adds the current repository to the configured list of\n>    repositories for running with \"run-on-repos\". It will also initialize\n>    the platform-specific scheduling mechanism. This may be to start the\n>    schedule for the first time OR to update how frequent \"run-on-repos\"\n>    is run, as appropriate.\n> \n> 4. (OPTIONAL) \"mode <mode>\" adjusts the config for the current repo to\n>    change the type of maintenance requested for this repo. For example,\n>    \"simple\" could just run \"git gc --auto\" using a normal range.\n>    \"incremental\" could run the maintenance tasks from this series.\n>    Finally, \"server\" could run maintenance tasks as if we are serving\n>    the repo to others, so we repack aggressively with full bitmaps, and\n>    more frequently.\n\n-- \nDanh\n"},{"id":"394900","messageId":"20200407014829.GL6369@camp.crustytoothpaste.net","threadId":"53164","inReplyTo":"cc9df614-2736-7cdd-006f-59878ee551c8@gmail.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-04-07T01:48:29Z","receivedAt":"2020-04-07T01:48:38Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-04-04 at 00:16:21, Derrick Stolee wrote:\n> On 4/3/2020 5:40 PM, Junio C Hamano wrote:\n> > \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > \n> >>  * git run-job <job-name>: This builtin will run a single instance of a\n> >>    maintenance job.\n> >> \n> >>  * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n> >>    that executes git run-job as a subcommand.\n> > \n> > What does this have to do with \"git\", though?  IOW, why does this\n> > have to be part of Git, so that those who would benefit from having\n> > a mechanism that makes it easy to run regular maintenance tasks but\n> > are not Git users (or those that want to do such maintenance tasks\n> > that are not necessarily tied to \"git\") must use \"git\" to do so?\n> > \n> > I'll find out later why it is so after reading thru 15 patches\n> > myself, so no need to give a quick answer to the above; it was just\n> > my knee-jerk reaction.\n> \n> That's a reasonable reaction. The short version of my reasoning is that\n> many many people _use_ Git but are not Git experts. While a Git expert\n> could find the right set of commands to run and at what frequency to\n> keep their repo clean, most users do not want to spend time learning\n> these commands. It's also worth our time as contributors to select what\n> a good set of non-intrusive maintenance tasks could be, and make them\n> easily accessible to users.\n> \n> This series gets us half of the way there: a user interested in doing\n> background maintenance could figure out how to launch \"git run-job\" on\n> a schedule for their platform, or to launch \"git job-runner\" at start-\n> up. That's a lot simpler than learning how the commit-graph,\n> multi-pack-index, prune-packed, pack-objects, and fetch builtins work\n> with the complicated sets of arguments.\n\nIf there are periodic tasks that should be done, even if only on large\nrepos, then let's have a git gc --periodic that does them.  I'm not sure\nthat fetch should be in that set, but nothing prevents users from doing\n\"git fetch origin && git gc --periodic\".  Let's make it as simple and\nstraightforward as possible.\n\nAs for handling multiple repositories, the tool to do that could be as\nsimple as a shell script which reads from ~/.config/git/repo-maintenance\n(or whatever) and runs the same command on all of the repos it finds\nthere, possibly with a subcommand to add and remove repos.\n\n> The second half would be to create a command such as\n> \n> \tgit please-run-maintenance-on-this-repo\n> \n> that initializes the background jobs and enables them on the repo they\n> are using. This allows the most casual of Git user to work efficiently\n> on very large repositories.\n\nI'm not opposed to seeing a tool that can schedule periodic maintenance\njobs, perhaps in contrib, depending on whether other people think it\nshould go.  However, I think running periodic jobs is best handled on\nUnix with cron or anacron and not a custom tool or a command in Git.\n\nI've dealt with systems that implemented periodic tasks without using\nthe existing tools for doing that, and I've found that usually that's a\nmistake.  Despite seeming straightforward, there are a lot of tricky\nedge cases to deal with and it's easy to get wrong.\n\nWe also don't have to reimplement all the features in the system\nscheduler and can let expert users use a different tool of their choice\ninstead if cron (or the Windows equivalent) is not to their liking.\n-- \nbrian m. carlson: Houston, Texas, US\nOpenPGP: https://keybase.io/bk2204\n"},{"id":"394910","messageId":"806dc7e9-9980-2c05-461f-9f3a62598244@gmail.com","threadId":"53164","inReplyTo":"20200407005828.GC2568@danh.dev","subject":"Re: [PATCH 01/15] run-job: create barebones builtin","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-07T10:54:33Z","receivedAt":"2020-04-07T10:54:41Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/6/2020 8:58 PM, Danh Doan wrote:\n> On 2020-04-06 10:42:23-0400, Derrick Stolee <stolee@gmail.com> wrote:\n>> Of course, not every platform has \"cron\" but that just means we need a\n>> cross-platform way to launch Git processes on some schedule. That could\n>> be a command that creates a cron job on platforms that have it, and on\n> \n> There's Unix system that doesn't have cron.\n> People could use other scheduler mechanism.\n> \n> A lot of systemd users uses systemd-timer.\n> I'm using snooze.\n\nThanks for listing some alternatives. I'll look into these.\n\n> Each of those set of utilities have different grammar and\n> configuration.\n> \n>> Windows it could create a scheduled task instead.\n\n>> 2. \"run-on-repos\" uses command-line arguments or config to launch \"git\n>>    -C <dir> maintenance run\" for all configured directories. The\n>>    intention is that this is launched on some schedule by a platform-\n>>    specific scheduling mechanism (i.e. cron).\n> \n> So, IIUC, Git will have a _hard_ dependencies on cron on *nix?\n> Else, we're gonna received a bug-report that some tools doesn't work?\n\nNo. Such a dependency would be unacceptable. I'm just using cron\nas an example when available.\n \n> I've seen some bug report in our distro that \"git add -p\" doesn't work\n> like documented, because it's in \"git-perl\" packages.\n> When we merge \"git-perl\" back to git, other people (who never use\n> \"git add -p\" and git-sendemail) complain why does we add a hard dependencies\n> on perl to git.\n\nGood news: \"git add -p\" is becoming a builtin with a lot of work by\nsome determined contributors.\n\nThanks,\n-Stolee\n\n"},{"id":"394911","messageId":"8946b9bc-06c2-3269-0fea-c9ba5b60d0ba@gmail.com","threadId":"53164","inReplyTo":"20200407005004.GB2568@danh.dev","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-04-07T10:59:08Z","receivedAt":"2020-04-07T10:59:13Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/6/2020 8:50 PM, Danh Doan wrote:\n> On 2020-04-03 20:16:21-0400, Derrick Stolee <stolee@gmail.com> wrote:\n>> On 4/3/2020 5:40 PM, Junio C Hamano wrote:\n>>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>\n>>>>  * git run-job <job-name>: This builtin will run a single instance of a\n>>>>    maintenance job.\n>>>>    \n>>>>  * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n>>>>    that executes git run-job as a subcommand.\n>>>\n>>> What does this have to do with \"git\", though?  IOW, why does this\n>>> have to be part of Git, so that those who would benefit from having\n>>> a mechanism that makes it easy to run regular maintenance tasks but\n>>> are not Git users (or those that want to do such maintenance tasks\n>>> that are not necessarily tied to \"git\") must use \"git\" to do so?\n> \n> I also agree with Junio,\n> I don't think Git should be responsible to be a scheduler.\n> It's the job of either tranditional crontab, at on *nix, or scheduler\n> on Windows.\n> \n>> That's a reasonable reaction. The short version of my reasoning is that\n>> many many people _use_ Git but are not Git experts. While a Git expert\n>> could find the right set of commands to run and at what frequency to\n>> keep their repo clean, most users do not want to spend time learning\n>> these commands. It's also worth our time as contributors to select what\n> \n> And now, people will need to learn _both_ Git existing maintainance\n> command, and new scheduler (Do I understand it right?, I haven't go\n> through all patches)\n\nThe point is that they would not need to learn the existing commands.\nThey could accept the community's \"best practices\" by running the\nsimple command to start background maintenance.\n\nIn an \"enterprise\" environment, the users would not even need to learn\nthe command in the first place, because the engineering tools team\ncould configure the maintenance tools using the necessary setup scripts\nto built the repo.\n\n> Yes, it could be a setup it once and forget, but,\n> if there's a problem with their local repo, they will scratch\n> their head to understand what wrong with them.\n> \n> It's easier to destroy their repo, and it's harder to know what\n> operation is running in their computer.\n\nThat's why we need to be careful. Luckily, these steps have been\ntested in the wild for over a year with great success (as part of\nVFS for Git).\n\nThanks,\n-Stolee\n\n"},{"id":"394928","messageId":"20200407141600.GA1963@danh.dev","threadId":"53164","inReplyTo":"806dc7e9-9980-2c05-461f-9f3a62598244@gmail.com","subject":"Re: [PATCH 01/15] run-job: create barebones builtin","fromName":"Danh Doan","fromEmail":"congdanhqx@gmail.com","sentAt":"2020-04-07T14:16:00Z","receivedAt":"2020-04-07T14:16:07Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2020-04-07 06:54:33-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> On 4/6/2020 8:58 PM, Danh Doan wrote:\n> > On 2020-04-06 10:42:23-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> >> Of course, not every platform has \"cron\" but that just means we need a\n> >> cross-platform way to launch Git processes on some schedule. That could\n> >> be a command that creates a cron job on platforms that have it, and on\n> > \n> > There's Unix system that doesn't have cron.\n> > People could use other scheduler mechanism.\n> > \n> > A lot of systemd users uses systemd-timer.\n> > I'm using snooze.\n> \n> Thanks for listing some alternatives. I'll look into these.\n\nI didn't mean to list those alternatives as only possible\nalternatives.\n\nThe point is people have their own preference to choose a scheduler\nthat suites their need.\n\nSomeone could use their own supervisor system with things like:\n\n\t#/bin/sh\n\n\tsleep 3600 # 1 hour\n\texec git cmd\n\nWhen \"git cmd\" exit, the supervisor will start the job again (because\nit's down and it needs to be run).\n\n> > Each of those set of utilities have different grammar and\n> > configuration.\n> > \n> >> Windows it could create a scheduled task instead.\n> \n> >> 2. \"run-on-repos\" uses command-line arguments or config to launch \"git\n> >>    -C <dir> maintenance run\" for all configured directories. The\n> >>    intention is that this is launched on some schedule by a platform-\n> >>    specific scheduling mechanism (i.e. cron).\n> > \n> > So, IIUC, Git will have a _hard_ dependencies on cron on *nix?\n> > Else, we're gonna received a bug-report that some tools doesn't work?\n> \n> No. Such a dependency would be unacceptable. I'm just using cron\n> as an example when available.\n\nThat will be too many possible solutions out there,\nI'm still not convinced on adding a scheduler to Git.\n\n> > I've seen some bug report in our distro that \"git add -p\" doesn't work\n> > like documented, because it's in \"git-perl\" packages.\n> > When we merge \"git-perl\" back to git, other people (who never use\n> > \"git add -p\" and git-sendemail) complain why does we add a hard dependencies\n> > on perl to git.\n> \n> Good news: \"git add -p\" is becoming a builtin with a lot of work by\n> some determined contributors.\n\nYeah, I knew it. t3701.{44,46} is also fixed with the builtin.\nBut, it will be some version into the future to be enabled by default.\n\nThe point is there're people that don't want to see a new hard\ndependencies for Git.\n\n-- \nDanh\n"},{"id":"394929","messageId":"20200407142647.GB1963@danh.dev","threadId":"53164","inReplyTo":"8946b9bc-06c2-3269-0fea-c9ba5b60d0ba@gmail.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Danh Doan","fromEmail":"congdanhqx@gmail.com","sentAt":"2020-04-07T14:26:47Z","receivedAt":"2020-04-07T14:26:57Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2020-04-07 06:59:08-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> On 4/6/2020 8:50 PM, Danh Doan wrote:\n> > On 2020-04-03 20:16:21-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> >> On 4/3/2020 5:40 PM, Junio C Hamano wrote:\n> >>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >>>\n> >>>>  * git run-job <job-name>: This builtin will run a single instance of a\n> >>>>    maintenance job.\n> >>>>    \n> >>>>  * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n> >>>>    that executes git run-job as a subcommand.\n> >>>\n> >>> What does this have to do with \"git\", though?  IOW, why does this\n> >>> have to be part of Git, so that those who would benefit from having\n> >>> a mechanism that makes it easy to run regular maintenance tasks but\n> >>> are not Git users (or those that want to do such maintenance tasks\n> >>> that are not necessarily tied to \"git\") must use \"git\" to do so?\n> > \n> > I also agree with Junio,\n> > I don't think Git should be responsible to be a scheduler.\n> > It's the job of either tranditional crontab, at on *nix, or scheduler\n> > on Windows.\n> > \n> >> That's a reasonable reaction. The short version of my reasoning is that\n> >> many many people _use_ Git but are not Git experts. While a Git expert\n> >> could find the right set of commands to run and at what frequency to\n> >> keep their repo clean, most users do not want to spend time learning\n> >> these commands. It's also worth our time as contributors to select what\n> > \n> > And now, people will need to learn _both_ Git existing maintainance\n> > command, and new scheduler (Do I understand it right?, I haven't go\n> > through all patches)\n> \n> The point is that they would not need to learn the existing commands.\n> They could accept the community's \"best practices\" by running the\n> simple command to start background maintenance.\n\nWe could provide some \"best practices\" by an FAQ.\nPeople can refer to it for \"best practices\" and run their favourite\nchoice of scheduler.\n\n> In an \"enterprise\" environment, the users would not even need to learn\n> the command in the first place, because the engineering tools team\n> could configure the maintenance tools using the necessary setup scripts\n> to built the repo.\n\nIn that \"enterprise\" environment, if the engineering tools team could\nconfigure the maintainance tools using the command that introduced\ntogether with this series, that very engineering tools team could\nconfigure the scheduler to run required Git command, or create their\nown wrappers. In such \"enterprise\" environment, most of computers'\nsoftware set are configured to be installed, the engineering tools\nteam know which software're installed in which system, they should\nknow which set of scheduler should be run, it should be simple for\nthem to configure their system.\n\n> > Yes, it could be a setup it once and forget, but,\n> > if there's a problem with their local repo, they will scratch\n> > their head to understand what wrong with them.\n> > \n> > It's easier to destroy their repo, and it's harder to know what\n> > operation is running in their computer.\n> \n> That's why we need to be careful. Luckily, these steps have been\n> tested in the wild for over a year with great success (as part of\n> VFS for Git).\n\nNo offence but I find this quote could be applied:\n\nThere are two ways of constructing a software design: One way is to\nmake it so simple that there are obviously no deficiencies, and the\nother way is to make it so complicated that there are no obvious\ndeficiencies. - Tony Hoare.\n\nAdding this set of commands to Git gonna made Git over-complicated,\nIMHO.\n\n-- \nDanh\n"},{"id":"394954","messageId":"nycvar.QRO.7.76.6.2004071623400.46@tvgsbejvaqbjf.bet","threadId":"53164","inReplyTo":"20200407141600.GA1963@danh.dev","subject":"Re: [PATCH 01/15] run-job: create barebones builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-04-07T14:30:35Z","receivedAt":"2020-04-07T14:30:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 7 Apr 2020, Danh Doan wrote:\n\n> On 2020-04-07 06:54:33-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> > On 4/6/2020 8:58 PM, Danh Doan wrote:\n> > > On 2020-04-06 10:42:23-0400, Derrick Stolee <stolee@gmail.com> wrote:\n> > >> Of course, not every platform has \"cron\" but that just means we need a\n> > >> cross-platform way to launch Git processes on some schedule. That could\n> > >> be a command that creates a cron job on platforms that have it, and on\n> > >\n> > > There's Unix system that doesn't have cron.\n> > > People could use other scheduler mechanism.\n> > >\n> > > A lot of systemd users uses systemd-timer.\n> > > I'm using snooze.\n> >\n> > Thanks for listing some alternatives. I'll look into these.\n>\n> I didn't mean to list those alternatives as only possible\n> alternatives.\n\nIn contrast, I think that they are _really_ alternatives, and they are\nonly options for people who are dedicated fans of fiddling with the\ntechnical details.\n\nIn other words, `cron` is a very viable option for a few people who are\n_not_ in the audience of this here patch series.\n\nThe audience of this patch series are software engineers who _have_ to use\nGit, but do not really want to spend their time learning about the\ninternal details. For those developers, especially those working on\ninsanely large repositories, we want to provide some convenient functions\n(much like `git gc --auto` tries to help developers who do not want to\nbother with Git details, but _better_ because it tries very much to stay\n_out_ of the way of the developer, which `git gc --auto` distinctly does\n_not_) that were developed using the experience with the world's largest\nrepository.\n\n> The point is people have their own preference to choose a scheduler\n> that suites their need.\n\nAnd they can!\n\nBut again, this here patch series is obviously for those who do not want\nto tinker with Git's functionality, yet still want to have decent\nperformance.\n\nLearning from the experience that led to the invention of `git gc --auto`,\nthere is _a large_ benefit in doing this: `git gc --auto` was invented\nbecause some prolific Linux contributors were experiencing abysmal\nperformance because they did not want to spend time learning how to\nkeep their repositories in a good shape, but rather they wanted to spend\ntime developing Linux kernel code.\n\n> Someone could use their own supervisor system with things like:\n>\n> \t#/bin/sh\n>\n> \tsleep 3600 # 1 hour\n> \texec git cmd\n>\n> When \"git cmd\" exit, the supervisor will start the job again (because\n> it's down and it needs to be run).\n\nSure. And that would work for developers who are interested in how the\nworld works, and have enough time to learn about this.\n\nIn my experience the vast majority of enterprise software developers are\nnot really as excited about Git internals as I am. This patch series is\nfor them. Because they are good people, too, and deserve our care in\nsupporting them.\n\nCiao,\nDscho\n"},{"id":"394955","messageId":"nycvar.QRO.7.76.6.2004071634570.46@tvgsbejvaqbjf.bet","threadId":"53164","inReplyTo":"20200407142647.GB1963@danh.dev","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-04-07T14:43:32Z","receivedAt":"2020-04-07T14:43:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Danh,\n\nOn Tue, 7 Apr 2020, Danh Doan wrote:\n\n> Adding this set of commands to Git gonna made Git over-complicated,\n> IMHO.\n\nLet me counter that by The Tale of the Green Button: while it is not\ncompletely historically accurate, it is a good illustration of focusing on\nWhat Matters Most:\n\nOnce upon a time, Xerox built really nice copy machines that were very\ngood and did a lot of useful things such as making one or more copies of\none or more pages at a certain set of paper sizes with a certain set of\npaper criteria (heavy paper, shiny paper, double-sided copies) etc.\n\nAnd the tale goes: nobody used it. Then a usability expert came in,\ninterviewed the users and figured out that it was too complicated to use.\nThe result was the big green button with which you can make one copy using\nthe standard paper size and the default paper.\n\nThis set of commands that you are complaining about is intended to be that\nBig Green Button.\n\nAlso, maybe we should not talk too loudly about \"making\" Git\nover-complicated. If you care to have a look at\nhttps://git-man-page-generator.lokaltog.net/ and find yourself being very\nmuch reminded of the current Git User Interface's complexity, you might\nagree that we should probably try to make _using_ Git less complicated.\n\nEven if it means adding new commands. Such as `git restore` and `git\nswitch`. And, yes, like `git job-runner` (or whatever we end up calling\nit, I do agree with Junio that `git maintenance` is a nice name for its\nintended purpose).\n\nDon't just believe me. I invite you to interview the software engineers\ndeveloping the Windows Operating System,. Or for that matter, _any_\nsoftware engineers working on projects substantially larger than git.git.\n\nCiao,\nDscho\n"},{"id":"395003","messageId":"xmqqzhbnrrdb.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"20200407014829.GL6369@camp.crustytoothpaste.net","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-07T20:08:16Z","receivedAt":"2020-04-07T20:08:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> If there are periodic tasks that should be done, even if only on large\n> repos, then let's have a git gc --periodic that does them.  I'm not sure\n> that fetch should be in that set, but nothing prevents users from doing\n> \"git fetch origin && git gc --periodic\".  Let's make it as simple and\n> straightforward as possible.\n\nYeah, thanks for bringing up \"git gc\" here.  Earlier I mumbled\nsometrhing about \"git repack\", which seemed to have needlessly\nmuddied the discussion, but \"gc\" is exactly the \"we already have a\nwrapper the users are familiar with---why not extend it\" thing.\n\n> I'm not opposed to seeing a tool that can schedule periodic maintenance\n> jobs, perhaps in contrib, depending on whether other people think it\n> should go.  However, I think running periodic jobs is best handled on\n> Unix with cron or anacron and not a custom tool or a command in Git.\n>\n> I've dealt with systems that implemented periodic tasks without using\n> the existing tools for doing that, and I've found that usually that's a\n> mistake.  Despite seeming straightforward, there are a lot of tricky\n> edge cases to deal with and it's easy to get wrong.\n>\n> We also don't have to reimplement all the features in the system\n> scheduler and can let expert users use a different tool of their choice\n> instead if cron (or the Windows equivalent) is not to their liking.\n\n;-)  You said it much better than I would have myself.  Thanks.\n"},{"id":"395019","messageId":"nycvar.QRO.7.76.6.2004072355100.46@tvgsbejvaqbjf.bet","threadId":"53164","inReplyTo":"20200407014829.GL6369@camp.crustytoothpaste.net","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-04-07T22:23:43Z","receivedAt":"2020-04-07T22:24:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi brian,\n\nOn Tue, 7 Apr 2020, brian m. carlson wrote:\n\n> On 2020-04-04 at 00:16:21, Derrick Stolee wrote:\n> > On 4/3/2020 5:40 PM, Junio C Hamano wrote:\n> > > \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > >\n> > >>  * git run-job <job-name>: This builtin will run a single instance of a\n> > >>    maintenance job.\n> > >>\n> > >>  * git job-runner [--repo=<path>]: This builtin will run an infinite loop\n> > >>    that executes git run-job as a subcommand.\n> > >\n> > > What does this have to do with \"git\", though?  IOW, why does this\n> > > have to be part of Git, so that those who would benefit from having\n> > > a mechanism that makes it easy to run regular maintenance tasks but\n> > > are not Git users (or those that want to do such maintenance tasks\n> > > that are not necessarily tied to \"git\") must use \"git\" to do so?\n> > >\n> > > I'll find out later why it is so after reading thru 15 patches\n> > > myself, so no need to give a quick answer to the above; it was just\n> > > my knee-jerk reaction.\n> >\n> > That's a reasonable reaction. The short version of my reasoning is that\n> > many many people _use_ Git but are not Git experts. While a Git expert\n> > could find the right set of commands to run and at what frequency to\n> > keep their repo clean, most users do not want to spend time learning\n> > these commands. It's also worth our time as contributors to select what\n> > a good set of non-intrusive maintenance tasks could be, and make them\n> > easily accessible to users.\n> >\n> > This series gets us half of the way there: a user interested in doing\n> > background maintenance could figure out how to launch \"git run-job\" on\n> > a schedule for their platform, or to launch \"git job-runner\" at start-\n> > up. That's a lot simpler than learning how the commit-graph,\n> > multi-pack-index, prune-packed, pack-objects, and fetch builtins work\n> > with the complicated sets of arguments.\n>\n> If there are periodic tasks that should be done, even if only on large\n> repos, then let's have a git gc --periodic that does them.  I'm not sure\n> that fetch should be in that set, but nothing prevents users from doing\n> \"git fetch origin && git gc --periodic\".\n\nHmm. Who says that maintenance tasks are essentially only `gc`? With\n_maaaaaybe_ a `fetch` thrown in?\n\nAnd about \"nothing prevents users from doing ...\": while that is true,\n_even less_ is preventing those same users from _forgetting_ to run them,\nor _not even knowing_ about the need to run them.\n\nI vividly remember how `git gc --auto` was introduced. A Linux developer\ntoo many was complaining about their Git operations becoming slower and\nslower over the course of some months.\n\nThat is a very illustrative example of what we're trying to accomplish\nhere: we are dealing with people whose job it is not to become Git\nexperts, but whose job is to develop software, and it just so happens that\nthey use Git as version control system. For the most part, they are not\neven all that interested in the internals of Git. Strange, I know.\n\nThe solution then, and now, is to teach Git to do these things for the\nusers, without the need to know a specific command and when to run it.\n\nI am not saying that the current state of the design is perfect. What I am\nsaying is that the goal is very clear, and important, and we must try to\nget there: to teach Git to run maintenance for the user, without much in\nthe way of the user asking for it, but Git knowing when to do what, and\ntrying mostly to stay out of the users' way.\n\n> Let's make it as simple and straightforward as possible.\n\nI fear that we're running the danger of confusing _two_ meanings of \"as\nsimple and straightforward as possible\" here.\n\nFrom my perspective, Stolee's patches aim to make using Git as simple and\nstraightforward as possible, and the historical record shows that Scalar\n(after which they are modeled) does a pretty good job at that, or is at\nleast heading in the right direction.\n\nI get the impression, however, that many reviewers here seem to favor the\ngoal of making the _patches_ as simple and straightforward as possible,\nhowever, at the expense of the original goal. Like, totally sacrificing\nthe ease of use in return for \"just use a shell script\" advice.\n\n> As for handling multiple repositories, the tool to do that could be as\n> simple as a shell script which reads from ~/.config/git/repo-maintenance\n> (or whatever) and runs the same command on all of the repos it finds\n> there, possibly with a subcommand to add and remove repos.\n\nSure, that is flexible.\n\nAnd it requires a ton of Git expertise to know what to put into those\nscripts. And Git updates cannot deliver more value to those scripts.\n\nWhich taps right into the above-mentioned focus on _the patches'_\nsimplicity, as opposed to the original goal of making _using Git_ simpler\nand less painful with big repositories.\n\n> > The second half would be to create a command such as\n> >\n> > \tgit please-run-maintenance-on-this-repo\n> >\n> > that initializes the background jobs and enables them on the repo they\n> > are using. This allows the most casual of Git user to work efficiently\n> > on very large repositories.\n>\n> I'm not opposed to seeing a tool that can schedule periodic maintenance\n> jobs, perhaps in contrib, depending on whether other people think it\n> should go.  However, I think running periodic jobs is best handled on\n> Unix with cron or anacron and not a custom tool or a command in Git.\n\nOkay, here is a challenge for you: design this such that the Windows\nexperience does _not_ feel like a 3rd-class citizen. Go ahead. Yes, there\nis a scheduler. Yep, it does not do cron-like things. Precisely: you have\nto feed it an XML to make use of the \"advanced\" features. Yeah, I also\ncannot remember what the semantics are regarding missed jobs due to\nshutdown cycles. Nope, you cannot rely on the XML being an option, that\nwould require Windows 10. The list goes on.\n\nDo you see where I am getting at?\n\nIt's really asking the user to take on _all_ the burden of the complexity.\nThe complexity has to live _somewhere_, after all, and if you want to\nprevent Git from taking care of it, there are only the users left to hold\nthe candle.\n\nI'd rather Git take up that responsibility, and lift that burden from the\nusers' shoulders. It's not like we have a stellar record of usability, so\nwe might just as well welcome Stolee's effort to improve Git in that\nrespect.\n\n> I've dealt with systems that implemented periodic tasks without using\n> the existing tools for doing that, and I've found that usually that's a\n> mistake.  Despite seeming straightforward, there are a lot of tricky\n> edge cases to deal with and it's easy to get wrong.\n\nThat might be true for general-purpose periodic taks managers.\n\nBut that's not what we need in Git, do we? The scope is a _lot_ more\nreduced.\n\nBut maybe you found one of those issues in Stolee's patches? If so, please\ndo contribute your experience there to point out those issues, so that\nthey can be addressed.\n\n> We also don't have to reimplement all the features in the system\n> scheduler and can let expert users use a different tool of their choice\n> instead if cron (or the Windows equivalent) is not to their liking.\n\nDo we really want to start relying on `cron`, when the major platform used\nby the target audience (enterprise software engineers who deal with rather\nlarger repositories than git.git or linux.git) quite obviously _lacks_\nsupport for that?\n\nThat would be a decision that would not make sense to me. I mean, why go\nto all the lengths of making it easier and less involved to manage large\nrepositories, and then just slam the door shut into the faces of all of\nthose developers working on Windows, an operating system with a rather\nlarge market share?\n\nCiao,\nDscho\n"},{"id":"395029","messageId":"20200408000149.GN6369@camp.crustytoothpaste.net","threadId":"53164","inReplyTo":"nycvar.QRO.7.76.6.2004072355100.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-04-08T00:01:49Z","receivedAt":"2020-04-08T00:01:57Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Hey,\n\nOn 2020-04-07 at 22:23:43, Johannes Schindelin wrote:\n> > If there are periodic tasks that should be done, even if only on large\n> > repos, then let's have a git gc --periodic that does them.  I'm not sure\n> > that fetch should be in that set, but nothing prevents users from doing\n> > \"git fetch origin && git gc --periodic\".\n> \n> Hmm. Who says that maintenance tasks are essentially only `gc`? With\n> _maaaaaybe_ a `fetch` thrown in?\n\nWhat I'm saying is that we have a tool to run maintenance tasks on the\nrepository.  If we need to perform additional maintenance tasks, let's\nput them in the same place as the ones we have now.  I realize \"gc\" may\nbecome a less accurate name, but oh, well.\n\n> > Let's make it as simple and straightforward as possible.\n> \n> I get the impression, however, that many reviewers here seem to favor the\n> goal of making the _patches_ as simple and straightforward as possible,\n> however, at the expense of the original goal. Like, totally sacrificing\n> the ease of use in return for \"just use a shell script\" advice.\n\nI think we can have both.  They are not mutually exclusive, and I've\nproposed a suggestion for both.\n\n> > As for handling multiple repositories, the tool to do that could be as\n> > simple as a shell script which reads from ~/.config/git/repo-maintenance\n> > (or whatever) and runs the same command on all of the repos it finds\n> > there, possibly with a subcommand to add and remove repos.\n> \n> Sure, that is flexible.\n> \n> And it requires a ton of Git expertise to know what to put into those\n> scripts. And Git updates cannot deliver more value to those scripts.\n\nPerhaps I was unclear what I thought could be the design of this.  My\nproposal is something like the following:\n\n  git schedule-gc add [--period=TIME] [--fetch=REMOTE | --fetch-all] REPO\n  git schedule-gc remove REPO\n\nThe actual command invoked by the system scheduler would be something\nlike the following:\n\n  git schedule-gc run\n\nIt would work as I proposed under the hood, but it would be relatively\nstraightforward to use.\n\n> > I'm not opposed to seeing a tool that can schedule periodic maintenance\n> > jobs, perhaps in contrib, depending on whether other people think it\n> > should go.  However, I think running periodic jobs is best handled on\n> > Unix with cron or anacron and not a custom tool or a command in Git.\n> \n> Okay, here is a challenge for you: design this such that the Windows\n> experience does _not_ feel like a 3rd-class citizen. Go ahead. Yes, there\n> is a scheduler. Yep, it does not do cron-like things. Precisely: you have\n> to feed it an XML to make use of the \"advanced\" features. Yeah, I also\n> cannot remember what the semantics are regarding missed jobs due to\n> shutdown cycles. Nope, you cannot rely on the XML being an option, that\n> would require Windows 10. The list goes on.\n\nI will freely admit that I know next to nothing about Windows.  I have\nused it only incidentally, if at all, for at least two decades.  It is\nnot a platform I generally have an interest in developing for, although\nI try to make it work as well as possible when I am working on a project\nwhich supports it.\n\nIt is, in general, my assumption, based on its wide usage, that it is a\npowerful and robust operating system with many features, but I have\nlittle actual knowledge about how it functions or the exact features it\nprovides.\n\nI want a solution that builds on the existing Unix tools for Unix,\nbecause that is least surprising to users and it is how Unix tools are\nsupposed to work.  I think we can agree that Git was designed with the\nUnix philosophy in mind.\n\nI also want a solution that works on Windows.  Ideally that solution\nwould build on existing components that are part of Windows, because it\nreduces the maintenance burden on all of us.  But unfortunately, I know\nnext to nothing about how to build such a solution.\n\n> > I've dealt with systems that implemented periodic tasks without using\n> > the existing tools for doing that, and I've found that usually that's a\n> > mistake.  Despite seeming straightforward, there are a lot of tricky\n> > edge cases to deal with and it's easy to get wrong.\n> \n> But maybe you found one of those issues in Stolee's patches? If so, please\n> do contribute your experience there to point out those issues, so that\n> they can be addressed.\n\nOne of the benefits of using anacron on Unix is that it can skip running\ntasks when the user is on battery.  This is not anything we can portably\ndo across systems, nor is it something that Git should need to know\nabout.\n\n> > We also don't have to reimplement all the features in the system\n> > scheduler and can let expert users use a different tool of their choice\n> > instead if cron (or the Windows equivalent) is not to their liking.\n> \n> Do we really want to start relying on `cron`, when the major platform used\n> by the target audience (enterprise software engineers who deal with rather\n> larger repositories than git.git or linux.git) quite obviously _lacks_\n> support for that?\n\nUnix users will be unhappy with us if we use our own scheduling system\nwhen cron is available.  They will expect us to reimplement those\nfeatures and they will complain if we do not.  While I cannot name\nnames, there are a nontrivial number of large, enterprise monorepos that\nrun only on macOS and Linux.\n\nThat doesn't prevent us from building tooling that does the scheduling\non Windows if we can't use the system scheduler, but it would be nice to\ntry to present a relatively unified interface across the two platforms.\n-- \nbrian m. carlson: Houston, Texas, US\nOpenPGP: https://keybase.io/bk2204\n"},{"id":"398285","messageId":"20200520190820.GB163566@google.com","threadId":"53164","inReplyTo":"cd3facd1e03923204bd2259ff0635a5bbf68d700.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/15] run-job: implement commit-graph job","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2020-05-20T19:08:20Z","receivedAt":"2020-05-20T19:08:28Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Thanks for this series, and sorry for the delayed (and partial) review.\nI hope comments here are still useful. I don't have any comments on the\nlarger design that other people haven't already made, but I do have a\nfew small questions and suggestions, inline below.\n\n\nOn 2020.04.03 20:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> The first job to implement in the 'git run-job' command is the\n> 'commit-graph' job. It is based on the sequence of events in the\n> 'commit-graph' job in Scalar [1]. This sequence is as follows:\n> \n> 1. git commit-graph write --reachable --split\n> 2. git commit-graph verify --shallow\n> 3. If the verify succeeds, stop.\n> 4. Delete the commit-graph-chain file.\n> 5. git commit-graph write --reachable --split\n> \n> By writing an incremental commit-graph file using the \"--split\"\n> option we minimize the disruption from this operation. The default\n> behavior is to merge layers until the new \"top\" layer is less than\n> half the size of the layer below. This provides quick writes \"most\"\n> of the time, with the longer writes following a power law\n> distribution.\n> \n> Most importantly, concurrent Git processes only look at the\n> commit-graph-chain file for a very short amount of time, so they\n> will verly likely not be holding a handle to the file when we try\n> to replace it. (This only matters on Windows.)\n> \n> If a concurrent process reads the old commit-graph-chain file, but\n> our job expires some of the .graph files before they can be read,\n> then those processes will see a warning message (but not fail).\n> This could be avoided by a future update to use the --expire-time\n> argument when writing the commit-graph.\n> \n> By using 'git commit-graph verify --shallow' we can ensure that\n> the file we just wrote is valid. This is an extra safety precaution\n> that is faster than our 'write' subcommand. In the rare situation\n> that the newest layer of the commit-graph is corrupt, we can \"fix\"\n> the corruption by deleting the commit-graph-chain file and rewrite\n> the full commit-graph as a new one-layer commit graph. This does\n> not completely prevent _that_ file from being corrupt, but it does\n> recompute the commit-graph by parsing commits from the object\n> database. In our use of this step in Scalar and VFS for Git, we\n> have only seen this issue arise because our microsoft/git fork\n> reverted 43d3561 (\"commit-graph write: don't die if the existing\n> graph is corrupt\" 2019-03-25) for a while to keep commit-graph\n> writes very fast. We dropped the revert when updating to v2.23.0.\n> The verify still has potential for catching corrupt data across\n> the layer boundary: if the new file has commit X with parent Y\n> in an old file but the commit ID for Y in the old file had a\n> bitswap, then we will notice that in the 'verify' command.\n> \n> [1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/CommitGraphStep.cs\n> \n> RFC QUESTIONS:\n> \n> 1. Are links like [1] helpful?\n\nYes, I appreciate being able to reference these. However, given that\nthese will show up in commit logs for ~forever, and ongoing work might\nhappen on Scalar, perhaps the links should be pinned to a specific\nrevision?\n\n\n> \n> 2. Can anyone think of a way to test the rewrite fallback?\n>    It requires corrupting the latest file between two steps of\n>    this one command, so that is a tricky spot to inject a fault.\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  Documentation/git-run-job.txt | 21 ++++++++++--\n>  builtin/run-job.c             | 60 ++++++++++++++++++++++++++++++++++-\n>  commit-graph.c                |  2 +-\n>  commit-graph.h                |  1 +\n>  t/t7900-run-job.sh            | 32 +++++++++++++++++++\n>  5 files changed, 112 insertions(+), 4 deletions(-)\n> \n> diff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\n> index 0627b3ed259..8bf2762d650 100644\n> --- a/Documentation/git-run-job.txt\n> +++ b/Documentation/git-run-job.txt\n> @@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git run-job <job-name>'\n> +'git run-job commit-graph'\n>  \n>  \n>  DESCRIPTION\n> @@ -28,7 +28,24 @@ BEHAVIOR MAY BE ALTERED IN THE FUTURE.\n>  JOBS\n>  ----\n>  \n> -TBD\n> +'commit-graph'::\n> +\n> +The `commit-graph` job updates the `commit-graph` files incrementally,\n> +then verifies that the written data is correct. If the new layer has an\n> +issue, then the chain file is removed and the `commit-graph` is\n> +rewritten from scratch.\n> ++\n> +The verification only checks the top layer of the `commit-graph` chain.\n> +If the incremental write merged the new commits with at least one\n> +existing layer, then there is potential for on-disk corruption being\n> +carried forward into the new file. This will be noticed and the new\n> +commit-graph file will be clean as Git reparses the commit data from\n> +the object database.\n> ++\n> +The incremental write is safe to run alongside concurrent Git processes\n> +since it will not expire `.graph` files that were in the previous\n> +`commit-graph-chain` file. They will be deleted by a later run based on\n> +the expiration delay.\n>  \n>  \n>  GIT\n> diff --git a/builtin/run-job.c b/builtin/run-job.c\n> index 2c78d053aa4..dd7709952d3 100644\n> --- a/builtin/run-job.c\n> +++ b/builtin/run-job.c\n> @@ -1,12 +1,65 @@\n>  #include \"builtin.h\"\n>  #include \"config.h\"\n> +#include \"commit-graph.h\"\n> +#include \"object-store.h\"\n>  #include \"parse-options.h\"\n> +#include \"repository.h\"\n> +#include \"run-command.h\"\n>  \n>  static char const * const builtin_run_job_usage[] = {\n> -\tN_(\"git run-job\"),\n> +\tN_(\"git run-job commit-graph\"),\n>  \tNULL\n>  };\n>  \n> +static int run_write_commit_graph(void)\n> +{\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\n> +\targv_array_pushl(&cmd, \"commit-graph\", \"write\",\n> +\t\t\t \"--split\", \"--reachable\", \"--no-progress\",\n> +\t\t\t NULL);\n> +\n> +\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +}\n> +\n> +static int run_verify_commit_graph(void)\n> +{\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\n> +\targv_array_pushl(&cmd, \"commit-graph\", \"verify\",\n> +\t\t\t \"--shallow\", \"--no-progress\", NULL);\n> +\n> +\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +}\n> +\n> +static int run_commit_graph_job(void)\n> +{\n> +\tchar *chain_path;\n> +\n> +\tif (run_write_commit_graph()) {\n> +\t\terror(_(\"failed to write commit-graph\"));\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tif (!run_verify_commit_graph())\n> +\t\treturn 0;\n> +\n> +\twarning(_(\"commit-graph verify caught error, rewriting\"));\n> +\n> +\tchain_path = get_chain_filename(the_repository->objects->odb);\n\nShould we avoid new uses of `the_repository` and take a repo pointer\nargument here instead?\n\n\n> +\tif (unlink(chain_path)) {\n> +\t\tUNLEAK(chain_path);\n> +\t\tdie(_(\"failed to remove commit-graph at %s\"), chain_path);\n> +\t}\n> +\tfree(chain_path);\n> +\n> +\tif (!run_write_commit_graph())\n> +\t\treturn 0;\n\nIs there a reason why we don't verify the second write attempt?\n\n\n> +\n> +\terror(_(\"failed to rewrite commit-graph\"));\n> +\treturn 1;\n> +}\n> +\n>  int cmd_run_job(int argc, const char **argv, const char *prefix)\n>  {\n>  \tstatic struct option builtin_run_job_options[] = {\n> @@ -23,6 +76,11 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n>  \t\t\t     builtin_run_job_usage,\n>  \t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n>  \n> +\tif (argc > 0) {\n> +\t\tif (!strcmp(argv[0], \"commit-graph\"))\n> +\t\t\treturn run_commit_graph_job();\n> +\t}\n> +\n>  \tusage_with_options(builtin_run_job_usage,\n>  \t\t\t   builtin_run_job_options);\n>  }\n> diff --git a/commit-graph.c b/commit-graph.c\n> index f013a84e294..6867f92d04a 100644\n> --- a/commit-graph.c\n> +++ b/commit-graph.c\n> @@ -56,7 +56,7 @@ static char *get_split_graph_filename(struct object_directory *odb,\n>  \t\t       oid_hex);\n>  }\n>  \n> -static char *get_chain_filename(struct object_directory *odb)\n> +char *get_chain_filename(struct object_directory *odb)\n>  {\n>  \treturn xstrfmt(\"%s/info/commit-graphs/commit-graph-chain\", odb->path);\n>  }\n> diff --git a/commit-graph.h b/commit-graph.h\n> index e87a6f63600..8b7bd5a6cb1 100644\n> --- a/commit-graph.h\n> +++ b/commit-graph.h\n> @@ -13,6 +13,7 @@\n>  struct commit;\n>  \n>  char *get_commit_graph_filename(struct object_directory *odb);\n> +char *get_chain_filename(struct object_directory *odb);\n>  int open_commit_graph(const char *graph_file, int *fd, struct stat *st);\n>  \n>  /*\n> diff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\n> index 1eac80b7ed3..18b9bd26b3a 100755\n> --- a/t/t7900-run-job.sh\n> +++ b/t/t7900-run-job.sh\n> @@ -5,6 +5,8 @@ test_description='git run-job\n>  Testing the background jobs, in the foreground\n>  '\n>  \n> +GIT_TEST_COMMIT_GRAPH=0\n> +\n>  . ./test-lib.sh\n>  \n>  test_expect_success 'help text' '\n> @@ -12,4 +14,34 @@ test_expect_success 'help text' '\n>  \ttest_i18ngrep \"usage: git run-job\" err\n>  '\n>  \n> +test_expect_success 'commit-graph job' '\n> +\tgit init server &&\n> +\t(\n> +\t\tcd server &&\n> +\t\tchain=.git/objects/info/commit-graphs/commit-graph-chain &&\n> +\t\tgit checkout -b master &&\n> +\t\tfor i in $(test_seq 1 15)\n> +\t\tdo\n> +\t\t\ttest_commit $i || return 1\n> +\t\tdone &&\n> +\t\tgit run-job commit-graph 2>../err &&\n> +\t\ttest_must_be_empty ../err &&\n> +\t\ttest_line_count = 1 $chain &&\n> +\t\tfor i in $(test_seq 16 19)\n> +\t\tdo\n> +\t\t\ttest_commit $i || return 1\n> +\t\tdone &&\n> +\t\tgit run-job commit-graph 2>../err &&\n> +\t\ttest_must_be_empty ../err &&\n> +\t\ttest_line_count = 2 $chain &&\n> +\t\tfor i in $(test_seq 20 23)\n> +\t\tdo\n> +\t\t\ttest_commit $i || return 1\n> +\t\tdone &&\n> +\t\tgit run-job commit-graph 2>../err &&\n> +\t\ttest_must_be_empty ../err &&\n> +\t\ttest_line_count = 1 $chain\n> +\t)\n> +'\n> +\n>  test_done\n> -- \n> gitgitgadget\n> \n"},{"id":"398286","messageId":"20200520190852.GC163566@google.com","threadId":"53164","inReplyTo":"77b1da5d3063a2404cd750adfe3bb8be9b6c497d.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/15] run-job: implement fetch job","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2020-05-20T19:08:52Z","receivedAt":"2020-05-20T19:09:01Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2020.04.03 20:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> When working with very large repositories, an incremental 'git fetch'\n> command can download a large amount of data. If there are many other\n> users pushing to a common repo, then this data can rival the initial\n> pack-file size of a 'git clone' of a medium-size repo.\n> \n> Users may want to keep the data on their local repos as close as\n> possible to the data on the remote repos by fetching periodically in\n> the background. This can break up a large daily fetch into several\n> smaller hourly fetches.\n> \n> However, if we simply ran 'git fetch <remote>' in the background,\n> then the user running a foregroudn 'git fetch <remote>' would lose\n> some important feedback when a new branch appears or an existing\n> branch updates. This is especially true if a remote branch is\n> force-updated and this isn't noticed by the user because it occurred\n> in the background. Further, the functionality of 'git push\n> --force-with-lease' becomes suspect.\n\nIt might be useful here to clarify the interaction between background\nfetching & the expectations around --force-with-lease.\n\n\n> When running 'git fetch <remote> <options>' in the background, use\n> the following options for careful updating:\n> \n> 1. --no-tags prevents getting a new tag when a user wants to see\n>    the new tags appear in their foreground fetches.\n> \n> 2. --refmap= removes the configured refspec which usually updates\n>    refs/remotes/<remote>/* with the refs advertised by the remote.\n> \n> 3. By adding a new refspec \"+refs/heads/*:refs/hidden/<remote>/*\"\n>    we can ensure that we actually load the new values somewhere in\n>    our refspace while not updating refs/heads or refs/remotes. By\n>    storing these refs here, the commit-graph job will update the\n>    commit-graph with the commits from these hidden refs.\n> \n> 4. --prune will delete the refs/hidden/<remote> refs that no\n>    longer appear on the remote.\n> \n> We've been using this step as a critical background job in Scalar\n> [1] (and VFS for Git). This solved a pain point that was showing up\n> in user reports: fetching was a pain! Users do not like waiting to\n> download the data that was created while they were away from their\n> machines. After implementing background fetch, the foreground fetch\n> commands sped up significantly because they mostly just update refs\n> and download a small amount of new data. The effect is especially\n> dramatic when paried with --no-show-forced-udpates (through\n> fetch.showForcedUpdates=false).\n> \n> [1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/FetchStep.cs\n> \n> RFC QUESTIONS:\n> \n> 1. One downside of the refs/hidden pattern is that 'git log' will\n>    decorate commits with twice as many refs if they appear at a\n>    remote ref (<remote>/<ref> _and_ refs/hidden/<remote>/<ref>). Is\n>    there an easy way to exclude a refspace from decorations? Should\n>    we make refs/hidden/* a \"special\" refspace that is excluded from\n>    decorations?\n> \n> 2. This feature is designed for a desktop machine or equivalent\n>    that has a permanent wired network connection, and the machine\n>    stays on while the user is not present. For things like laptops\n>    with inconsistent WiFi connections (that may be metered) the\n>    feature can use the less stable connection more than the user\n>    wants. Of course, this feature is opt-in for Git, but in Scalar\n>    we have a \"scalar pause\" command [2] that pauses all maintenance\n>    for some amount of time. We should consider a similar mechanism\n>    for Git, but for the point of this series the user needs to set\n>    up the \"background\" part of these jobs manually.\n> \n> [2] https://github.com/microsoft/scalar/blob/master/Scalar/CommandLine/PauseVerb.cs\n> [3] https://github.com/microsoft/scalar/blob/master/docs/advanced.md#controlling-background-maintenance\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  Documentation/git-run-job.txt | 13 ++++-\n>  builtin/run-job.c             | 89 ++++++++++++++++++++++++++++++++++-\n>  t/t7900-run-job.sh            | 22 +++++++++\n>  3 files changed, 122 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\n> index 8bf2762d650..eb92e891915 100644\n> --- a/Documentation/git-run-job.txt\n> +++ b/Documentation/git-run-job.txt\n> @@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git run-job commit-graph'\n> +'git run-job (commit-graph|fetch)'\n>  \n>  \n>  DESCRIPTION\n> @@ -47,6 +47,17 @@ since it will not expire `.graph` files that were in the previous\n>  `commit-graph-chain` file. They will be deleted by a later run based on\n>  the expiration delay.\n>  \n> +'fetch'::\n> +\n> +The `fetch` job updates the object directory with the latest objects\n> +from all registered remotes. For each remote, a `git fetch` command is\n> +run. The refmap is custom to avoid updating local or remote branches\n> +(those in `refs/heads` or `refs/remotes`). Instead, the remote refs are\n> +stored in `refs/hidden/<remote>/`. Also, no tags are updated.\n> ++\n> +This means that foreground fetches are still required to update the\n> +remote refs, but the users is notified when the branches and tags are\n> +updated on the remote.\n>  \n>  GIT\n>  ---\n> diff --git a/builtin/run-job.c b/builtin/run-job.c\n> index dd7709952d3..e59056b2918 100644\n> --- a/builtin/run-job.c\n> +++ b/builtin/run-job.c\n> @@ -7,7 +7,7 @@\n>  #include \"run-command.h\"\n>  \n>  static char const * const builtin_run_job_usage[] = {\n> -\tN_(\"git run-job commit-graph\"),\n> +\tN_(\"git run-job (commit-graph|fetch)\"),\n>  \tNULL\n>  };\n>  \n> @@ -60,6 +60,91 @@ static int run_commit_graph_job(void)\n>  \treturn 1;\n>  }\n>  \n> +static int fetch_remote(const char *remote)\n> +{\n> +\tint result;\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\tstruct strbuf refmap = STRBUF_INIT;\n> +\n> +\targv_array_pushl(&cmd, \"fetch\", remote, \"--quiet\", \"--prune\",\n> +\t\t\t \"--no-tags\", \"--refmap=\", NULL);\n> +\n> +\tstrbuf_addf(&refmap, \"+refs/heads/*:refs/hidden/%s/*\", remote);\n> +\targv_array_push(&cmd, refmap.buf);\n> +\n> +\tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +\n> +\tstrbuf_release(&refmap);\n> +\treturn result;\n> +}\n> +\n> +static int fill_remotes(struct string_list *remotes)\n> +{\n> +\tint result = 0;\n> +\tFILE *proc_out;\n> +\tstruct strbuf line = STRBUF_INIT;\n> +\tstruct child_process *remote_proc = xmalloc(sizeof(*remote_proc));\n\nShouldn't remote_proc just go on the stack here rather than getting\nmalloced?\n\n> +\n> +\tchild_process_init(remote_proc);\n> +\n> +\targv_array_pushl(&remote_proc->args, \"git\", \"remote\", NULL);\n> +\n> +\tremote_proc->out = -1;\n> +\n> +\tif (start_command(remote_proc)) {\n> +\t\terror(_(\"failed to start 'git remote' process\"));\n> +\t\tresult = 1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tproc_out = xfdopen(remote_proc->out, \"r\");\n> +\n> +\t/* if there is no line, leave the value as given */\n> +\twhile (!strbuf_getline(&line, proc_out))\n> +\t\tstring_list_append(remotes, line.buf);\n> +\n> +\tstrbuf_release(&line);\n> +\n> +\tfclose(proc_out);\n> +\n> +\tif (finish_command(remote_proc)) {\n> +\t\terror(_(\"failed to finish 'git remote' process\"));\n> +\t\tresult = 1;\n> +\t}\n> +\n> +cleanup:\n> +\tfree(remote_proc);\n> +\treturn result;\n> +}\n> +\n> +static int run_fetch_job(void)\n> +{\n> +\tint result = 0;\n> +\tstruct string_list_item *item;\n> +\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n> +\n> +\tif (fill_remotes(&remotes)) {\n> +\t\terror(_(\"failed to fill remotes\"));\n> +\t\tresult = 1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\t/*\n> +\t * Do not modify the result based on the success of the 'fetch'\n> +\t * operation, as a loss of network could cause 'fetch' to fail\n> +\t * quickly. We do not want that to stop the rest of our\n> +\t * background operations.\n> +\t */\n> +\tfor (item = remotes.items;\n> +\t     item && item < remotes.items + remotes.nr;\n> +\t     item++)\n> +\t\tfetch_remote(item->string);\n> +\n> +cleanup:\n> +\tstring_list_clear(&remotes, 0);\n> +\treturn result;\n> +}\n> +\n>  int cmd_run_job(int argc, const char **argv, const char *prefix)\n>  {\n>  \tstatic struct option builtin_run_job_options[] = {\n> @@ -79,6 +164,8 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n>  \tif (argc > 0) {\n>  \t\tif (!strcmp(argv[0], \"commit-graph\"))\n>  \t\t\treturn run_commit_graph_job();\n> +\t\tif (!strcmp(argv[0], \"fetch\"))\n> +\t\t\treturn run_fetch_job();\n>  \t}\n>  \n>  \tusage_with_options(builtin_run_job_usage,\n> diff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\n> index 18b9bd26b3a..d3faeba135b 100755\n> --- a/t/t7900-run-job.sh\n> +++ b/t/t7900-run-job.sh\n> @@ -44,4 +44,26 @@ test_expect_success 'commit-graph job' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'fetch job' '\n> +\tgit clone \"file://$(pwd)/server\" client &&\n> +\n> +\t# Before fetching, build a client commit-graph\n> +\tgit -C client run-job commit-graph &&\n> +\tchain=client/.git/objects/info/commit-graphs/commit-graph-chain &&\n> +\ttest_line_count = 1 $chain &&\n> +\n> +\tgit -C client branch -v --remotes >before-refs &&\n> +\ttest_commit -C server 24 &&\n> +\n> +\tgit -C client run-job fetch &&\n> +\tgit -C client branch -v --remotes >after-refs &&\n> +\ttest_cmp before-refs after-refs &&\n> +\ttest_cmp server/.git/refs/heads/master \\\n> +\t\t client/.git/refs/hidden/origin/master &&\n> +\n> +\t# the hidden ref should trigger a new layer in the commit-graph\n> +\tgit -C client run-job commit-graph &&\n> +\ttest_line_count = 2 $chain\n> +'\n> +\n>  test_done\n> -- \n> gitgitgadget\n> \n"},{"id":"398701","messageId":"20200527221730.GA65111@google.com","threadId":"53164","inReplyTo":"5277398f189dd6e1456b0afdd6d85dbac7d98d07.1585946894.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/15] run-job: implement pack-files job","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2020-05-27T22:17:30Z","receivedAt":"2020-05-27T22:17:41Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2020.04.03 20:48, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> The previous change cleaned up loose objects using the\n> 'loose-objects' that can be run safely in the background. Add a\n> similar job that performs similar cleanups for pack-files.\n> \n> One issue with running 'git repack' is that it is designed to\n> repack all pack-files into a single pack-file. While this is the\n> most space-efficient way to store object data, it is not time or\n> memory efficient. This becomes extremely important if the repo is\n> so large that a user struggles to store two copies of the pack on\n> their disk.\n> \n> Instead, perform an \"incremental\" repack by collecting a few small\n> pack-files into a new pack-file. The multi-pack-index facilitates\n> this process ever since 'git multi-pack-index expire' was added in\n> 19575c7 (multi-pack-index: implement 'expire' subcommand,\n> 2019-06-10) and 'git multi-pack-index repack' was added in ce1e4a1\n> (midx: implement midx_repack(), 2019-06-10).\n> \n> The 'pack-files' job runs the following steps:\n> \n> 1. 'git multi-pack-index write' creates a multi-pack-index file if\n>    one did not exist, and otherwise will update the multi-pack-index\n>    with any new pack-files that appeared since the last write. This\n>    is particularly relevant with the background fetch job.\n> \n>    When the multi-pack-index sees two copies of the same object, it\n>    stores the offset data into the newer pack-file. This means that\n>    some old pack-files could become \"unreferenced\" which I will use\n>    to mean \"a pack-file that is in the pack-file list of the\n>    multi-pack-index but none of the objects in the multi-pack-index\n>    reference a location inside that pack-file.\"\n> \n> 2. 'git multi-pack-index expire' deletes any unreferenced pack-files\n>    and updaes the multi-pack-index to drop those pack-files from the\n\nTypo: updaes -> updates\n\n\n>    list. This is safe to do as concurrent Git processes will see the\n>    multi-pack-index and not open those packs when looking for object\n>    contents. (Similar to the 'loose-objects' job, there are some Git\n\nIs it still safe for concurrent processes if the repo did not have a\nmulti-pack-index when the first process started?\n\n\n>    commands that open pack-files regardless of the multi-pack-index,\n>    but they are rarely used. Further, a user that self-selects to\n>    use background operations would likely refrain from using those\n>    commands.)\n> \n> 3. 'git multi-pack-index repack --bacth-size=<size>' collects a set\n\nTypo: bacth-size -> batch-size\n\n\n>    of pack-files that are listed in the multi-pack-index and creates\n>    a new pack-file containing the objects whose offsets are listed\n>    by the multi-pack-index to be in those objects. The set of pack-\n>    files is selected greedily by sorting the pack-files by modified\n>    time and adding a pack-file to the set if its \"expected size\" is\n>    smaller than the batch size until the total expected size of the\n>    selected pack-files is at least the batch size. The \"expected\n>    size\" is calculated by taking the size of the pack-file divided\n>    by the number of objects in the pack-file and multiplied by the\n>    number of objects from the multi-pack-index with offset in that\n>    pack-file. The expected size approximats how much data from that\n\nTypo: approximats -> approximates\n\n\n>    pack-file will contribute to the resulting pack-file size. The\n>    intention is that the resulting pack-file will be close in size\n>    to the provided batch size.\n> \n>    The next run of the pack-files job will delete these repacked\n>    pack-files during the 'expire' step.\n> \n>    In this version, the batch size is set to \"0\" which ignores the\n>    size restrictions when selecting the pack-files. It instead\n>    selects all pack-files and repacks all packed objects into a\n>    single pack-file. This will be updated in the next change, but\n>    it requires doing some calculations that are better isolated to\n>    a separate change.\n> \n> Each of the above steps update the multi-pack-index file. After\n> each step, we verify the new multi-pack-index. If the new\n> multi-pack-index is corrupt, then delete the multi-pack-index,\n> rewrite it from scratch, and stop doing the later steps of the\n> job. This is intended to be an extra-safe check without leaving\n> a repo with many pack-files without a multi-pack-index.\n> \n> These steps are based on a similar background maintenance step in\n> Scalar (and VFS for Git) [1]. This was incredibly effective for\n> users of the Windows OS repository. After using the same VFS for Git\n> repository for over a year, some users had _thousands_ of pack-files\n> that combined to up to 250 GB of data. We noticed a few users were\n> running into the open file descriptor limits (due in part to a bug\n> in the multi-pack-index fixed by af96fe3392 (midx: add packs to\n> packed_git linked list, 2019-04-29).\n> \n> These pack-files were mostly small since they contained the commits\n> and trees that were pushed to the origin in a given hour. The GVFS\n> protocol includes a \"prefetch\" step that asks for pre-computed pack-\n> files containing commits and trees by timestamp. These pack-files\n> were grouped into \"daily\" pack-files once a day for up to 30 days.\n> If a user did not request prefetch packs for over 30 days, then they\n> would get the entire history of commits and trees in a new, large\n> pack-file. This led to a large number of pack-files that had poor\n> delta compression.\n> \n> By running this pack-file maintenance step once per day, these repos\n> with thousands of packs spanning 200+ GB dropped to dozens of pack-\n> files spanning 30-50 GB. This was done all without removing objects\n> from the system and using a constant batch size of two gigabytes.\n> Once the work was done to reduce the pack-files to small sizes, the\n> batch size of two gigabytes means that not every run triggers a\n> repack operation, so the following run will not expire a pack-file.\n> This has kept these repos in a \"clean\" state.\n> \n> [1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/PackfileMaintenanceStep.cs\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  Documentation/git-run-job.txt | 18 ++++++-\n>  builtin/run-job.c             | 90 ++++++++++++++++++++++++++++++++++-\n>  midx.c                        |  2 +-\n>  midx.h                        |  1 +\n>  t/t7900-run-job.sh            | 39 +++++++++++++++\n>  5 files changed, 147 insertions(+), 3 deletions(-)\n> \n> diff --git a/Documentation/git-run-job.txt b/Documentation/git-run-job.txt\n> index 43ca1160b5a..108ed25b8bd 100644\n> --- a/Documentation/git-run-job.txt\n> +++ b/Documentation/git-run-job.txt\n> @@ -9,7 +9,7 @@ git-run-job - Run a maintenance job. Intended for background operation.\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git run-job (commit-graph|fetch|loose-objects)'\n> +'git run-job (commit-graph|fetch|loose-objects|pack-files)'\n>  \n>  \n>  DESCRIPTION\n> @@ -71,6 +71,22 @@ a batch of loose objects. The batch size is limited to 50 thousand\n>  objects to prevent the job from taking too long on a repository with\n>  many loose objects.\n>  \n> +'pack-files'::\n> +\n> +The `pack-files` job incrementally repacks the object directory using\n> +the `multi-pack-index` feature. In order to prevent race conditions with\n> +concurrent Git commands, it follows a two-step process. First, it\n> +deletes any pack-files included in the `multi-pack-index` where none of\n> +the objects in the `multi-pack-index` reference those pack-files; this\n> +only happens if all objects in the pack-file are also stored in a newer\n> +pack-file. Second, it selects a group of pack-files whose \"expected\n> +size\" is below the batch size until the group has total expected size at\n> +least the batch size; see the `--batch-size` option for the `repack`\n> +subcommand in linkgit:git-multi-pack-index[1]. The default batch-size is\n> +zero, which is a special case that attempts to repack all pack-files\n> +into a single pack-file.\n> +\n> +\n>  GIT\n>  ---\n>  Part of the linkgit:git[1] suite\n> diff --git a/builtin/run-job.c b/builtin/run-job.c\n> index cecf9058c51..d3543f7ccb9 100644\n> --- a/builtin/run-job.c\n> +++ b/builtin/run-job.c\n> @@ -1,13 +1,14 @@\n>  #include \"builtin.h\"\n>  #include \"config.h\"\n>  #include \"commit-graph.h\"\n> +#include \"midx.h\"\n>  #include \"object-store.h\"\n>  #include \"parse-options.h\"\n>  #include \"repository.h\"\n>  #include \"run-command.h\"\n>  \n>  static char const * const builtin_run_job_usage[] = {\n> -\tN_(\"git run-job (commit-graph|fetch|loose-objects)\"),\n> +\tN_(\"git run-job (commit-graph|fetch|loose-objects|pack-files)\"),\n>  \tNULL\n>  };\n>  \n> @@ -238,6 +239,91 @@ static int run_loose_objects_job(void)\n>  \treturn prune_packed() || pack_loose();\n>  }\n>  \n> +static int multi_pack_index_write(void)\n> +{\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\targv_array_pushl(&cmd, \"multi-pack-index\", \"write\",\n> +\t\t\t \"--no-progress\", NULL);\n> +\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +}\n> +\n> +static int rewrite_multi_pack_index(void)\n> +{\n> +\tchar *midx_name = get_midx_filename(the_repository->objects->odb->path);\n> +\n> +\tunlink(midx_name);\n> +\tfree(midx_name);\n> +\n> +\tif (multi_pack_index_write()) {\n> +\t\terror(_(\"failed to rewrite multi-pack-index\"));\n> +\t\treturn 1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int multi_pack_index_verify(void)\n> +{\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\targv_array_pushl(&cmd, \"multi-pack-index\", \"verify\",\n> +\t\t\t \"--no-progress\", NULL);\n> +\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +}\n> +\n> +static int multi_pack_index_expire(void)\n> +{\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\targv_array_pushl(&cmd, \"multi-pack-index\", \"expire\",\n> +\t\t\t \"--no-progress\", NULL);\n> +\treturn run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +}\n> +\n> +static int multi_pack_index_repack(void)\n> +{\n> +\tint result;\n> +\tstruct argv_array cmd = ARGV_ARRAY_INIT;\n> +\targv_array_pushl(&cmd, \"multi-pack-index\", \"repack\",\n> +\t\t\t \"--no-progress\", \"--batch-size=0\", NULL);\n> +\tresult = run_command_v_opt(cmd.argv, RUN_GIT_CMD);\n> +\n> +\tif (result && multi_pack_index_verify()) {\n> +\t\twarning(_(\"multi-pack-index verify failed after repack\"));\n> +\t\tresult = rewrite_multi_pack_index();\n> +\t}\n> +\n> +\treturn result;\n> +}\n> +\n> +static int run_pack_files_job(void)\n> +{\n> +\tif (multi_pack_index_write()) {\n> +\t\terror(_(\"failed to write multi-pack-index\"));\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tif (multi_pack_index_verify()) {\n> +\t\twarning(_(\"multi-pack-index verify failed after initial write\"));\n> +\t\treturn rewrite_multi_pack_index();\n> +\t}\n> +\n> +\tif (multi_pack_index_expire()) {\n> +\t\terror(_(\"multi-pack-index expire failed\"));\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tif (multi_pack_index_verify()) {\n> +\t\twarning(_(\"multi-pack-index verify failed after expire\"));\n> +\t\treturn rewrite_multi_pack_index();\n> +\t}\n> +\n> +\tif (multi_pack_index_repack()) {\n> +\t\terror(_(\"multi-pack-index repack failed\"));\n> +\t\treturn 1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n>  int cmd_run_job(int argc, const char **argv, const char *prefix)\n>  {\n>  \tstatic struct option builtin_run_job_options[] = {\n> @@ -261,6 +347,8 @@ int cmd_run_job(int argc, const char **argv, const char *prefix)\n>  \t\t\treturn run_fetch_job();\n>  \t\tif (!strcmp(argv[0], \"loose-objects\"))\n>  \t\t\treturn run_loose_objects_job();\n> +\t\tif (!strcmp(argv[0], \"pack-files\"))\n> +\t\t\treturn run_pack_files_job();\n>  \t}\n>  \n>  \tusage_with_options(builtin_run_job_usage,\n> diff --git a/midx.c b/midx.c\n> index 1527e464a7b..0f0d0a38812 100644\n> --- a/midx.c\n> +++ b/midx.c\n> @@ -36,7 +36,7 @@\n>  \n>  #define PACK_EXPIRED UINT_MAX\n>  \n> -static char *get_midx_filename(const char *object_dir)\n> +char *get_midx_filename(const char *object_dir)\n>  {\n>  \treturn xstrfmt(\"%s/pack/multi-pack-index\", object_dir);\n>  }\n> diff --git a/midx.h b/midx.h\n> index e6fa356b5ca..cf2c09dffc2 100644\n> --- a/midx.h\n> +++ b/midx.h\n> @@ -39,6 +39,7 @@ struct multi_pack_index {\n>  \n>  #define MIDX_PROGRESS     (1 << 0)\n>  \n> +char *get_midx_filename(const char *object_dir);\n>  struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local);\n>  int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id);\n>  int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result);\n> diff --git a/t/t7900-run-job.sh b/t/t7900-run-job.sh\n> index 41da083257b..416ba04989d 100755\n> --- a/t/t7900-run-job.sh\n> +++ b/t/t7900-run-job.sh\n> @@ -6,6 +6,7 @@ Testing the background jobs, in the foreground\n>  '\n>  \n>  GIT_TEST_COMMIT_GRAPH=0\n> +GIT_TEST_MULTI_PACK_INDEX=0\n>  \n>  . ./test-lib.sh\n>  \n> @@ -93,4 +94,42 @@ test_expect_success 'loose-objects job' '\n>  \ttest_cmp packs-between packs-after\n>  '\n>  \n> +test_expect_success 'pack-files job' '\n> +\tpackDir=.git/objects/pack &&\n> +\n> +\t# Create three disjoint pack-files with size BIG, small, small.\n> +\n> +\techo HEAD~2 | git -C client pack-objects --revs $packDir/test-1 &&\n> +\n> +\ttest_tick &&\n> +\tgit -C client pack-objects --revs $packDir/test-2 <<-\\EOF &&\n> +\tHEAD~1\n> +\t^HEAD~2\n> +\tEOF\n> +\n> +\ttest_tick &&\n> +\tgit -C client pack-objects --revs $packDir/test-3 <<-\\EOF &&\n> +\tHEAD\n> +\t^HEAD~1\n> +\tEOF\n> +\n> +\trm -f client/$packDir/pack-* &&\n> +\trm -f client/$packDir/loose-* &&\n> +\n> +\tls client/$packDir/*.pack >packs-before &&\n> +\ttest_line_count = 3 packs-before &&\n> +\n> +\t# the job repacks the two into a new pack, but does not\n> +\t# delete the old ones.\n> +\tgit -C client run-job pack-files &&\n> +\tls client/$packDir/*.pack >packs-between &&\n> +\ttest_line_count = 4 packs-between &&\n> +\n> +\t# the job deletes the two old packs, and does not write\n> +\t# a new one because only one pack remains.\n> +\tgit -C client run-job pack-files &&\n> +\tls client/.git/objects/pack/*.pack >packs-after &&\n> +\ttest_line_count = 1 packs-after\n> +'\n> +\n>  test_done\n> -- \n> gitgitgadget\n> \n"},{"id":"398703","messageId":"20200527223907.GB65111@google.com","threadId":"53164","inReplyTo":"20200408000149.GN6369@camp.crustytoothpaste.net","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2020-05-27T22:39:07Z","receivedAt":"2020-05-27T22:39:18Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"I'm late to the discussion, but I'd like to chime in here too.\n\n\nOn 2020.04.08 00:01, brian m. carlson wrote:\n> Hey,\n> \n> On 2020-04-07 at 22:23:43, Johannes Schindelin wrote:\n> > > If there are periodic tasks that should be done, even if only on large\n> > > repos, then let's have a git gc --periodic that does them.  I'm not sure\n> > > that fetch should be in that set, but nothing prevents users from doing\n> > > \"git fetch origin && git gc --periodic\".\n> > \n> > Hmm. Who says that maintenance tasks are essentially only `gc`? With\n> > _maaaaaybe_ a `fetch` thrown in?\n> \n> What I'm saying is that we have a tool to run maintenance tasks on the\n> repository.  If we need to perform additional maintenance tasks, let's\n> put them in the same place as the ones we have now.  I realize \"gc\" may\n> become a less accurate name, but oh, well.\n> \n> > > Let's make it as simple and straightforward as possible.\n> > \n> > I get the impression, however, that many reviewers here seem to favor the\n> > goal of making the _patches_ as simple and straightforward as possible,\n> > however, at the expense of the original goal. Like, totally sacrificing\n> > the ease of use in return for \"just use a shell script\" advice.\n> \n> I think we can have both.  They are not mutually exclusive, and I've\n> proposed a suggestion for both.\n> \n> > > As for handling multiple repositories, the tool to do that could be as\n> > > simple as a shell script which reads from ~/.config/git/repo-maintenance\n> > > (or whatever) and runs the same command on all of the repos it finds\n> > > there, possibly with a subcommand to add and remove repos.\n> > \n> > Sure, that is flexible.\n> > \n> > And it requires a ton of Git expertise to know what to put into those\n> > scripts. And Git updates cannot deliver more value to those scripts.\n> \n> Perhaps I was unclear what I thought could be the design of this.  My\n> proposal is something like the following:\n> \n>   git schedule-gc add [--period=TIME] [--fetch=REMOTE | --fetch-all] REPO\n>   git schedule-gc remove REPO\n> \n> The actual command invoked by the system scheduler would be something\n> like the following:\n> \n>   git schedule-gc run\n> \n> It would work as I proposed under the hood, but it would be relatively\n> straightforward to use.\n\nRegardless of what happens with the job-runner, I would like to see a\ntop-level command that performs a single iteration of all the\nrecommended maintenance steps, with zero configuration required, on a\nsingle repo. This gives an entry point for users who want to manage\ntheir own maintenance schedule without running a background process.\n\n\n> > > I'm not opposed to seeing a tool that can schedule periodic maintenance\n> > > jobs, perhaps in contrib, depending on whether other people think it\n> > > should go.  However, I think running periodic jobs is best handled on\n> > > Unix with cron or anacron and not a custom tool or a command in Git.\n> > \n> > Okay, here is a challenge for you: design this such that the Windows\n> > experience does _not_ feel like a 3rd-class citizen. Go ahead. Yes, there\n> > is a scheduler. Yep, it does not do cron-like things. Precisely: you have\n> > to feed it an XML to make use of the \"advanced\" features. Yeah, I also\n> > cannot remember what the semantics are regarding missed jobs due to\n> > shutdown cycles. Nope, you cannot rely on the XML being an option, that\n> > would require Windows 10. The list goes on.\n> \n> I will freely admit that I know next to nothing about Windows.  I have\n> used it only incidentally, if at all, for at least two decades.  It is\n> not a platform I generally have an interest in developing for, although\n> I try to make it work as well as possible when I am working on a project\n> which supports it.\n> \n> It is, in general, my assumption, based on its wide usage, that it is a\n> powerful and robust operating system with many features, but I have\n> little actual knowledge about how it functions or the exact features it\n> provides.\n> \n> I want a solution that builds on the existing Unix tools for Unix,\n> because that is least surprising to users and it is how Unix tools are\n> supposed to work.  I think we can agree that Git was designed with the\n> Unix philosophy in mind.\n> \n> I also want a solution that works on Windows.  Ideally that solution\n> would build on existing components that are part of Windows, because it\n> reduces the maintenance burden on all of us.  But unfortunately, I know\n> next to nothing about how to build such a solution.\n> \n> > > I've dealt with systems that implemented periodic tasks without using\n> > > the existing tools for doing that, and I've found that usually that's a\n> > > mistake.  Despite seeming straightforward, there are a lot of tricky\n> > > edge cases to deal with and it's easy to get wrong.\n> > \n> > But maybe you found one of those issues in Stolee's patches? If so, please\n> > do contribute your experience there to point out those issues, so that\n> > they can be addressed.\n> \n> One of the benefits of using anacron on Unix is that it can skip running\n> tasks when the user is on battery.  This is not anything we can portably\n> do across systems, nor is it something that Git should need to know\n> about.\n> \n> > > We also don't have to reimplement all the features in the system\n> > > scheduler and can let expert users use a different tool of their choice\n> > > instead if cron (or the Windows equivalent) is not to their liking.\n> > \n> > Do we really want to start relying on `cron`, when the major platform used\n> > by the target audience (enterprise software engineers who deal with rather\n> > larger repositories than git.git or linux.git) quite obviously _lacks_\n> > support for that?\n> \n> Unix users will be unhappy with us if we use our own scheduling system\n> when cron is available.  They will expect us to reimplement those\n> features and they will complain if we do not.  While I cannot name\n> names, there are a nontrivial number of large, enterprise monorepos that\n> run only on macOS and Linux.\n\nSpeaking purely as a user, I agree with this point. This is why I want a\nsingle-iteration top-level maintenance command.\n\nOnce we have that, we can provide recommended configs for existing\nscheduling solutions (cron, launchd, systemd, etc.) in contrib/. If the\nWindows scheduler is cumbersome enough that users don't want to use it,\nthen I think it's perfectly reasonable to provide our own limited\njob-runner in contrib/ as well, so long as we don't require people to\nuse it.\n\n> That doesn't prevent us from building tooling that does the scheduling\n> on Windows if we can't use the system scheduler, but it would be nice to\n> try to present a relatively unified interface across the two platforms.\n> -- \n> brian m. carlson: Houston, Texas, US\n> OpenPGP: https://keybase.io/bk2204\n\n\n"},{"id":"398713","messageId":"xmqqa71s6g1w.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"20200527223907.GB65111@google.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-05-28T00:47:55Z","receivedAt":"2020-05-28T00:48:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> Regardless of what happens with the job-runner, I would like to see a\n> top-level command that performs a single iteration of all the\n> recommended maintenance steps, with zero configuration required, on a\n> single repo. This gives an entry point for users who want to manage\n> their own maintenance schedule without running a background process.\n> ...\n>> Unix users will be unhappy with us if we use our own scheduling system\n>> when cron is available.  They will expect us to reimplement those\n>> features and they will complain if we do not.  While I cannot name\n>> names, there are a nontrivial number of large, enterprise monorepos that\n>> run only on macOS and Linux.\n>\n> Speaking purely as a user, I agree with this point. This is why I want a\n> single-iteration top-level maintenance command.\n\nYes, well said.  \n\nIt exactly is what \"git gc\" was meant to be.  To put it differently,\nif you asked any non-novice end-user if there is one single command\nthat s/he would use to keep a repository healthy, it is very likely\nthat the answer would be \"git gc\".\n\nAnd having such a single point of entry would be a good thing.\n\nThanks.\n\n\n"},{"id":"398737","messageId":"nycvar.QRO.7.76.6.2005272334560.56@tvgsbejvaqbjf.bet","threadId":"53164","inReplyTo":"xmqqa71s6g1w.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-05-27T21:52:48Z","receivedAt":"2020-05-28T14:27:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 27 May 2020, Junio C Hamano wrote:\n\n> Josh Steadmon <steadmon@google.com> writes:\n>\n> > Regardless of what happens with the job-runner, I would like to see a\n> > top-level command that performs a single iteration of all the\n> > recommended maintenance steps, with zero configuration required, on a\n> > single repo. This gives an entry point for users who want to manage\n> > their own maintenance schedule without running a background process.\n> > ...\n> >> Unix users will be unhappy with us if we use our own scheduling system\n> >> when cron is available.  They will expect us to reimplement those\n> >> features and they will complain if we do not.  While I cannot name\n> >> names, there are a nontrivial number of large, enterprise monorepos that\n> >> run only on macOS and Linux.\n> >\n> > Speaking purely as a user, I agree with this point. This is why I want a\n> > single-iteration top-level maintenance command.\n>\n> Yes, well said.\n>\n> It exactly is what \"git gc\" was meant to be.  To put it differently,\n> if you asked any non-novice end-user if there is one single command\n> that s/he would use to keep a repository healthy, it is very likely\n> that the answer would be \"git gc\".\n\nThe biggest problem with bringing up `git gc` in this context (and it is\nactually a quite big problem) is that \"gc\" stands for \"garbage\ncollection\", and these maintenance tasks are not even close to being about\ncollecting garbage.\n\nSo while `git gc` looks like a good candidate on its technical merits, the\nusability/discoverability point of view paints a very different picture.\n\nWhat Scalar does is conceptually a _very_ different thing from letting\n`git gc --auto` (which I guess is what you _actually_ meant, as I have yet\nto meet even a single Git user outside of this mailing list who knows `git\ngc`, let alone who runs it manually) determine whether loose objects\nshould be cleaned up and packs should be consolidated.\n\nLike, pre-fetching a daily pack in preparation for the user fetching\nupdates. You could argue that this is, in a way, _accumulating_ \"garbage\".\n\nThe entire idea of those maintenance tasks is that they are _not_\ntriggered by the user, not even as a side effect (`git gc --auto` is very\nmuch a side effect, and on Windows, where it does not `daemonize()`\nbecause that concept does not translate well into the Win32 API, having it\nrun in the foreground is very much felt by the users).\n\nThose maintenance tasks should stay out of the users' way as much as\npossible.\n\n> And having such a single point of entry would be a good thing.\n\nI guess I would argue for the introduction of a new command, like `git\nmaintenance`, which could potentially trigger a `git gc --auto`, but is\nprimarily intended to run _outside_ of the users' work hours.\n\nOnce we have that, we can always figure out whether there is a convenient\nway to register this via `crontab` or Windows Task Scheduler, without\nasking the users to do all of these tedious steps manually. I, for one,\nhave to spend the extra time looking up what those positional numbers in\nthe `crontab` thing _mean_, _every_ _single_ _time_ I touch the `crontab`.\n_Every_ _single_ _time_.\n\nTherefore, I would like very much to have a `git maintenance --schedule`\n(or something like that), even if only on Windows, on the grounds alone\nthat it is even more tedious to work with the Windows Task Scheduler. But\nI would prefer to have that also in my Linux setup. The convenience for\nthe users (myself included) is just too compelling.\n\nCiao,\nDscho\n"},{"id":"398739","messageId":"xmqq1rn45d5n.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"nycvar.QRO.7.76.6.2005272334560.56@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-05-28T14:48:04Z","receivedAt":"2020-05-28T14:48:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> It exactly is what \"git gc\" was meant to be.  To put it differently,\n>> if you asked any non-novice end-user if there is one single command\n>> that s/he would use to keep a repository healthy, it is very likely\n>> that the answer would be \"git gc\".\n>\n> The biggest problem with bringing up `git gc` in this context (and it is\n> actually a quite big problem) is that \"gc\" stands for ...\n\nAh, no, I wasn't saying \"the single command has to be called 'gc'\".\nI do not care too much about the name, as it will be hidden scripted\naway.\n\nI was responding to Josh's \"We want a single point of entry for all\nthe housekeeping tasks\" with \"yes---it is not any controversial new\nidea---we have had such a single entry point for housekeeping tasks\nin 'gc' for a long time and we are receptive of having such a single\ncommand\".\n"},{"id":"398740","messageId":"20200528145018.GA58643@google.com","threadId":"53164","inReplyTo":"nycvar.QRO.7.76.6.2005272334560.56@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-05-28T14:50:18Z","receivedAt":"2020-05-28T14:50:24Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nJohannes Schindelin wrote:\n\n> So while `git gc` looks like a good candidate on its technical merits, the\n> usability/discoverability point of view paints a very different picture.\n\nHere is what I take away from that comment:\n\n    It was a mistake for \"git gc\" to take responsibility for operations\n    like \"git pack-refs\".  We should migrate to a new \"git maintenance\n    run\" command and make \"git gc\" print a signpost to help people find\n    the new name.\n\nThat seems like a reasonable direction to me, but as you're hinting,\nfrom a technical design perspective it doesn't change where e.g. midx\nmaintenance operations would go.  I would expect the routine\nmaintenance command, whatever it is called, to perform such\noperations.  I would not expect to have to choose between two subtly\ndifferent maintenance commands, or in other words to have to pay as a\nuser for an unrelated naming choice.\n\nI think it's worth discussing naming, but it's kind of a distraction\nfrom what Brian brought up.  The real question is, do we consider the\nexisting \"git gc\" infrastructure such a lost cause that we should\ntouch it as little as possible?\n\nI don't think that was Stolee's intent --- instead, I supsect that it\nwas just the discoverability issue you mention that led to this series\nforgetting about the possibility of incorporating this functionality\ninto \"git gc\".\n\n[...]\n> I guess I would argue for the introduction of a new command, like `git\n> maintenance`, which could potentially trigger a `git gc --auto`, but is\n> primarily intended to run _outside_ of the users' work hours.\n>\n> Once we have that, we can always figure out whether there is a convenient\n> way to register this via `crontab` or Windows Task Scheduler, without\n> asking the users to do all of these tedious steps manually.\n\nWonderful, it sounds like we're pretty much in agreement.\n\n[...]\n> Therefore, I would like very much to have a `git maintenance --schedule`\n> (or something like that), even if only on Windows, on the grounds alone\n> that it is even more tedious to work with the Windows Task Scheduler. But\n> I would prefer to have that also in my Linux setup. The convenience for\n> the users (myself included) is just too compelling.\n\nI think Josh was arguing for deferring that part to a separate series.\nOtherwise we could end up spending so much time focusing on scheduling\nand service supervision that the series gets delayed and no one\nbenefits from Stolee's work to improve how routine packing works.\n\nThanks,\nJonathan\n"},{"id":"398741","messageId":"xmqqwo4w3y4s.fsf@gitster.c.googlers.com","threadId":"53164","inReplyTo":"20200528145018.GA58643@google.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-05-28T14:57:55Z","receivedAt":"2020-05-28T14:58:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> I think it's worth discussing naming, but it's kind of a distraction\n> from what Brian brought up.  The real question is, do we consider the\n> existing \"git gc\" infrastructure such a lost cause that we should\n> touch it as little as possible?\n\nI am fine with that, as long as the \"new\" thing will take over what\n\"git gc\" currently does.  It has always been bothering me that the\nmaintenance pieces for features added in the past few years like\nmidx were deliberately let outside the scope of \"one entry point for\nhousekeeping\".\n"},{"id":"398742","messageId":"20200528150340.GB58643@google.com","threadId":"53164","inReplyTo":"xmqqwo4w3y4s.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-05-28T15:03:40Z","receivedAt":"2020-05-28T15:03:47Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>>                              The real question is, do we consider the\n>> existing \"git gc\" infrastructure such a lost cause that we should\n>> touch it as little as possible?\n>\n> I am fine with that, as long as the \"new\" thing will take over what\n> \"git gc\" currently does.\n\nGood reminder, thank you.\n\nYes, as long as we end up replacing the old thing, making a parallel\nnew thing (e.g. with a config option for switching between during a\ntransition period) can be a fine approach.\n\nThanks,\nJonathan\n"},{"id":"398745","messageId":"980f76bc-c28e-42cd-a85e-acb70861c9a7@gmail.com","threadId":"53164","inReplyTo":"20200528150340.GB58643@google.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-05-28T15:30:32Z","receivedAt":"2020-05-28T15:30:37Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 5/28/2020 11:03 AM, Jonathan Nieder wrote:\n> Junio C Hamano wrote:\n>> Jonathan Nieder <jrnieder@gmail.com> writes:\n> \n>>>                              The real question is, do we consider the\n>>> existing \"git gc\" infrastructure such a lost cause that we should\n>>> touch it as little as possible?\n>>\n>> I am fine with that, as long as the \"new\" thing will take over what\n>> \"git gc\" currently does.\n> \n> Good reminder, thank you.\n> \n> Yes, as long as we end up replacing the old thing, making a parallel\n> new thing (e.g. with a config option for switching between during a\n> transition period) can be a fine approach.\n\nThanks for the discussion, everyone. I'm sorry that I'm very late in\nproviding a new RFC that takes this approach, but yes I intend to create\nthe \"single entry point\" for maintenance activities, and incorporate\nauto-GC as a default option there.\n\nSomething that is a good long-term goal is to have the new maintenance\nentry point replace the \"git gc --auto\" calls, so we have better\ncustomization of post-command \"automatic\" maintenance. This can be done\nwithout any of the \"background\" part of my original RFC.\n\nI've just been to busy with other tasks to devote the deep focus that\nthis feature deserves. Thanks for your patience.\n\n-Stolee\n"},{"id":"398803","messageId":"nycvar.QRO.7.76.6.2005280638570.56@tvgsbejvaqbjf.bet","threadId":"53164","inReplyTo":"980f76bc-c28e-42cd-a85e-acb70861c9a7@gmail.com","subject":"Re: [PATCH 00/15] [RFC] Maintenance jobs and job runner","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-05-28T04:39:37Z","receivedAt":"2020-05-28T21:13:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Thu, 28 May 2020, Derrick Stolee wrote:\n\n> On 5/28/2020 11:03 AM, Jonathan Nieder wrote:\n> > Junio C Hamano wrote:\n> >> Jonathan Nieder <jrnieder@gmail.com> writes:\n> >\n> >>>                              The real question is, do we consider the\n> >>> existing \"git gc\" infrastructure such a lost cause that we should\n> >>> touch it as little as possible?\n> >>\n> >> I am fine with that, as long as the \"new\" thing will take over what\n> >> \"git gc\" currently does.\n> >\n> > Good reminder, thank you.\n> >\n> > Yes, as long as we end up replacing the old thing, making a parallel\n> > new thing (e.g. with a config option for switching between during a\n> > transition period) can be a fine approach.\n>\n> Thanks for the discussion, everyone. I'm sorry that I'm very late in\n> providing a new RFC that takes this approach, but yes I intend to create\n> the \"single entry point\" for maintenance activities, and incorporate\n> auto-GC as a default option there.\n\nGreat! I look forward to reviewing your next patch series iteration,\nwhenever you're ready.\n\nCiao,\nDscho\n\n>\n> Something that is a good long-term goal is to have the new maintenance\n> entry point replace the \"git gc --auto\" calls, so we have better\n> customization of post-command \"automatic\" maintenance. This can be done\n> without any of the \"background\" part of my original RFC.\n>\n> I've just been to busy with other tasks to devote the deep focus that\n> this feature deserves. Thanks for your patience.\n>\n> -Stolee\n>\n"}]}