Maintenance builtin, allowing 'gc --auto' customization
cover Maintenance builtin, allowing 'gc --auto' customization
This is a second attempt at redesigning Git's repository maintenance patterns. The first attempt [1] included a way to run jobs in the background using a long-lived process; that idea was rejected and is not included in this series. A future series will use the OS to handle scheduling tasks.
[1]
https://lore.kernel.org/git/[email protected]/
As mentioned before, git gc already plays the role of maintaining Git repositories. It has accumulated several smaller pieces in its long history, including:
1. Repacking all reachable objects into one pack-file (and deleting unreachable objects).
2. Packing refs.
3. Expiring reflogs.
4. Clearing rerere logs.
5. Updating the commit-graph file.
While expiring reflogs, clearing rererelogs, and deleting unreachable objects are suitable under the guise of "garbage collection", packing refs and updating the commit-graph file are not as obviously fitting. Further, these operations are "all or nothing" in that they rewrite almost all repository data, which does not perform well at extremely large scales. These operations can also be disruptive to foreground Git commands when git gc --auto triggers during routine use.
This series does not intend to change what git gc does, but instead create new choices for automatic maintenance activities, of which git gc remains the only one enabled by default.
The new maintenance tasks are:
* 'commit-graph' : write and verify a single layer of an incremental commit-graph.
* 'loose-objects' : prune packed loose objects, then create a new pack from a batch of loose objects.
* 'pack-files' : expire redundant packs from the multi-pack-index, then repack using the multi-pack-index's incremental repack strategy.
* 'fetch' : fetch from each remote, storing the refs in 'refs/hidden//'.
These tasks are all disabled by default, but can be enabled with config options or run explicitly using "git maintenance run --task=". There are additional config options to allow customizing the conditions for which the tasks run during the '--auto' option. ('fetch' will never run with the '--auto' option.)
Because 'gc' is implemented as a maintenance task, the most dramatic change
of this series is to convert the 'git gc --auto' calls into 'git maintenance run --auto' calls at the end of some Git commands. By default, the only change is that 'git gc --auto' will be run below an additional 'git maintenance' process.
The 'git maintenance' builtin has a 'run' subcommand so it can be extended later with subcommands that manage background maintenance, such as 'start', 'stop', 'pause', or 'schedule'. These are not the subject of this series, as it is important to focus on the maintenance activities themselves.
An expert user could set up scheduled background maintenance themselves with the current series. I have the following crontab data set up to run maintenance on an hourly basis:
0 * * * * git -C /<path-to-repo> maintenance run --no-quiet >>/<path-to-repo>/.git/maintenance.log
My config includes all tasks except the 'gc' task. The hourly run is over-aggressive, but is sufficient for testing. I'll replace it with daily when I feel satisfied.
Hopefully this direction is seen as a positive one. My goal was to add more options for expert users, along with the flexibility to create background maintenance via the OS in a later series.
OUTLINE
=======
Patches 1-4 remove some references to the_repository in builtin/gc.c before we start depending on code in that builtin.
Patches 5-7 create the 'git maintenance run' builtin and subcommand as a simple shim over 'git gc' and replaces calls to 'git gc --auto' from other commands.
Patches 8-15 create new maintenance tasks. These are the same tasks sent in the previous RFC.
Patches 16-21 create more customization through config and perform other polish items.
FUTURE WORK
===========
* Add 'start', 'stop', and 'schedule' subcommands to initialize the commands run in the background.
* Split the 'gc' builtin into smaller maintenance tasks that are enabled by default, but might have different '--auto' conditions and more config options.
* Replace config like 'gc.writeCommitGraph' and 'fetch.writeCommitGraph' with use of the 'commit-graph' task.
Thanks, -Stolee
Derrick Stolee (21):
gc: use the_repository less often
gc: use repository in too_many_loose_objects()
gc: use repo config
gc: drop the_repository in log location
maintenance: create basic maintenance runner
maintenance: add --quiet option
maintenance: replace run_auto_gc()
maintenance: initialize task array and hashmap
maintenance: add commit-graph task
maintenance: add --task option
maintenance: take a lock on the objects directory
maintenance: add fetch task
maintenance: add loose-objects task
maintenance: add pack-files task
maintenance: auto-size pack-files batch
maintenance: create maintenance.<task>.enabled config
maintenance: use pointers to check --auto
maintenance: add auto condition for commit-graph task
maintenance: create auto condition for loose-objects
maintenance: add pack-files auto condition
midx: use start_delayed_progress()
.gitignore | 1 + Documentation/config.txt | 2 + Documentation/config/maintenance.txt | 32 + Documentation/fetch-options.txt | 5 +- Documentation/git-clone.txt | 7 +- Documentation/git-maintenance.txt | 124 ++++ builtin.h | 1 + builtin/am.c | 2 +- builtin/commit.c | 2 +- builtin/fetch.c | 6 +- builtin/gc.c | 881 +++++++++++++++++++++++++-- builtin/merge.c | 2 +- builtin/rebase.c | 4 +- commit-graph.c | 8 +- commit-graph.h | 1 + config.c | 24 +- config.h | 2 + git.c | 1 + midx.c | 12 +- midx.h | 1 + object.h | 1 + run-command.c | 7 +- run-command.h | 2 +- t/t5319-multi-pack-index.sh | 14 +- t/t5510-fetch.sh | 2 +- t/t5514-fetch-multiple.sh | 2 +- t/t7900-maintenance.sh | 211 +++++++ 27 files changed, 1265 insertions(+), 92 deletions(-) create mode 100644 Documentation/config/maintenance.txt create mode 100644 Documentation/git-maintenance.txt create mode 100755 t/t7900-maintenance.sh
base-commit: 4a0fcf9f760c9774be77f51e1e88a7499b53d2e2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-671%2Fderrickstolee%2Fmaintenance%2Fgc-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-671/derrickstolee/maintenance/gc-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/671
Signature · 1 line
gitgitgadget
PATCH 1/21 gc: use the_repository less often
From: Derrick Stolee <[email protected]>
In a later change, we will consume several static methods in builtin/gc.c for another builtin. Before doing so, let's clean up some uses of the_repository. These specifically are centered around accesses to the packed_git list.
--- builtin/gc.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-)
builtin/gc.c +20 -18
@@ -192,12 +192,13 @@ static int too_many_loose_objects(void)return needed; } -static struct packed_git *find_base_packs (struct string_list *packs, +static struct packed_git *find_base_packs (struct repository *r, +struct string_list *packs,unsigned long limit) {struct packed_git *p, *base =NULL ; -for (p =get_all_packs (the_repository); p; p = p->next) { +for (p =get_all_packs (r); p; p = p->next) {if (!p->pack_local)continue ;if (limit) {
@@ -214,7 +215,7 @@ static struct packed_git *find_base_packs(struct string_list *packs,return base; } -static int too_many_packs (void ) +static int too_many_packs (struct repository *r) {struct packed_git *p;int cnt;
@@ -222,7 +223,7 @@ static int too_many_packs(void)if (gc_auto_pack_limit <=0 )return 0 ; -for (cnt =0 , p =get_all_packs (the_repository); p; p = p->next) { +for (cnt =0 , p =get_all_packs (r); p; p = p->next) {if (!p->pack_local)continue ;if (p->pack_keep)
@@ -334,7 +335,7 @@ static void add_repack_incremental_option(void)argv_array_push (&repack,"--no-write-bitmap-index" ); } -static int need_to_gc (void ) +static int need_to_gc (struct repository *r) {/* * Setting gc.auto to 0 or negative can disable the
@@ -349,18 +350,18 @@ static int need_to_gc(void)* we run "repack -A -d -l". Otherwise we tell the caller * there is no need. */ -if (too_many_packs ()) { +if (too_many_packs (r)) {struct string_list keep_pack =STRING_LIST_INIT_NODUP ;if (big_pack_threshold) { -find_base_packs (&keep_pack, big_pack_threshold); +find_base_packs (r, &keep_pack, big_pack_threshold);if (keep_pack.nr >= gc_auto_pack_limit) { big_pack_threshold =0 ;string_list_clear (&keep_pack,0 ); -find_base_packs (&keep_pack,0 ); +find_base_packs (r, &keep_pack,0 ); } }else { -struct packed_git *p =find_base_packs (&keep_pack,0 ); +struct packed_git *p =find_base_packs (r, &keep_pack,0 );uint64_t mem_have, mem_want; mem_have =total_ram ();
@@ -523,6 +524,7 @@ static void gc_before_repack(void)int cmd_gc (int argc,const char **argv,const char *prefix) { +struct repository *r = the_repository;int aggressive =0 ;int auto_gc =0 ;int quiet =0 ;
@@ -589,7 +591,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)/* * Auto-gc should be least intrusive as possible. */ -if (!need_to_gc ()) +if (!need_to_gc (r))return 0 ;if (!quiet) {if (detach_auto)
@@ -623,9 +625,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix)if (keep_base_pack !=-1 ) {if (keep_base_pack) -find_base_packs (&keep_pack,0 ); +find_base_packs (r, &keep_pack,0 ); }else if (big_pack_threshold) { -find_base_packs (&keep_pack, big_pack_threshold); +find_base_packs (r, &keep_pack, big_pack_threshold); }add_repack_all_option (&keep_pack);
@@ -652,7 +654,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)gc_before_repack ();if (!repository_format_precious_objects) { -close_object_store (the_repository->objects); +close_object_store (r->objects);if (run_command_v_opt (repack.argv,RUN_GIT_CMD ))die (FAILED_RUN , repack.argv[0 ]);
@@ -678,15 +680,15 @@ int cmd_gc(int argc, const char **argv, const char *prefix)die (FAILED_RUN , rerere.argv[0 ]); report_garbage = report_pack_garbage; -reprepare_packed_git (the_repository); +reprepare_packed_git (r);if (pack_garbage.nr >0 ) { -close_object_store (the_repository->objects); +close_object_store (r->objects);clean_pack_garbage (); } -prepare_repo_settings (the_repository); -if (the_repository->settings.gc_write_commit_graph ==1 ) -write_commit_graph_reachable (the_repository->objects->odb, +prepare_repo_settings (r); +if (r->settings.gc_write_commit_graph ==1 ) +write_commit_graph_reachable (r->objects->odb, !quiet && !daemonized ?COMMIT_GRAPH_WRITE_PROGRESS :0 ,NULL );
Signature · 1 line
gitgitgadget
PATCH 2/21 gc: use repository in too_many_loose_objects()
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The previous change performed a mechanical change to swap the_repository for a struct repository pointer when the use of the_repository was obvious. However, the too_many_loose_objects() method uses git_path() instead of repo_git_path(), which implies a hidden dependence on the_repository.
--- builtin/gc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
builtin/gc.c +4 -4
@@ -159,7 +159,7 @@ static void gc_config(void)git_config (git_default_config,NULL ); } -static int too_many_loose_objects (void ) +static int too_many_loose_objects (struct repository *r) {/* * Quickly check if a "gc" is needed, by estimating how
@@ -174,7 +174,7 @@ static int too_many_loose_objects(void)int needed =0 ;const unsigned hexsz_loose = the_hash_algo->hexsz -2 ; - dir =opendir (git_path ("objects/17" )); + dir =opendir (repo_git_path (r,"objects/17" ));if (!dir)return 0 ;
@@ -378,7 +378,7 @@ static int need_to_gc(struct repository *r)add_repack_all_option (&keep_pack);string_list_clear (&keep_pack,0 ); - }else if (too_many_loose_objects ()) + }else if (too_many_loose_objects (r))add_repack_incremental_option ();else return 0 ;
@@ -692,7 +692,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) !quiet && !daemonized ?COMMIT_GRAPH_WRITE_PROGRESS :0 , NULL); -if (auto_gc &&too_many_loose_objects ()) +if (auto_gc &&too_many_loose_objects (r))warning (_ ("There are too many unreachable loose objects; " "run 'git prune' to remove them." ));
Signature · 1 line
gitgitgadget
PATCH 3/21 gc: use repo config
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The gc builtin is working towards using a repository pointer instead of relying on the_repository everywhere. Some of the instances of the_repository are hidden underneath git_config...() methods. Expose them by using repo_config...() instead.
One method did not previously have a repo equivalent:git_config_get_expiry().
--- builtin/gc.c | 37 +++++++++++++++++++------------------ config.c | 24 +++++++++++++++--------- config.h | 2 ++ 3 files changed, 36 insertions(+), 27 deletions(-)
builtin/gc.c +19 -18
@@ -116,12 +116,13 @@ static void process_log_file_on_signal(int signo)raise (signo); } -static int gc_config_is_timestamp_never (const char *var) +static int gc_config_is_timestamp_never (struct repository *r, +const char *var) {const char *value;timestamp_t expire; -if (!git_config_get_value (var, &value) && value) { +if (!repo_config_get_value (r, var, &value) && value) {if (parse_expiry_date (value, &expire))die (_ ("failed to parse '%s' value '%s'" ), var, value);return expire ==0 ;
@@ -129,34 +130,34 @@ static int gc_config_is_timestamp_never(const char *var)return 0 ; } -static void gc_config (void ) +static void gc_config (struct repository *r) {const char *value; -if (!git_config_get_value ("gc.packrefs" , &value)) { +if (!repo_config_get_value (r,"gc.packrefs" , &value)) {if (value && !strcmp (value,"notbare" )) pack_refs =-1 ;else pack_refs =git_config_bool ("gc.packrefs" , value); } -if (gc_config_is_timestamp_never ("gc.reflogexpire" ) && -gc_config_is_timestamp_never ("gc.reflogexpireunreachable" )) +if (gc_config_is_timestamp_never (r,"gc.reflogexpire" ) && +gc_config_is_timestamp_never (r,"gc.reflogexpireunreachable" )) prune_reflogs =0 ; -git_config_get_int ("gc.aggressivewindow" , &aggressive_window); -git_config_get_int ("gc.aggressivedepth" , &aggressive_depth); -git_config_get_int ("gc.auto" , &gc_auto_threshold); -git_config_get_int ("gc.autopacklimit" , &gc_auto_pack_limit); -git_config_get_bool ("gc.autodetach" , &detach_auto); -git_config_get_expiry ("gc.pruneexpire" , &prune_expire); -git_config_get_expiry ("gc.worktreepruneexpire" , &prune_worktrees_expire); -git_config_get_expiry ("gc.logexpiry" , &gc_log_expire); +repo_config_get_int (r,"gc.aggressivewindow" , &aggressive_window); +repo_config_get_int (r,"gc.aggressivedepth" , &aggressive_depth); +repo_config_get_int (r,"gc.auto" , &gc_auto_threshold); +repo_config_get_int (r,"gc.autopacklimit" , &gc_auto_pack_limit); +repo_config_get_bool (r,"gc.autodetach" , &detach_auto); +repo_config_get_expiry (r,"gc.pruneexpire" , &prune_expire); +repo_config_get_expiry (r,"gc.worktreepruneexpire" , &prune_worktrees_expire); +repo_config_get_expiry (r,"gc.logexpiry" , &gc_log_expire); -git_config_get_ulong ("gc.bigpackthreshold" , &big_pack_threshold); -git_config_get_ulong ("pack.deltacachesize" , &max_delta_cache_size); +repo_config_get_ulong (r,"gc.bigpackthreshold" , &big_pack_threshold); +repo_config_get_ulong (r,"pack.deltacachesize" , &max_delta_cache_size); -git_config (git_default_config,NULL ); +repo_config (r, git_default_config,NULL ); }static int too_many_loose_objects (struct repository *r)
@@ -562,7 +563,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)argv_array_pushl (&rerere,"rerere" ,"gc" ,NULL );/* default expiry time, overwritten in gc_config */ -gc_config (); +gc_config (r);if (parse_expiry_date (gc_log_expire, &gc_log_expire_time))die (_ ("failed to parse gc.logexpiry value %s" ), gc_log_expire);
config.c +15 -9
@@ -2211,6 +2211,20 @@ int repo_config_get_pathname(struct repository *repo,return ret; } +int repo_config_get_expiry (struct repository *repo, +const char *key,const char **output) +{ +int ret =repo_config_get_string_const (repo, key, output); +if (ret) +return ret; +if (strcmp (*output,"now" )) { +timestamp_t now =approxidate ("now" ); +if (approxidate (*output) >= now) +git_die_config (key,_ ("Invalid %s: '%s'" ), key, *output); + } +return ret; +} +/* Functions used historically to read configuration from 'the_repository' */ void git_config (config_fn_t fn,void *data) {
@@ -2274,15 +2288,7 @@ int git_config_get_pathname(const char *key, const char **dest)int git_config_get_expiry (const char *key,const char **output) { -int ret =git_config_get_string_const (key, output); -if (ret) -return ret; -if (strcmp (*output,"now" )) { -timestamp_t now =approxidate ("now" ); -if (approxidate (*output) >= now) -git_die_config (key,_ ("Invalid %s: '%s'" ), key, *output); - } -return ret; +return repo_config_get_expiry (the_repository, key, output); }int git_config_get_expiry_in_days (const char *key,timestamp_t *expiry,timestamp_t now)
config.h +2 -0
@@ -490,6 +490,8 @@ int repo_config_get_maybe_bool(struct repository *repo, const char *key, int *dest);int repo_config_get_pathname (struct repository *repo,const char *key,const char **dest); +int repo_config_get_expiry (struct repository *repo, +const char *key,const char **output);/** * Querying For Specific Variables
Signature · 1 line
gitgitgadget
PATCH 4/21 gc: drop the_repository in log location
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The report_last_gc_error() method use git_pathdup() which implicitly uses the_repository. Replace this with strbuf_repo_path() to get a path buffer we control that uses a given repository pointer.
--- builtin/gc.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-)
builtin/gc.c +11 -9
@@ -462,28 +462,30 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)* gc should not proceed due to an error in the last run. Prints a * message and returns -1 if an error occurred while reading gc.log */ -static int report_last_gc_error (void ) +static int report_last_gc_error (struct repository *r) {struct strbuf sb =STRBUF_INIT ;int ret =0 ;ssize_t len;struct stat st; -char *gc_log_path =git_pathdup ("gc.log" ); +struct strbuf gc_log_path =STRBUF_INIT ; -if (stat (gc_log_path, &st)) { +strbuf_repo_git_path (&gc_log_path, r,"gc.log" ); + +if (stat (gc_log_path.buf, &st)) {if (errno ==ENOENT ) goto done; - ret =error_errno (_ ("cannot stat '%s'" ), gc_log_path); + ret =error_errno (_ ("cannot stat '%s'" ), gc_log_path.buf); goto done; }if (st.st_mtime < gc_log_expire_time) goto done; - len =strbuf_read_file (&sb, gc_log_path,0 ); + len =strbuf_read_file (&sb, gc_log_path.buf,0 );if (len <0 ) - ret =error_errno (_ ("cannot read '%s'" ), gc_log_path); + ret =error_errno (_ ("cannot read '%s'" ), gc_log_path.buf);else if (len >0 ) {/* * A previous gc failed. Report the error, and don't
@@ -496,12 +498,12 @@ static int report_last_gc_error(void)"Automatic cleanup will not be performed " "until the file is removed.\n\n" "%s"), - gc_log_path, sb.buf); + gc_log_path.buf, sb.buf); ret =1 ; }strbuf_release (&sb); done: -free (gc_log_path); +strbuf_release (&gc_log_path);return ret; }
@@ -602,7 +604,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)fprintf (stderr,_ ("See \"git help gc\" for manual housekeeping.\n" )); }if (detach_auto) { -int ret =report_last_gc_error (); +int ret =report_last_gc_error (r);if (ret <0 )/* an I/O error occurred, already reported */ exit (128 );
Signature · 1 line
gitgitgadget
Re: [PATCH 04/21] gc: drop the_repository in log location
Quoted 7 lines
From: Derrick Stolee <[email protected]>
The
report_last_gc_error()method usegit_pathdup()which implicitly usesthe_repository. Replace this withstrbuf_repo_path()to get a path buffer we control that uses a given repository pointer.Signed-off-by Derrick Stolee <[email protected]>
Regarding the first 4 patches, it would be great if there was a test like the one in test_repository.c (together with a code coverage report, perhaps) that verifies that all code paths do not use the_repository.
But set aside that test for now - I don't think gc.c fully supports arbitrary repositories. In particular, when running a subprocess, it inherits the environment from the current process. I see that future patches try to resolve this by passing "-C", but that does not work if environment variables like GIT_DIR are set (because the environment variables override the "-C"). Perhaps we need a function that runs a subprocess in a specific repository. I ran into the same problem when attempting to make fetch-pack (which runs index-pack as a subprocess) support arbitrary repositories, but I haven't looked deeply into resolving this yet (and I haven't looked at that problem in a while).
Having said that, I'm fine with these patches being in the set - the only negative is that perhaps a reader would be misled into thinking that GC supports arbitrary repositories.
Re: [PATCH 04/21] gc: drop the_repository in log location
Quoted 23 lines · Jonathan Tan
Quoted 7 lines
From: Derrick Stolee <[email protected]>
The
report_last_gc_error()method usegit_pathdup()which implicitly usesthe_repository. Replace this withstrbuf_repo_path()to get a path buffer we control that uses a given repository pointer.Signed-off-by Derrick Stolee <[email protected]>Regarding the first 4 patches, it would be great if there was a test like the one in
test_repository.c(together with a code coverage report, perhaps) that verifies that all code paths do not usethe_repository.But set aside that test for now - I don't think
gc.cfully supports arbitrary repositories. In particular, when running a subprocess, it inherits the environment from the current process. I see that future patches try to resolve this by passing "-C", but that does not work if environment variables likeGIT_DIRare set (because the environment variables override the "-C"). Perhaps we need a function that runs a subprocess in a specific repository. I ran into the same problem when attempting to make fetch-pack (which runs index-pack as a subprocess) support arbitrary repositories, but I haven't looked deeply into resolving this yet (and I haven't looked at that problem in a while).
Quoted 3 lines
Having said that, I'm fine with these patches being in the set - the only negative is that perhaps a reader would be misled into thinking that GC supports arbitrary repositories.
I agree. I hope that I do not give the impression that GC is now safe for arbitrary repositories. I only thought that this was prudent to do before I start taking new dependencies on the code.
It' probably time for someone to do a round of the_repository cleanups again, and perhaps the RC window is a good time to think about that (for submission after the release).
Thanks,
-Stolee
PATCH 5/21 maintenance: create basic maintenance runner
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The 'gc' builtin is our current entrypoint for automatically maintaining a repository. This one tool does many operations, such as repacking the repository, packing refs, and rewriting the commit-graph file. The name implies it performs "garbage collection" which means several different things, and some users may not want to use this operation that rewrites the entire object database.
Create a new 'maintenance' builtin that will become a more general- purpose command. To start, it will only support the 'run' subcommand, but will later expand to add subcommands for scheduling maintenance in the background.
For now, the 'maintenance' builtin is a thin shim over the 'gc' builtin. In fact, the only option is the '--auto' toggle, which is handed directly to the 'gc' builtin. The current change is isolated to this simple operation to prevent more interesting logic from being lost in all of the boilerplate of adding a new builtin.
Use existing builtin/gc.c file because we want to share code between the two builtins. It is possible that we will have 'maintenance' replace the 'gc' builtin entirely at some point, leaving 'git gc' as an alias for some specific arguments to 'git maintenance run'.
--- .gitignore | 1 + Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++ builtin.h | 1 + builtin/gc.c | 61 +++++++++++++++++++++++++++++++ git.c | 1 + t/t7900-maintenance.sh | 22 +++++++++++ 6 files changed, 143 insertions(+) create mode 100644 Documentation/git-maintenance.txt create mode 100755 t/t7900-maintenance.sh
.gitignore +1 -0
@@ -90,6 +90,7 @@ /git-ls-tree /git-mailinfo /git-mailsplit +/git-maintenance /git-merge /git-merge-base /git-merge-index
Documentation/git-maintenance.txt new file +57 -0
57 lines not on this page · raw message
builtin.h +1 -0
@@ -167,6 +167,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);int cmd_ls_remote (int argc,const char **argv,const char *prefix);int cmd_mailinfo (int argc,const char **argv,const char *prefix);int cmd_mailsplit (int argc,const char **argv,const char *prefix); +int cmd_maintenance (int argc,const char **argv,const char *prefix);int cmd_merge (int argc,const char **argv,const char *prefix);int cmd_merge_base (int argc,const char **argv,const char *prefix);int cmd_merge_index (int argc,const char **argv,const char *prefix);
builtin/gc.c +61 -0
64 lines not on this page · raw message
git.c +1 -0
@@ -527,6 +527,7 @@ static struct cmd_struct commands[] = { { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "mailsplit", cmd_mailsplit, NO_PARSEOPT }, + { "maintenance", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
t/t7900-maintenance.sh new file +22 -0
@@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='git maintenance builtin' + +GIT_TEST_COMMIT_GRAPH=0 +GIT_TEST_MULTI_PACK_INDEX=0 + +. ./test-lib.sh + +test_expect_success 'help text' ' + test_must_fail git maintenance -h 2>err && + test_i18ngrep "usage: git maintenance run" err +' + +test_expect_success 'gc [--auto]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run && + GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && + grep ",\"gc\"]" run-no-auto.txt && + grep ",\"gc\",\"--auto\"]" run-auto.txt +' + +test_done
Signature · 1 line
gitgitgadget
PATCH 6/21 maintenance: add --quiet option
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Maintenance activities are commonly used as steps in larger scripts. Providing a '--quiet' option allows those scripts to be less noisy when run on a terminal window. Turn this mode on by default when stderr is not a terminal.
Pipe the option to the 'git gc' child process.
--- Documentation/git-maintenance.txt | 3 +++ builtin/gc.c | 7 +++++++ t/t7900-maintenance.sh | 8 +++++--- 3 files changed, 15 insertions(+), 3 deletions(-)
Documentation/git-maintenance.txt +3 -0
@@ -52,6 +52,9 @@ OPTIONS in the `gc.auto` config setting, or when the number of pack-files exceeds the `gc.autoPackLimit` config setting. +--quiet:: + Do not report progress or other information over `stderr`. + GIT --- Part of the linkgit:git[1] suite
builtin/gc.c +7 -0
@@ -712,6 +712,7 @@ static const char * const builtin_maintenance_usage[] = {struct maintenance_opts {int auto_flag; +int quiet; } opts;static int maintenance_task_gc (struct repository *r)
@@ -723,6 +724,8 @@ static int maintenance_task_gc(struct repository *r)if (opts.auto_flag)argv_array_pushl (&cmd,"--auto" ,NULL ); +if (opts.quiet) +argv_array_pushl (&cmd,"--quiet" ,NULL );close_object_store (r->objects); result =run_command_v_opt (cmd.argv,RUN_GIT_CMD );
@@ -743,6 +746,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)static struct option builtin_maintenance_options[] = {OPT_BOOL (0 ,"auto" , &opts.auto_flag,N_ ("run tasks based on the state of the repository" )), +OPT_BOOL (0 ,"quiet" , &opts.quiet, +N_ ("do not report progress or other information over stderr" )),OPT_END () };
@@ -752,6 +757,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)usage_with_options (builtin_maintenance_usage, builtin_maintenance_options); + opts.quiet = !isatty (2 ); + argc = parse_options(argc, argv, prefix, builtin_maintenance_options, builtin_maintenance_usage,
t/t7900-maintenance.sh +5 -3
@@ -12,11 +12,13 @@ test_expect_success 'help text' ' test_i18ngrep "usage: git maintenance run" err ' -test_expect_success 'gc [--auto]' ' - GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run && +test_expect_success 'gc [--auto|--quiet]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run --no-quiet && GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && + GIT_TRACE2_EVENT="$(pwd)/run-quiet.txt" git maintenance run --quiet && grep ",\"gc\"]" run-no-auto.txt && - grep ",\"gc\",\"--auto\"]" run-auto.txt + grep ",\"gc\",\"--auto\"" run-auto.txt && + grep ",\"gc\",\"--quiet\"" run-quiet.txt ' test_done
Signature · 1 line
gitgitgadget
PATCH 7/21 maintenance: replace run_auto_gc()
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The run_auto_gc() method is used in several places to trigger a check for repo maintenance after some Git commands, such as 'git commit' or 'git fetch'.
To allow for extra customization of this maintenance activity, replace the 'git gc --auto [--quiet]' call with one to 'git maintenance run --auto [--quiet]'. As we extend the maintenance builtin with other steps, users will be able to select different maintenance activities.
Rename run_auto_gc() to run_auto_maintenance() to be clearer what is happening on this call, and to expose all callers in the current diff.
Since 'git fetch' already allows disabling the 'git gc --auto' subprocess, add an equivalent option with a different name to be more descriptive of the new behavior: '--[no-]maintenance'. Update the documentation to include these options at the same time.
--- Documentation/fetch-options.txt | 5 +++-- Documentation/git-clone.txt | 7 ++++--- builtin/am.c | 2 +- builtin/commit.c | 2 +- builtin/fetch.c | 6 ++++-- builtin/merge.c | 2 +- builtin/rebase.c | 4 ++-- run-command.c | 7 +++++-- run-command.h | 2 +- t/t5510-fetch.sh | 2 +- 10 files changed, 23 insertions(+), 16 deletions(-)
Documentation/fetch-options.txt +3 -2
@@ -86,9 +86,10 @@ ifndef::git-pull[] Allow several <repository> and <group> arguments to be specified. No <refspec>s may be specified. +--[no-]maintenance:: --[no-]auto-gc:: - Run `git gc --auto` at the end to perform garbage collection - if needed. This is enabled by default. + Run `git maintenance run --auto` at the end to perform garbage + collection if needed. This is enabled by default. --[no-]write-commit-graph:: Write a commit-graph after fetching. This overrides the config
Documentation/git-clone.txt +4 -3
@@ -78,9 +78,10 @@ repository using this option and then delete branches (or use any other Git command that makes any existing commit unreferenced) in the source repository, some objects may become unreferenced (or dangling). These objects may be removed by normal Git operations (such as `git commit`) -which automatically call `git gc --auto`. (See linkgit:git-gc[1].) -If these objects are removed and were referenced by the cloned repository, -then the cloned repository will become corrupt. +which automatically call `git maintenance run --auto` and `git gc --auto`. +(See linkgit:git-maintenance[1] and linkgit:git-gc[1].) If these objects +are removed and were referenced by the cloned repository, then the cloned +repository will become corrupt. + Note that running `git repack` without the `--local` option in a repository cloned with `--shared` will copy objects from the source repository into a pack
builtin/am.c +1 -1
@@ -1795,7 +1795,7 @@ static void am_run(struct am_state *state, int resume)if (!state->rebasing) {am_destroy (state);close_object_store (the_repository->objects); -run_auto_gc (state->quiet); +run_auto_maintenance (state->quiet); } }
builtin/commit.c +1 -1
@@ -1702,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)git_test_write_commit_graph_or_die ();repo_rerere (the_repository,0 ); -run_auto_gc (quiet); +run_auto_maintenance (quiet);run_commit_hook (use_editor,get_index_file (),"post-commit" ,NULL );if (amend && !no_post_rewrite) {commit_post_rewrite (the_repository, current_head, &oid);
builtin/fetch.c +4 -2
@@ -196,8 +196,10 @@ static struct option builtin_fetch_options[] = {OPT_STRING_LIST (0 ,"negotiation-tip" , &negotiation_tip,N_ ("revision" ),N_ ("report that we have only objects reachable from this object" )),OPT_PARSE_LIST_OBJECTS_FILTER (&filter_options), +OPT_BOOL (0 ,"maintenance" , &enable_auto_gc, +N_ ("run 'maintenance --auto' after fetching" )),OPT_BOOL (0 ,"auto-gc" , &enable_auto_gc, -N_ ("run 'gc --auto' after fetching" )), +N_ ("run 'maintenance --auto' after fetching" )),OPT_BOOL (0 ,"show-forced-updates" , &fetch_show_forced_updates,N_ ("check for forced-updates on all updated branches" )), OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph,
@@ -1882,7 +1884,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)close_object_store (the_repository->objects);if (enable_auto_gc) -run_auto_gc (verbosity <0 ); +run_auto_maintenance (verbosity <0 );return result; }
builtin/merge.c +1 -1
@@ -457,7 +457,7 @@ static void finish(struct commit *head_commit,* user should see them. */ close_object_store (the_repository->objects); -run_auto_gc (verbosity <0 ); +run_auto_maintenance (verbosity <0 ); } }if (new_head && show_diffstat) {
builtin/rebase.c +2 -2
@@ -728,10 +728,10 @@ static int finish_rebase(struct rebase_options *opts)apply_autostash (state_dir_path ("autostash" , opts));close_object_store (the_repository->objects);/* -* We ignore errors in 'gc --auto', since the +* We ignore errors in 'git maintenance run --auto', since the * user should see them. */ -run_auto_gc (!(opts->flags & (REBASE_NO_QUIET |REBASE_VERBOSE ))); +run_auto_maintenance (!(opts->flags & (REBASE_NO_QUIET |REBASE_VERBOSE )));if (opts->type ==REBASE_MERGE ) {struct replay_opts replay =REPLAY_OPTS_INIT ;
run-command.c +5 -2
@@ -1865,14 +1865,17 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,return result; } -int run_auto_gc (int quiet) +int run_auto_maintenance (int quiet) {struct argv_array argv_gc_auto =ARGV_ARRAY_INIT ;int status; -argv_array_pushl (&argv_gc_auto,"gc" ,"--auto" ,NULL ); +argv_array_pushl (&argv_gc_auto,"maintenance" ,"run" ,"--auto" ,NULL );if (quiet)argv_array_push (&argv_gc_auto,"--quiet" ); +else +argv_array_push (&argv_gc_auto,"--no-quiet" ); + status =run_command_v_opt (argv_gc_auto.argv,RUN_GIT_CMD );argv_array_clear (&argv_gc_auto);return status;
run-command.h +1 -1
@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);/* * Trigger an auto-gc */ -int run_auto_gc (int quiet); +int run_auto_maintenance (int quiet);#define RUN_COMMAND_NO_STDIN 1#define RUN_GIT_CMD 2/*If this is to be git sub-command */
t/t5510-fetch.sh +1 -1
@@ -919,7 +919,7 @@ test_expect_success 'fetching with auto-gc does not lock up' ' git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && - GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 && + GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 && test_i18ngrep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out )
Signature · 1 line
gitgitgadget
PATCH 8/21 maintenance: initialize task array and hashmap
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
In anticipation of implementing multiple maintenance tasks inside the 'maintenance' builtin, use a list and hashmap of structs to describe the work to be done.
The struct maintenance_task stores the name of the task (as given by a future command-line argument) along with a function pointer to its implementation and a boolean for whether the step is enabled.
A list of pointers to these structs are initialized with the full list of implemented tasks along with a default order. For now, this list only contains the "gc" task. This task is also the only task enabled by default.
This list is also inserted into a hashmap. This allows command-line arguments to quickly find the tasks by name, not sensitive to case. To ensure this list and hashmap work well together, the list only contains pointers to the struct information. This will allow a sort on the list while preserving the hashmap data.
--- builtin/gc.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-)
builtin/gc.c +62 -1
@@ -705,6 +705,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)return 0 ; } +#define MAX_NUM_TASKS 1 +static const char *const builtin_maintenance_usage[] = {N_ ("git maintenance run [<options>]" ),NULL
@@ -734,9 +736,67 @@ static int maintenance_task_gc(struct repository *r)return result; } +typedef int maintenance_task_fn (struct repository *r); + +struct maintenance_task { +struct hashmap_entry ent; +const char *name; +maintenance_task_fn *fn; +unsigned enabled:1 ; +}; + +static int task_entry_cmp (const void *unused_cmp_data, +const struct hashmap_entry *eptr, +const struct hashmap_entry *entry_or_key, +const void *keydata) +{ +const struct maintenance_task *e1, *e2; +const char *name = keydata; + + e1 = container_of(eptr, const struct maintenance_task, ent); + e2 = container_of(entry_or_key, const struct maintenance_task, ent); + +return strcasecmp (e1->name, name ? name : e2->name); +} + +struct maintenance_task *tasks[MAX_NUM_TASKS ]; +int num_tasks; +struct hashmap task_map; +static int maintenance_run (struct repository *r) { -return maintenance_task_gc (r); +int i; +int result =0 ; + +for (i =0 ; !result && i < num_tasks; i++) { +if (!tasks[i]->enabled) +continue ; + result = tasks[i]->fn (r); + } + +return result; +} + +static void initialize_tasks (void ) +{ +int i; + num_tasks =0 ; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) + tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task )); + + tasks[num_tasks]->name ="gc" ; + tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->enabled =1 ; + num_tasks++; + +hashmap_init (&task_map, task_entry_cmp,NULL ,MAX_NUM_TASKS ); + +for (i =0 ; i < num_tasks; i++) { +hashmap_entry_init (&tasks[i]->ent, +strihash (tasks[i]->name)); +hashmap_add (&task_map, &tasks[i]->ent); + } }int cmd_maintenance (int argc,const char **argv,const char *prefix)
@@ -758,6 +818,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix) builtin_maintenance_options); opts.quiet = !isatty (2 ); +initialize_tasks (); argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
Signature · 1 line
gitgitgadget
Re: [PATCH 08/21] maintenance: initialize task array and hashmap
Quoted 5 lines
This list is also inserted into a hashmap. This allows command-line arguments to quickly find the tasks by name, not sensitive to case. To ensure this list and hashmap work well together, the list only contains pointers to the struct information. This will allow a sort on the list while preserving the hashmap data.
I think having the hashmap is unnecessarily complicated in this case - with the small number of tasks, a list would be fine. But I don't feel strongly about this.
Re: [PATCH 08/21] maintenance: initialize task array and hashmap
Quoted 9 lines · Jonathan Tan
Quoted 5 lines
This list is also inserted into a hashmap. This allows command-line arguments to quickly find the tasks by name, not sensitive to case. To ensure this list and hashmap work well together, the list only contains pointers to the struct information. This will allow a sort on the list while preserving the hashmap data.
I think having the hashmap is unnecessarily complicated in this case - with the small number of tasks, a list would be fine. But I don't feel strongly about this.
You're probably right that iterating through a list with (hopefully) at most a dozen entries is fast enough that a hashmap is overkill here.
Now is the real test: can I change this patch in v2 without needing to mess with any of the others? The intention here was to make adding tasks as simple as possible, so we shall see. :D
Thanks,
-Stolee
Re: [PATCH 08/21] maintenance: initialize task array and hashmap
Quoted 11 lines · Derrick Stolee
Quoted 9 lines · Jonathan Tan
Quoted 5 lines
This list is also inserted into a hashmap. This allows command-line arguments to quickly find the tasks by name, not sensitive to case. To ensure this list and hashmap work well together, the list only contains pointers to the struct information. This will allow a sort on the list while preserving the hashmap data.
I think having the hashmap is unnecessarily complicated in this case - with the small number of tasks, a list would be fine. But I don't feel strongly about this.
Quoted 6 lines
You're probably right that iterating through a list with (hopefully) at most a dozen entries is fast enough that a hashmap is overkill here.
Now is the real test: can I change this patch in v2 without needing to mess with any of the others? The intention here was to make adding tasks as simple as possible, so we shall see. :D
Adding a new element to a list would be simple no matter how the list is represented. But I think the real question is what access pattern we expect. Do we need to look up by name a single one or selected few? Do we need the iteration/enumeration be stable? etc.
PATCH 9/21 maintenance: add commit-graph task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The first new task in the 'git maintenance' builtin is the 'commit-graph' job. It is based on the sequence of events in the 'commit-graph' job in Scalar [1]. This sequence is as follows:
1. git commit-graph write --reachable --split
2. git commit-graph verify --shallow
3. If the verify succeeds, stop.
4. Delete the commit-graph-chain file.
5. git commit-graph write --reachable --split
By writing an incremental commit-graph file using the "--split" option we minimize the disruption from this operation. The default behavior is to merge layers until the new "top" layer is less than half the size of the layer below. This provides quick writes most of the time, with the longer writes following a power law distribution.
Most importantly, concurrent Git processes only look at the commit-graph-chain file for a very short amount of time, so they will verly likely not be holding a handle to the file when we try to replace it. (This only matters on Windows.)
If a concurrent process reads the old commit-graph-chain file, but our job expires some of the .graph files before they can be read, then those processes will see a warning message (but not fail). This could be avoided by a future update to use the --expire-time argument when writing the commit-graph.
By using 'git commit-graph verify --shallow' we can ensure that the file we just wrote is valid. This is an extra safety precaution that is faster than our 'write' subcommand. In the rare situation that the newest layer of the commit-graph is corrupt, we can "fix" the corruption by deleting the commit-graph-chain file and rewrite the full commit-graph as a new one-layer commit graph. This does not completely prevent _that_ file from being corrupt, but it does recompute the commit-graph by parsing commits from the object database. In our use of this step in Scalar and VFS for Git, we have only seen this issue arise because our microsoft/git fork reverted 43d3561 ("commit-graph write: don't die if the existing graph is corrupt" 2019-03-25) for a while to keep commit-graph writes very fast. We dropped the revert when updating to v2.23.0. The verify still has potential for catching corrupt data across the layer boundary: if the new file has commit X with parent Y in an old file but the commit ID for Y in the old file had a bitswap, then we will notice that in the 'verify' command.
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/CommitGraphStep.cs
--- Documentation/git-maintenance.txt | 18 ++++++++ builtin/gc.c | 76 ++++++++++++++++++++++++++++++- commit-graph.c | 8 ++-- commit-graph.h | 1 + t/t7900-maintenance.sh | 2 +- 5 files changed, 99 insertions(+), 6 deletions(-)
Documentation/git-maintenance.txt +18 -0
@@ -35,6 +35,24 @@ run:: TASKS ----- +commit-graph:: + The `commit-graph` job updates the `commit-graph` files incrementally, + then verifies that the written data is correct. If the new layer has an + issue, then the chain file is removed and the `commit-graph` is + rewritten from scratch. ++ +The verification only checks the top layer of the `commit-graph` chain. +If the incremental write merged the new commits with at least one +existing layer, then there is potential for on-disk corruption being +carried forward into the new file. This will be noticed and the new +commit-graph file will be clean as Git reparses the commit data from +the object database. ++ +The incremental write is safe to run alongside concurrent Git processes +since it will not expire `.graph` files that were in the previous +`commit-graph-chain` file. They will be deleted by a later run based on +the expiration delay. + gc:: Cleanup unnecessary files and optimize the local repository. "GC" stands for "garbage collection," but this task performs many
builtin/gc.c +75 -1
94 lines not on this page · raw message
commit-graph.c +4 -4
@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb, oid_hex); } -static char *get_chain_filename (struct object_directory *odb) +char *get_commit_graph_chain_filename (struct object_directory *odb) {return xstrfmt ("%s/info/commit-graphs/commit-graph-chain" , odb->path); }
@@ -520,7 +520,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,struct stat st;struct object_id *oids;int i =0 , valid =1 , count; -char *chain_name =get_chain_filename (odb); +char *chain_name =get_commit_graph_chain_filename (odb);FILE *fp;int stat_res;
@@ -1635,7 +1635,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) }if (ctx->split) { -char *lock_name =get_chain_filename (ctx->odb); +char *lock_name =get_commit_graph_chain_filename (ctx->odb);hold_lock_file_for_update_mode (&lk, lock_name,LOCK_DIE_ON_ERROR ,0444 );
@@ -2012,7 +2012,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)if (ctx->split_opts && ctx->split_opts->expire_time) expire_time = ctx->split_opts->expire_time;if (!ctx->split) { -char *chain_file_name =get_chain_filename (ctx->odb); +char *chain_file_name =get_commit_graph_chain_filename (ctx->odb);unlink (chain_file_name);free (chain_file_name); ctx->num_commit_graphs_after =0 ;
commit-graph.h +1 -0
@@ -25,6 +25,7 @@ struct commit;struct bloom_filter_settings ;char *get_commit_graph_filename (struct object_directory *odb); +char *get_commit_graph_chain_filename (struct object_directory *odb);int open_commit_graph (const char *graph_file,int *fd,struct stat *st);/*
t/t7900-maintenance.sh +1 -1
@@ -12,7 +12,7 @@ test_expect_success 'help text' ' test_i18ngrep "usage: git maintenance run" err ' -test_expect_success 'gc [--auto|--quiet]' ' +test_expect_success 'run [--auto|--quiet]' ' GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run --no-quiet && GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && GIT_TRACE2_EVENT="$(pwd)/run-quiet.txt" git maintenance run --quiet &&
Signature · 1 line
gitgitgadget
Re: [PATCH 09/21] maintenance: add commit-graph task
builtin/gc.c struct maintenance_opts { · line 728
Quoted 9 lines
builtin/gc.c +9
-0+static int run_write_commit_graph (struct repository *r) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ; + +argv_array_pushl (&cmd,"-C" , r->worktree, +"commit-graph" ,"write" , +"--split" ,"--reachable" , +NULL );
As mentioned in my reply to an earlier patch (sent a few minutes ago), this won't work if there are environment variables like GIT_DIR present.
Besides that, the overall design looks reasonable.
Re: [PATCH 09/21] maintenance: add commit-graph task
Quoted 4 lines · Jonathan Tan
Quoted 4 lines
builtin/gc.c +4
-0+static int run_write_commit_graph (struct repository *r) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ;
Quoted 8 lines
builtin/gc.c struct maintenance_opts { · line 728
Quoted 5 lines
builtin/gc.c +5
-0+ +argv_array_pushl (&cmd,"-C" , r->worktree, +"commit-graph" ,"write" , +"--split" ,"--reachable" , +NULL );As mentioned in my reply to an earlier patch (sent a few minutes ago), this won't work if there are environment variables like
GIT_DIRpresent.
Do we not pass GIT_DIR to the subcommand? Or does using "-C" override the GIT_DIR?
Thanks,
-Stolee
Re: [PATCH 09/21] maintenance: add commit-graph task
Quoted 14 lines · Derrick Stolee
Quoted 4 lines · Jonathan Tan
Quoted 4 lines
builtin/gc.c +4
-0+static int run_write_commit_graph (struct repository *r) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ;Quoted 8 lines
builtin/gc.c struct maintenance_opts { · line 728
Quoted 5 lines
builtin/gc.c +5
-0+ +argv_array_pushl (&cmd,"-C" , r->worktree, +"commit-graph" ,"write" , +"--split" ,"--reachable" , +NULL );As mentioned in my reply to an earlier patch (sent a few minutes ago), this won't work if there are environment variables like
GIT_DIRpresent.
Quoted 2 lines
Do we not pass
GIT_DIRto the subcommand? Or does using "-C" override theGIT_DIR?
We do pass GIT_DIR to the subcommand, and "-C" does not override it. I think this code would work as long as "r" is the_repository, which it would be in the current code. But then the "-C" would be doing nothing useful (it might change to the top of the worktree if we weren't there for some reason, but I don't think "commit-graph write" would care either way).
But if "r" is some other repository, "commit-graph" would continue to operate in the parent process repository because of the inherited GIT_DIR. Using "--git-dir" would solve that, but as a general practice, if you're spawning a sub-process that might be in another repository, you should clear any repo-specific environment variables. The list is in local_repo_env, which you can feed to the "env" or "env_array" parameter of a child_process (see the use in connect.c for an example).
Even in the current scheme where "r" is always the_repository, I suspect this might still be buggy. If we're in a bare repository, presumably r->worktree would be NULL.
-Peff
Re: [PATCH 09/21] maintenance: add commit-graph task
Quoted 34 lines · Jeff King
Quoted 14 lines · Derrick Stolee
Quoted 4 lines · Jonathan Tan
Quoted 4 lines
builtin/gc.c +4
-0+static int run_write_commit_graph (struct repository *r) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ;Quoted 8 lines
builtin/gc.c struct maintenance_opts { · line 728
Quoted 5 lines
builtin/gc.c +5
-0+ +argv_array_pushl (&cmd,"-C" , r->worktree, +"commit-graph" ,"write" , +"--split" ,"--reachable" , +NULL );As mentioned in my reply to an earlier patch (sent a few minutes ago), this won't work if there are environment variables like
GIT_DIRpresent.Quoted 2 lines
Do we not pass
GIT_DIRto the subcommand? Or does using "-C" override theGIT_DIR?We do pass
GIT_DIRto the subcommand, and "-C" does not override it. I think this code would work as long as "r" isthe_repository, which it would be in the current code. But then the "-C" would be doing nothing useful (it might change to the top of the worktree if we weren't there for some reason, but I don't think "commit-graph write" would care either way).But if "r" is some other repository, "commit-graph" would continue to operate in the parent process repository because of the inherited
GIT_DIR. Using "--git-dir" would solve that, but as a general practice, if you're spawning a sub-process that might be in another repository, you should clear any repo-specific environment variables. The list is inlocal_repo_env, which you can feed to the "env" or "env_array" parameter of achild_process(see the use inconnect.cfor an example).
Quoted 3 lines
Even in the current scheme where "r" is always
the_repository, I suspect this might still be buggy. If we're in a bare repository, presumablyr->worktreewould be NULL.
Ah. I'll investigate this more and work to create a way to run a subcommand in a given repository. Your pointers will help a lot.
Thanks,
-Stolee
PATCH 10/21 maintenance: add --task option
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
A user may want to only run certain maintenance tasks in a certain order. Add the --task=<task> option, which allows a user to specify an ordered list of tasks to run. These cannot be run multiple times, however.
Here is where our array of maintenance_task pointers becomes critical. We can sort the array of pointers based on the task order, but we do not want to move the struct data itself in order to preserve the hashmap references. We use the hashmap to match the --task=<task> arguments into the task struct data.
--- Documentation/git-maintenance.txt | 4 ++ builtin/gc.c | 62 ++++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 23 ++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-)
Documentation/git-maintenance.txt +4 -0
@@ -73,6 +73,10 @@ OPTIONS --quiet:: Do not report progress or other information over `stderr`. +--task=<task>:: + If this option is specified one or more times, then only run the + specified tasks in the specified order. + GIT --- Part of the linkgit:git[1] suite
builtin/gc.c +60 -2
99 lines not on this page · raw message
t/t7900-maintenance.sh +23 -0
@@ -21,4 +21,27 @@ test_expect_success 'run [--auto|--quiet]' ' grep ",\"gc\",\"--quiet\"" run-quiet.txt ' +test_expect_success 'run --task=<task>' ' + GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" git maintenance run --task=commit-graph && + GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" git maintenance run --task=gc && + GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" git maintenance run --task=commit-graph && + GIT_TRACE2_EVENT="$(pwd)/run-both.txt" git maintenance run --task=commit-graph --task=gc && + ! grep ",\"gc\"" run-commit-graph.txt && + grep ",\"gc\"" run-gc.txt && + grep ",\"gc\"" run-both.txt && + grep ",\"commit-graph\",\"write\"" run-commit-graph.txt && + ! grep ",\"commit-graph\",\"write\"" run-gc.txt && + grep ",\"commit-graph\",\"write\"" run-both.txt +' + +test_expect_success 'run --task=bogus' ' + test_must_fail git maintenance run --task=bogus 2>err && + test_i18ngrep "is not a valid task" err +' + +test_expect_success 'run --task duplicate' ' + test_must_fail git maintenance run --task=gc --task=gc 2>err && + test_i18ngrep "cannot be selected multiple times" err +' + test_done
Signature · 1 line
gitgitgadget
PATCH 11/21 maintenance: take a lock on the objects directory
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Performing maintenance on a Git repository involves writing data to the .git directory, which is not safe to do with multiple writers attempting the same operation. Ensure that only one 'git maintenance' process is running at a time by holding a file-based lock. Simply the presence of the .git/maintenance.lock file will prevent future maintenance. This lock is never committed, since it does not represent meaningful data. Instead, it is only a placeholder.
If the lock file already exists, then fail silently. This will become very important later when we implement the 'fetch' task, as this is our stop-gap from creating a recursive process loop between 'git fetch' and 'git maintenance run'.
--- builtin/gc.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
builtin/gc.c +19 -0
@@ -849,6 +849,24 @@ static int maintenance_run(struct repository *r) {int i;int result =0 ; +struct lock_file lk; +char *lock_path =xstrfmt ("%s/maintenance" , r->objects->odb->path); + +if (hold_lock_file_for_update (&lk, lock_path,LOCK_NO_DEREF ) <0 ) { +/* +* Another maintenance command is running. +* +* If --auto was provided, then it is likely due to a +* recursive process stack. Do not report an error in +* that case. +*/ +if (!opts.auto_flag && !opts.quiet) +error (_ ("lock file '%s' exists, skipping maintenance" ), + lock_path); +free (lock_path); +return 0 ; + } +free (lock_path);if (opts.tasks_selected)QSORT (tasks, num_tasks, compare_tasks_by_selection);
@@ -863,6 +881,7 @@ static int maintenance_run(struct repository *r) result = tasks[i]->fn (r); } +rollback_lock_file (&lk);return result; }
Signature · 1 line
gitgitgadget
PATCH 12/21 maintenance: add fetch task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
When working with very large repositories, an incremental 'git fetch' command can download a large amount of data. If there are many other users pushing to a common repo, then this data can rival the initial pack-file size of a 'git clone' of a medium-size repo.
Users may want to keep the data on their local repos as close as possible to the data on the remote repos by fetching periodically in the background. This can break up a large daily fetch into several smaller hourly fetches.
However, if we simply ran 'git fetch <remote>' in the background, then the user running a foregroudn 'git fetch <remote>' would lose some important feedback when a new branch appears or an existing branch updates. This is especially true if a remote branch is force-updated and this isn't noticed by the user because it occurred in the background. Further, the functionality of 'git push --force-with-lease' becomes suspect.
When running 'git fetch <remote> <options>' in the background, use the following options for careful updating:
1. --no-tags prevents getting a new tag when a user wants to see the new tags appear in their foreground fetches.
2. --refmap= removes the configured refspec which usually updates refs/remotes/<remote>/* with the refs advertised by the remote.
3. By adding a new refspec "+refs/heads/*:refs/hidden/<remote>/*" we can ensure that we actually load the new values somewhere in our refspace while not updating refs/heads or refs/remotes. By storing these refs here, the commit-graph job will update the commit-graph with the commits from these hidden refs.
4. --prune will delete the refs/hidden/<remote> refs that no longer appear on the remote.
We've been using this step as a critical background job in Scalar
[1] (and VFS for Git). This solved a pain point that was showing up in user reports: fetching was a pain! Users do not like waiting to download the data that was created while they were away from their machines. After implementing background fetch, the foreground fetch commands sped up significantly because they mostly just update refs and download a small amount of new data. The effect is especially dramatic when paried with --no-show-forced-udpates (through fetch.showForcedUpdates=false).
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/FetchStep.cs
--- Documentation/git-maintenance.txt | 12 ++++++ builtin/gc.c | 65 ++++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 24 ++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-)
Documentation/git-maintenance.txt +12 -0
@@ -53,6 +53,18 @@ since it will not expire `.graph` files that were in the previous `commit-graph-chain` file. They will be deleted by a later run based on the expiration delay. +fetch:: + The `fetch` job updates the object directory with the latest objects + from all registered remotes. For each remote, a `git fetch` command + is run. The refmap is custom to avoid updating local or remote + branches (those in `refs/heads` or `refs/remotes`). Instead, the + remote refs are stored in `refs/hidden/<remote>/`. Also, no tags are + updated. ++ +This means that foreground fetches are still required to update the +remote refs, but the users is notified when the branches and tags are +updated on the remote. + gc:: Cleanup unnecessary files and optimize the local repository. "GC" stands for "garbage collection," but this task performs many
builtin/gc.c +64 -1
89 lines not on this page · raw message
t/t7900-maintenance.sh +24 -0
@@ -44,4 +44,28 @@ test_expect_success 'run --task duplicate' ' test_i18ngrep "cannot be selected multiple times" err ' +test_expect_success 'run --task=fetch with no remotes' ' + git maintenance run --task=fetch 2>err && + test_must_be_empty err +' + +test_expect_success 'fetch multiple remotes' ' + git clone . clone1 && + git clone . clone2 && + git remote add remote1 "file://$(pwd)/clone1" && + git remote add remote2 "file://$(pwd)/clone2" && + git -C clone1 switch -c one && + git -C clone2 switch -c two && + test_commit -C clone1 one && + test_commit -C clone2 two && + GIT_TRACE2_EVENT="$(pwd)/run-fetch.txt" git maintenance run --task=fetch && + grep ",\"fetch\",\"remote1\"" run-fetch.txt && + grep ",\"fetch\",\"remote2\"" run-fetch.txt && + test_path_is_missing .git/refs/remotes && + test_cmp clone1/.git/refs/heads/one .git/refs/hidden/remote1/one && + test_cmp clone2/.git/refs/heads/two .git/refs/hidden/remote2/two && + git log hidden/remote1/one && + git log hidden/remote2/two +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH 12/21] maintenance: add fetch task
Quoted 8 lines
3. By adding a new refspec "+refs/heads/*:refs/hidden/<remote>/*" we can ensure that we actually load the new values somewhere in our refspace while not updating refs/heads or refs/remotes. By storing these refs here, the commit-graph job will update the commit-graph with the commits from these hidden refs.
4. --prune will delete the refs/hidden/<remote> refs that no longer appear on the remote.
Having a ref path where Git can place commit IDs that it needs persisted is useful, not only in this case but in other cases (e.g. when fetching a submodule commit by hash, we might not have a ref name for that commit but want to persist it anyway), so I look forward to having something like this.
The name of this special ref path and its specific nature could be discussed further, but maybe it is sufficient for now to just say that the refs under this special ref path are controlled by Git, and their layout is experimental and subject to change (e.g. future versions of Git could just erase the entire path and rewrite the refs its own way).
PATCH 13/21 maintenance: add loose-objects task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
One goal of background maintenance jobs is to allow a user to disable auto-gc (gc.auto=0) but keep their repository in a clean state. Without any cleanup, loose objects will clutter the object database and slow operations. In addition, the loose objects will take up extra space because they are not stored with deltas against similar objects.
Create a 'loose-objects' task for the 'git maintenance run' command. This helps clean up loose objects without disrupting concurrent Git commands using the following sequence of events:
1. Run 'git prune-packed' to delete any loose objects that exist in a pack-file. Concurrent commands will prefer the packed version of the object to the loose version. (Of course, there are exceptions for commands that specifically care about the location of an object. These are rare for a user to run on purpose, and we hope a user that has selected background maintenance will not be trying to do foreground maintenance.)
2. Run 'git pack-objects' on a batch of loose objects. These objects are grouped by scanning the loose object directories in lexicographic order until listing all loose objects -or- reaching 50,000 objects. This is more than enough if the loose objects are created only by a user doing normal development. We noticed users with _millions_ of loose objects because VFS for Git downloads blobs on-demand when a file read operation requires populating a virtual file. This has potential of happening in partial clones if someone runs 'git grep' or otherwise evades the batch-download feature for requesting promisor objects.
This step is based on a similar step in Scalar [1] and VFS for Git.
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/LooseObjectsStep.cs
--- Documentation/git-maintenance.txt | 11 +++ builtin/gc.c | 107 +++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 35 ++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-)
Documentation/git-maintenance.txt +11 -0
@@ -73,6 +73,17 @@ gc:: It can also be disruptive in some situations, as it deletes stale data. +loose-objects:: + The `loose-objects` job cleans up loose objects and places them into + pack-files. In order to prevent race conditions with concurrent Git + commands, it follows a two-step process. First, it deletes any loose + objects that already exist in a pack-file; concurrent Git processes + will examine the pack-file for the object data instead of the loose + object. Second, it creates a new pack-file (starting with "loose-") + containing a batch of loose objects. The batch size is limited to 50 + thousand objects to prevent the job from taking too long on a + repository with many loose objects. + OPTIONS ------- --auto::
builtin/gc.c +106 -1
125 lines not on this page · raw message
t/t7900-maintenance.sh +35 -0
@@ -68,4 +68,39 @@ test_expect_success 'fetch multiple remotes' ' git log hidden/remote2/two ' +test_expect_success 'loose-objects task' ' + # Repack everything so we know the state of the object dir + git repack -adk && + + # Hack to stop maintenance from running during "git commit" + echo in use >.git/objects/maintenance.lock && + test_commit create-loose-object && + rm .git/objects/maintenance.lock && + + ls .git/objects >obj-dir-before && + test_file_not_empty obj-dir-before && + ls .git/objects/pack/*.pack >packs-before && + test_line_count = 1 packs-before && + + # The first run creates a pack-file + # but does not delete loose objects. + git maintenance run --task=loose-objects && + ls .git/objects >obj-dir-between && + test_cmp obj-dir-before obj-dir-between && + ls .git/objects/pack/*.pack >packs-between && + test_line_count = 2 packs-between && + + # The second run deletes loose objects + # but does not create a pack-file. + git maintenance run --task=loose-objects && + ls .git/objects >obj-dir-after && + cat >expect <<-\EOF && + info + pack + EOF + test_cmp expect obj-dir-after && + ls .git/objects/pack/*.pack >packs-after && + test_cmp packs-between packs-after +' + test_done
Signature · 1 line
gitgitgadget
PATCH 14/21 maintenance: add pack-files task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The previous change cleaned up loose objects using the 'loose-objects' that can be run safely in the background. Add a similar job that performs similar cleanups for pack-files.
One issue with running 'git repack' is that it is designed to repack all pack-files into a single pack-file. While this is the most space-efficient way to store object data, it is not time or memory efficient. This becomes extremely important if the repo is so large that a user struggles to store two copies of the pack on their disk.
Instead, perform an "incremental" repack by collecting a few small pack-files into a new pack-file. The multi-pack-index facilitates this process ever since 'git multi-pack-index expire' was added in 19575c7 (multi-pack-index: implement 'expire' subcommand, 2019-06-10) and 'git multi-pack-index repack' was added in ce1e4a1 (midx: implement midx_repack(), 2019-06-10).
The 'pack-files' job runs the following steps:
1. 'git multi-pack-index write' creates a multi-pack-index file if one did not exist, and otherwise will update the multi-pack-index with any new pack-files that appeared since the last write. This is particularly relevant with the background fetch job.
When the multi-pack-index sees two copies of the same object, it stores the offset data into the newer pack-file. This means that some old pack-files could become "unreferenced" which I will use to mean "a pack-file that is in the pack-file list of the multi-pack-index but none of the objects in the multi-pack-index reference a location inside that pack-file."
2. 'git multi-pack-index expire' deletes any unreferenced pack-files and updaes the multi-pack-index to drop those pack-files from the list. This is safe to do as concurrent Git processes will see the multi-pack-index and not open those packs when looking for object contents. (Similar to the 'loose-objects' job, there are some Git commands that open pack-files regardless of the multi-pack-index, but they are rarely used. Further, a user that self-selects to use background operations would likely refrain from using those commands.)
3. 'git multi-pack-index repack --bacth-size=<size>' collects a set of pack-files that are listed in the multi-pack-index and creates a new pack-file containing the objects whose offsets are listed by the multi-pack-index to be in those objects. The set of pack- files is selected greedily by sorting the pack-files by modified time and adding a pack-file to the set if its "expected size" is smaller than the batch size until the total expected size of the selected pack-files is at least the batch size. The "expected size" is calculated by taking the size of the pack-file divided by the number of objects in the pack-file and multiplied by the number of objects from the multi-pack-index with offset in that pack-file. The expected size approximats how much data from that pack-file will contribute to the resulting pack-file size. The intention is that the resulting pack-file will be close in size to the provided batch size.
The next run of the pack-files job will delete these repacked pack-files during the 'expire' step.
In this version, the batch size is set to "0" which ignores the size restrictions when selecting the pack-files. It instead selects all pack-files and repacks all packed objects into a single pack-file. This will be updated in the next change, but it requires doing some calculations that are better isolated to a separate change.
Each of the above steps update the multi-pack-index file. After each step, we verify the new multi-pack-index. If the new multi-pack-index is corrupt, then delete the multi-pack-index, rewrite it from scratch, and stop doing the later steps of the job. This is intended to be an extra-safe check without leaving a repo with many pack-files without a multi-pack-index.
These steps are based on a similar background maintenance step in Scalar (and VFS for Git) [1]. This was incredibly effective for users of the Windows OS repository. After using the same VFS for Git repository for over a year, some users had _thousands_ of pack-files that combined to up to 250 GB of data. We noticed a few users were running into the open file descriptor limits (due in part to a bug in the multi-pack-index fixed by af96fe3 (midx: add packs to packed_git linked list, 2019-04-29).
These pack-files were mostly small since they contained the commits and trees that were pushed to the origin in a given hour. The GVFS protocol includes a "prefetch" step that asks for pre-computed pack- files containing commits and trees by timestamp. These pack-files were grouped into "daily" pack-files once a day for up to 30 days. If a user did not request prefetch packs for over 30 days, then they would get the entire history of commits and trees in a new, large pack-file. This led to a large number of pack-files that had poor delta compression.
By running this pack-file maintenance step once per day, these repos with thousands of packs spanning 200+ GB dropped to dozens of pack- files spanning 30-50 GB. This was done all without removing objects from the system and using a constant batch size of two gigabytes. Once the work was done to reduce the pack-files to small sizes, the batch size of two gigabytes means that not every run triggers a repack operation, so the following run will not expire a pack-file. This has kept these repos in a "clean" state.
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/PackfileMaintenanceStep.cs
--- Documentation/git-maintenance.txt | 15 ++++ builtin/gc.c | 124 +++++++++++++++++++++++++++++- midx.c | 2 +- midx.h | 1 + t/t7900-maintenance.sh | 37 +++++++++ 5 files changed, 177 insertions(+), 2 deletions(-)
Documentation/git-maintenance.txt +15 -0
@@ -84,6 +84,21 @@ loose-objects:: thousand objects to prevent the job from taking too long on a repository with many loose objects. +pack-files:: + The `pack-files` job incrementally repacks the object directory + using the `multi-pack-index` feature. In order to prevent race + conditions with concurrent Git commands, it follows a two-step + process. First, it deletes any pack-files included in the + `multi-pack-index` where none of the objects in the + `multi-pack-index` reference those pack-files; this only happens + if all objects in the pack-file are also stored in a newer + pack-file. Second, it selects a group of pack-files whose "expected + size" is below the batch size until the group has total expected + size at least the batch size; see the `--batch-size` option for + the `repack` subcommand in linkgit:git-multi-pack-index[1]. The + default batch-size is zero, which is a special case that attempts + to repack all pack-files into a single pack-file. + OPTIONS ------- --auto::
builtin/gc.c +123 -1
148 lines not on this page · raw message
midx.c +1 -1
@@ -36,7 +36,7 @@#define PACK_EXPIRED UINT_MAX -static char *get_midx_filename (const char *object_dir) +char *get_midx_filename (const char *object_dir) {return xstrfmt ("%s/pack/multi-pack-index" , object_dir); }
midx.h +1 -0
@@ -37,6 +37,7 @@ struct multi_pack_index {#define MIDX_PROGRESS (1 << 0) +char *get_midx_filename (const char *object_dir);struct multi_pack_index *load_multi_pack_index (const char *object_dir,int local);int prepare_midx_pack (struct repository *r,struct multi_pack_index *m,uint32_t pack_int_id);int bsearch_midx (const struct object_id *oid,struct multi_pack_index *m,uint32_t *result);
t/t7900-maintenance.sh +37 -0
@@ -103,4 +103,41 @@ test_expect_success 'loose-objects task' ' test_cmp packs-between packs-after ' +test_expect_success 'pack-files task' ' + packDir=.git/objects/pack && + for i in $(test_seq 1 5) + do + test_commit $i || return 1 + done && + + # Create three disjoint pack-files with size BIG, small, small. + echo HEAD~2 | git pack-objects --revs $packDir/test-1 && + test_tick && + git pack-objects --revs $packDir/test-2 <<-\EOF && + HEAD~1 + ^HEAD~2 + EOF + test_tick && + git pack-objects --revs $packDir/test-3 <<-\EOF && + HEAD + ^HEAD~1 + EOF + rm -f $packDir/pack-* && + rm -f $packDir/loose-* && + ls $packDir/*.pack >packs-before && + test_line_count = 3 packs-before && + + # the job repacks the two into a new pack, but does not + # delete the old ones. + git maintenance run --task=pack-files && + ls $packDir/*.pack >packs-between && + test_line_count = 4 packs-between && + + # the job deletes the two old packs, and does not write + # a new one because only one pack remains. + git maintenance run --task=pack-files && + ls .git/objects/pack/*.pack >packs-after && + test_line_count = 1 packs-after +' + test_done
Signature · 1 line
gitgitgadget
PATCH 15/21 maintenance: auto-size pack-files batch
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
When repacking during the 'pack-files' job, we use the --batch-size option in 'git multi-pack-index repack'. The initial setting used --batch-size=0 to repack everything into a single pack-file. This is not sustaintable for a large repository. The amount of work required is also likely to use too many system resources for a background job.
Update the 'pack-files' maintenance task by dynamically computing a --batch-size option based on the current pack-file structure.
The dynamic default size is computed with this idea in mind for a client repository that was cloned from a very large remote: there is likely one "big" pack-file that was created at clone time. Thus, do not try repacking it as it is likely packed efficiently by the server.
Instead, we select the second-largest pack-file, and create a batch size that is one larger than that pack-file. If there are three or more pack-files, then this guarantees that at least two will be combined into a new pack-file.
Of course, this means that the second-largest pack-file size is likely to grow over time and may eventually surpass the initially-cloned pack-file. Recall that the pack-file batch is selected in a greedy
manner: the packs are considered from oldest to newest and are selected if they have size smaller than the batch size until the total selected size is larger than the batch size. Thus, that oldest "clone" pack will be first to repack after the new data creates a pack larger than that.
We also want to place some limits on how large these pack-files become, in order to bound the amount of time spent repacking. A maximum batch-size of two gigabytes means that large repositories will never be packed into a single pack-file using this job, but also that repack is rather expensive. This is a trade-off that is valuable to have if the maintenance is being run automatically or in the background. Users who truly want to optimize for space and performance (and are willing to pay the upfront cost of a full repack) can use the 'gc' task to do so.
--- builtin/gc.c | 47 +++++++++++++++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 5 +++-- 2 files changed, 49 insertions(+), 3 deletions(-)
builtin/gc.c +46 -1
@@ -1032,20 +1032,65 @@ static int multi_pack_index_expire(struct repository *r)return result; } +#define TWO_GIGABYTES (2147483647) +#define UNSET_BATCH_SIZE ((unsigned long)-1) + +static off_t get_auto_pack_size (struct repository *r) +{ +/* +* The "auto" value is special: we optimize for +* one large pack-file (i.e. from a clone) and +* expect the rest to be small and they can be +* repacked quickly. +* +* The strategy we select here is to select a +* size that is one more than the second largest +* pack-file. This ensures that we will repack +* at least two packs if there are three or more +* packs. +*/ +off_t max_size =0 ; +off_t second_largest_size =0 ; +off_t result_size; +struct packed_git *p; + +reprepare_packed_git (r); +for (p =get_all_packs (r); p; p = p->next) { +if (p->pack_size > max_size) { + second_largest_size = max_size; + max_size = p->pack_size; + }else if (p->pack_size > second_largest_size) + second_largest_size = p->pack_size; + } + + result_size = second_largest_size +1 ; + +/* But limit ourselves to a batch size of 2g */ +if (result_size >TWO_GIGABYTES ) + result_size =TWO_GIGABYTES ; + +return result_size; +} +static int multi_pack_index_repack (struct repository *r) {int result;struct argv_array cmd =ARGV_ARRAY_INIT ; +struct strbuf batch_arg =STRBUF_INIT ; +argv_array_pushl (&cmd,"-C" , r->worktree,"multi-pack-index" ,"repack" ,NULL );if (opts.quiet)argv_array_push (&cmd,"--no-progress" ); -argv_array_push (&cmd,"--batch-size=0" ); +strbuf_addf (&batch_arg,"--batch-size=%" PRIuMAX, + (uintmax_t )get_auto_pack_size (r)); +argv_array_push (&cmd, batch_arg.buf);close_object_store (r->objects); result =run_command_v_opt (cmd.argv,RUN_GIT_CMD ); +strbuf_release (&batch_arg);if (result &&multi_pack_index_verify (r)) {warning (_ ("multi-pack-index verify failed after repack" ));
t/t7900-maintenance.sh +3 -2
@@ -134,10 +134,11 @@ test_expect_success 'pack-files task' ' test_line_count = 4 packs-between && # the job deletes the two old packs, and does not write - # a new one because only one pack remains. + # a new one because the batch size is not high enough to + # pack the largest pack-file. git maintenance run --task=pack-files && ls .git/objects/pack/*.pack >packs-after && - test_line_count = 1 packs-after + test_line_count = 2 packs-after ' test_done
Signature · 1 line
gitgitgadget
PATCH 16/21 maintenance: create maintenance.<task>.enabled config
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Currently, a normal run of "git maintenance run" will only run the 'gc' task, as it is the only one enabled. This is mostly for backwards- compatible reasons since "git maintenance run --auto" commands replaced previous "git gc --auto" commands after some Git processes. Users could manually run specific maintenance tasks by calling "git maintenance run --task=<task>" directly.
Allow users to customize which steps are run automatically using config. The 'maintenance.<task>.enabled' option then can turn on these other tasks (or turn off the 'gc' task).
--- Documentation/config.txt | 2 ++ Documentation/config/maintenance.txt | 4 ++++ Documentation/git-maintenance.txt | 6 +++++- builtin/gc.c | 15 +++++++++++++-- t/t7900-maintenance.sh | 12 ++++++++++++ 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 Documentation/config/maintenance.txt
Documentation/config.txt +2 -0
@@ -396,6 +396,8 @@ include::config/mailinfo.txt[] include::config/mailmap.txt[] +include::config/maintenance.txt[] + include::config/man.txt[] include::config/merge.txt[]
Documentation/config/maintenance.txt new file +4 -0
@@ -0,0 +1,4 @@ +maintenance.<task>.enabled:: + This boolean config option controls whether the maintenance task + with name `<task>` is run when no `--task` option is specified. + By default, only `maintenance.gc.enabled` is true.
Documentation/git-maintenance.txt +5 -1
@@ -30,7 +30,11 @@ SUBCOMMANDS ----------- run:: - Run one or more maintenance tasks. + Run one or more maintenance tasks. If one or more `--task` options + are specified, then those tasks are run in that order. Otherwise, + the tasks are determined by which `maintenance.<task>.enabled` + config options are true. By default, only `maintenance.gc.enabled` + is true. TASKS -----
builtin/gc.c +13 -2
@@ -1208,9 +1208,10 @@ static int maintenance_run(struct repository *r)return result; } -static void initialize_tasks (void ) +static void initialize_tasks (struct repository *r) {int i; +struct strbuf config_name =STRBUF_INIT ; num_tasks =0 ;for (i =0 ; i <MAX_NUM_TASKS ; i++)
@@ -1240,10 +1241,20 @@ static void initialize_tasks(void)hashmap_init (&task_map, task_entry_cmp,NULL ,MAX_NUM_TASKS );for (i =0 ; i < num_tasks; i++) { +int config_value; +hashmap_entry_init (&tasks[i]->ent,strihash (tasks[i]->name));hashmap_add (&task_map, &tasks[i]->ent); + +strbuf_setlen (&config_name,0 ); +strbuf_addf (&config_name,"maintenance.%s.enabled" , tasks[i]->name); + +if (!repo_config_get_bool (r, config_name.buf, &config_value)) + tasks[i]->enabled = config_value; } + +strbuf_release (&config_name); } static int task_option_parse(const struct option *opt,
@@ -1304,7 +1315,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix) builtin_maintenance_options); opts.quiet = !isatty (2 ); -initialize_tasks (); +initialize_tasks (r); argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
t/t7900-maintenance.sh +12 -0
@@ -21,6 +21,18 @@ test_expect_success 'run [--auto|--quiet]' ' grep ",\"gc\",\"--quiet\"" run-quiet.txt ' +test_expect_success 'maintenance.<task>.enabled' ' + git config maintenance.gc.enabled false && + git config maintenance.commit-graph.enabled true && + git config maintenance.loose-objects.enabled true && + GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run && + ! grep ",\"fetch\"" run-config.txt && + ! grep ",\"gc\"" run-config.txt && + ! grep ",\"multi-pack-index\"" run-config.txt && + grep ",\"commit-graph\"" run-config.txt && + grep ",\"prune-packed\"" run-config.txt +' + test_expect_success 'run --task=<task>' ' GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" git maintenance run --task=commit-graph && GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" git maintenance run --task=gc &&
Signature · 1 line
gitgitgadget
PATCH 17/21 maintenance: use pointers to check --auto
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The 'git maintenance run' command has an '--auto' option. This is used by other Git commands such as 'git commit' or 'git fetch' to check if maintenance should be run after adding data to the repository.
Previously, this --auto option was only used to add the argument to the 'git gc' command as part of the 'gc' task. We will be expanding the other tasks to perform a check to see if they should do work as part of the --auto flag, when they are enabled by config.
First, update the 'gc' task to perform the auto check inside the maintenance process. This prevents running an extra 'git gc --auto' command when not needed. It also shows a model for other tasks.
Second, use the 'auto_condition' function pointer as a signal for whether we enable the maintenance task under '--auto'. For instance, we do not want to enable the 'fetch' task in '--auto' mode, so that function pointer will remain NULL.
Now that we are not automatically calling 'git gc', a test in t5514-fetch-multiple.sh must be changed to watch for 'git maintenance' instead.
We continue to pass the '--auto' option to the 'git gc' command when necessary, because of the gc.autoDetach config option changes behavior. Likely, we will want to absorb the daemonizing behavior implied by gc.autoDetach as a maintenance.autoDetach config option.
--- builtin/gc.c | 15 +++++++++++++++ t/t5514-fetch-multiple.sh | 2 +- t/t7900-maintenance.sh | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-)
builtin/gc.c +15 -0
@@ -1132,10 +1132,18 @@ static int maintenance_task_pack_files(struct repository *r)typedef int maintenance_task_fn (struct repository *r); +/* +* An auto condition function returns 1 if the task should run +* and 0 if the task should NOT run. See needs_to_gc() for an +* example. +*/ +typedef int maintenance_auto_fn (struct repository *r); +struct maintenance_task {struct hashmap_entry ent;const char *name;maintenance_task_fn *fn; +maintenance_auto_fn *auto_condition;int task_order;unsigned enabled:1 , selected:1 ;
@@ -1201,6 +1209,11 @@ static int maintenance_run(struct repository *r)if (!opts.tasks_selected && !tasks[i]->enabled)continue ; +if (opts.auto_flag && + (!tasks[i]->auto_condition || + !tasks[i]->auto_condition (r))) +continue ; + result = tasks[i]->fn (r); }
@@ -1231,6 +1244,7 @@ static void initialize_tasks(struct repository *r) tasks[num_tasks]->name ="gc" ; tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->auto_condition = need_to_gc; tasks[num_tasks]->enabled =1 ; num_tasks++;
@@ -1315,6 +1329,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix) builtin_maintenance_options); opts.quiet = !isatty (2 ); +gc_config (r);initialize_tasks (r); argc = parse_options(argc, argv, prefix,
t/t5514-fetch-multiple.sh +1 -1
@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' ' GIT_TRACE=1 git fetch --multiple one two 2>trace && git branch -r > output && test_cmp ../expect output && - grep "built-in: git gc" trace >gc && + grep "built-in: git maintenance" trace >gc && test_line_count = 1 gc ) '
t/t7900-maintenance.sh +1 -1
@@ -17,7 +17,7 @@ test_expect_success 'run [--auto|--quiet]' ' GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && GIT_TRACE2_EVENT="$(pwd)/run-quiet.txt" git maintenance run --quiet && grep ",\"gc\"]" run-no-auto.txt && - grep ",\"gc\",\"--auto\"" run-auto.txt && + ! grep ",\"gc\",\"--auto\"" run-auto.txt && grep ",\"gc\",\"--quiet\"" run-quiet.txt '
Signature · 1 line
gitgitgadget
PATCH 18/21 maintenance: add auto condition for commit-graph task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Instead of writing a new commit-graph in every 'git maintenance run --auto' process (when maintenance.commit-graph.enalbed is configured to be true), only write when there are "enough" commits not in a commit-graph file.
This count is controlled by the maintenance.commit-graph.auto config option.
To compute the count, use a depth-first search starting at each ref, and leaving markers using the PARENT1 flag. If this count reaches the limit, then terminate early and start the task. Otherwise, this operation will peel every ref and parse the commit it points to. If these are all in the commit-graph, then this is typically a very fast operation. Users with many refs might feel a slow-down, and hence could consider updating their limit to be very small. A negative value will force the step to run every time.
--- Documentation/config/maintenance.txt | 10 ++++ builtin/gc.c | 76 ++++++++++++++++++++++++++++ object.h | 1 + 3 files changed, 87 insertions(+)
Documentation/config/maintenance.txt +10 -0
@@ -2,3 +2,13 @@ maintenance.<task>.enabled:: This boolean config option controls whether the maintenance task with name `<task>` is run when no `--task` option is specified. By default, only `maintenance.gc.enabled` is true. + +maintenance.commit-graph.auto:: + This integer config option controls how often the `commit-graph` task + should be run as part of `git maintenance run --auto`. If zero, then + the `commit-graph` task will not run with the `--auto` option. A + negative value will force the task to run every time. Otherwise, a + positive value implies the command should run when the number of + reachable commits that are not in the commit-graph file is at least + the value of `maintenance.commit-graph.auto`. The default value is + 100.
builtin/gc.c +76 -0
94 lines not on this page · raw message
object.h +1 -0
@@ -73,6 +73,7 @@ struct object_array {* list-objects-filter.c: 21 * builtin/fsck.c: 0--3 * builtin/index-pack.c: 2021 +* builtin/maintenance.c: 16 * builtin/pack-objects.c: 20 * builtin/reflog.c: 10--12 * builtin/show-branch.c: 0-------------------------------------------26
Signature · 1 line
gitgitgadget
PATCH 19/21 maintenance: create auto condition for loose-objects
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The loose-objects task deletes loose objects that already exist in a pack-file, then place the remaining loose objects into a new pack-file. If this step runs all the time, then we risk creating pack-files with very few objects with every 'git commit' process. To prevent overwhelming the packs directory with small pack-files, place a minimum number of objects to justify the task.
The 'maintenance.loose-objects.auto' config option specifies a minimum number of loose objects to justify the task to run under the '--auto' option. This defaults to 100 loose objects. Setting the value to zero will prevent the step from running under '--auto' while a negative value will force it to run every time.
--- Documentation/config/maintenance.txt | 9 +++++++++ builtin/gc.c | 30 ++++++++++++++++++++++++++++ t/t7900-maintenance.sh | 25 +++++++++++++++++++++++ 3 files changed, 64 insertions(+)
Documentation/config/maintenance.txt +9 -0
@@ -12,3 +12,12 @@ maintenance.commit-graph.auto:: reachable commits that are not in the commit-graph file is at least the value of `maintenance.commit-graph.auto`. The default value is 100. + +maintenance.loose-objects.auto:: + This integer config option controls how often the `loose-objects` task + should be run as part of `git maintenance run --auto`. If zero, then + the `loose-objects` task will not run with the `--auto` option. A + negative value will force the task to run every time. Otherwise, a + positive value implies the command should run when the number of + loose objects is at least the value of `maintenance.loose-objects.auto`. + The default value is 100.
builtin/gc.c +30 -0
@@ -960,6 +960,35 @@ struct write_loose_object_data {int batch_size; }; +static int loose_object_auto_limit =100 ; + +static int loose_object_count (const struct object_id *oid, +const char *path, +void *data) +{ +int *count = (int *)data; +if (++(*count) >= loose_object_auto_limit) +return 1 ; +return 0 ; +} + +static int loose_object_auto_condition (struct repository *r) +{ +int count =0 ; + +repo_config_get_int (r,"maintenance.loose-objects.auto" , + &loose_object_auto_limit); + +if (!loose_object_auto_limit) +return 0 ; +if (loose_object_auto_limit <0 ) +return 1 ; + +return for_each_loose_file_in_objdir (r->objects->odb->path, + loose_object_count, +NULL ,NULL , &count); +} +static int loose_object_exists (const struct object_id *oid,const char *path,void *data)
@@ -1311,6 +1340,7 @@ static void initialize_tasks(struct repository *r) tasks[num_tasks]->name ="loose-objects" ; tasks[num_tasks]->fn = maintenance_task_loose_objects; + tasks[num_tasks]->auto_condition = loose_object_auto_condition; num_tasks++; tasks[num_tasks]->name ="pack-files" ;
t/t7900-maintenance.sh +25 -0
@@ -115,6 +115,31 @@ test_expect_success 'loose-objects task' ' test_cmp packs-between packs-after ' +test_expect_success 'maintenance.loose-objects.auto' ' + git repack -adk && + GIT_TRACE2_EVENT="$(pwd)/trace-lo1.txt" \ + git -c maintenance.loose-objects.auto=1 maintenance \ + run --auto --task=loose-objects && + ! grep "\"prune-packed\"" trace-lo1.txt && + for i in 1 2 + do + printf data-A-$i | git hash-object -t blob --stdin -w && + GIT_TRACE2_EVENT="$(pwd)/trace-loA-$i" \ + git -c maintenance.loose-objects.auto=2 \ + maintenance run --auto --task=loose-objects && + ! grep "\"prune-packed\"" trace-loA-$i && + printf data-B-$i | git hash-object -t blob --stdin -w && + GIT_TRACE2_EVENT="$(pwd)/trace-loB-$i" \ + git -c maintenance.loose-objects.auto=2 \ + maintenance run --auto --task=loose-objects && + grep "\"prune-packed\"" trace-loB-$i && + GIT_TRACE2_EVENT="$(pwd)/trace-loC-$i" \ + git -c maintenance.loose-objects.auto=2 \ + maintenance run --auto --task=loose-objects && + grep "\"prune-packed\"" trace-loC-$i || return 1 + done +' + test_expect_success 'pack-files task' ' packDir=.git/objects/pack && for i in $(test_seq 1 5)
Signature · 1 line
gitgitgadget
PATCH 20/21 maintenance: add pack-files auto condition
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The pack-files task updates the multi-pack-index by deleting pack-files that have been replaced with new packs, then repacking a batch of small pack-files into a larger pack-file. This incremental repack is faster than rewriting all object data, but is slower than some other maintenance activities.
The 'maintenance.pack-files.auto' config option specifies how many pack-files should exist outside of the multi-pack-index before running the step. These pack-files could be created by 'git fetch' commands or by the loose-objects task. The default value is 10.
Setting the option to zero disables the task with the '--auto' option, and a negative value makes the task run every time.
--- Documentation/config/maintenance.txt | 9 ++++++++ builtin/gc.c | 31 ++++++++++++++++++++++++++++ t/t7900-maintenance.sh | 30 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+)
Documentation/config/maintenance.txt +9 -0
@@ -21,3 +21,12 @@ maintenance.loose-objects.auto:: positive value implies the command should run when the number of loose objects is at least the value of `maintenance.loose-objects.auto`. The default value is 100. + +maintenance.pack-files.auto:: + This integer config option controls how often the `pack-files` task + should be run as part of `git maintenance run --auto`. If zero, then + the `pack-files` task will not run with the `--auto` option. A + negative value will force the task to run every time. Otherwise, a + positive value implies the command should run when the number of + pack-files not in the multi-pack-index is at least the value of + `maintenance.pack-files.auto`. The default value is 10.
builtin/gc.c +31 -0
@@ -31,6 +31,7 @@#include "remote.h" #include "midx.h" #include "refs.h" +#include "object-store.h" #define FAILED_RUN "failed to run %s"
@@ -1072,6 +1073,35 @@ static int maintenance_task_loose_objects(struct repository *r)return prune_packed (r) ||pack_loose (r); } +static int pack_files_auto_condition (struct repository *r) +{ +struct packed_git *p; +int enabled; +int pack_files_auto_limit =10 ; +int count =0 ; + +if (repo_config_get_bool (r,"core.multiPackIndex" , &enabled) || + !enabled) +return 0 ; + +repo_config_get_int (r,"maintenance.pack-files.auto" , + &pack_files_auto_limit); + +if (!pack_files_auto_limit) +return 0 ; +if (pack_files_auto_limit <0 ) +return 1 ; + +for (p =get_packed_git (r); + count < pack_files_auto_limit && p; + p = p->next) { +if (!p->multi_pack_index) + count++; + } + +return count >= pack_files_auto_limit; +} +static int multi_pack_index_write (struct repository *r) {int result;
@@ -1345,6 +1375,7 @@ static void initialize_tasks(struct repository *r) tasks[num_tasks]->name ="pack-files" ; tasks[num_tasks]->fn = maintenance_task_pack_files; + tasks[num_tasks]->auto_condition = pack_files_auto_condition; num_tasks++; tasks[num_tasks]->name ="gc" ;
t/t7900-maintenance.sh +30 -0
@@ -178,4 +178,34 @@ test_expect_success 'pack-files task' ' test_line_count = 2 packs-after ' +test_expect_success 'maintenance.pack-files.auto' ' + git repack -adk && + git config core.multiPackIndex true && + git multi-pack-index write && + GIT_TRACE2_EVENT=1 git -c maintenance.pack-files.auto=1 maintenance \ + run --auto --task=pack-files >out && + ! grep "\"multi-pack-index\"" out && + for i in 1 2 + do + test_commit A-$i && + git pack-objects --revs .git/objects/pack/pack <<-\EOF && + HEAD + ^HEAD~1 + EOF + GIT_TRACE2_EVENT=$(pwd)/trace-A-$i git \ + -c maintenance.pack-files.auto=2 \ + maintenance run --auto --task=pack-files && + ! grep "\"multi-pack-index\"" trace-A-$i && + test_commit B-$i && + git pack-objects --revs .git/objects/pack/pack <<-\EOF && + HEAD + ^HEAD~1 + EOF + GIT_TRACE2_EVENT=$(pwd)/trace-B-$i git \ + -c maintenance.pack-files.auto=2 \ + maintenance run --auto --task=pack-files >out && + grep "\"multi-pack-index\"" trace-B-$i >/dev/null || return 1 + done +' + test_done
Signature · 1 line
gitgitgadget
PATCH 21/21 midx: use start_delayed_progress()
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Now that the multi-pack-index may be written as part of auto maintenance at the end of a command, reduce the progress output when the operations are quick. Use start_delayed_progress() instead of start_progress().
Update t5319-multi-pack-index.sh to use GIT_PROGRESS_DELAY=0 now that the progress indicators are conditional.
--- midx.c | 10 +++++----- t/t5319-multi-pack-index.sh | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-)
midx.c +5 -5
@@ -837,7 +837,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * packs.pack_paths_checked =0 ;if (flags &MIDX_PROGRESS ) - packs.progress =start_progress (_ ("Adding packfiles to multi-pack-index" ),0 ); + packs.progress =start_delayed_progress (_ ("Adding packfiles to multi-pack-index" ),0 );else packs.progress =NULL ;
@@ -974,7 +974,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * }if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Writing chunks to multi-pack-index" ), + progress =start_delayed_progress (_ ("Writing chunks to multi-pack-index" ), num_chunks);for (i =0 ; i < num_chunks; i++) {if (written != chunk_offsets[i])
@@ -1109,7 +1109,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flagreturn 0 ;if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Looking for referenced packfiles" ), + progress =start_delayed_progress (_ ("Looking for referenced packfiles" ), m->num_packs);for (i =0 ; i < m->num_packs; i++) {if (prepare_midx_pack (r, m, i))
@@ -1230,7 +1230,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla count =xcalloc (m->num_packs,sizeof (uint32_t ));if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Counting referenced objects" ), + progress =start_delayed_progress (_ ("Counting referenced objects" ), m->num_objects);for (i =0 ; i < m->num_objects; i++) {int pack_int_id =nth_midxed_pack_int_id (m, i);
@@ -1240,7 +1240,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flastop_progress (&progress);if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Finding and deleting unreferenced packfiles" ), + progress =start_delayed_progress (_ ("Finding and deleting unreferenced packfiles" ), m->num_packs);for (i =0 ; i < m->num_packs; i++) {char *pack_name;
t/t5319-multi-pack-index.sh +7 -7
@@ -172,12 +172,12 @@ test_expect_success 'write progress off for redirected stderr' ' ' test_expect_success 'write force progress on for stderr' ' - git multi-pack-index --object-dir=$objdir --progress write 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress write 2>err && test_file_not_empty err ' test_expect_success 'write with the --no-progress option' ' - git multi-pack-index --object-dir=$objdir --no-progress write 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress write 2>err && test_line_count = 0 err '
@@ -334,17 +334,17 @@ test_expect_success 'git-fsck incorrect offset' ' ' test_expect_success 'repack progress off for redirected stderr' ' - git multi-pack-index --object-dir=$objdir repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack 2>err && test_line_count = 0 err ' test_expect_success 'repack force progress on for stderr' ' - git multi-pack-index --object-dir=$objdir --progress repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress repack 2>err && test_file_not_empty err ' test_expect_success 'repack with the --no-progress option' ' - git multi-pack-index --object-dir=$objdir --no-progress repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress repack 2>err && test_line_count = 0 err '
@@ -488,7 +488,7 @@ test_expect_success 'expire progress off for redirected stderr' ' test_expect_success 'expire force progress on for stderr' ' ( cd dup && - git multi-pack-index --progress expire 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --progress expire 2>err && test_file_not_empty err ) '
@@ -496,7 +496,7 @@ test_expect_success 'expire force progress on for stderr' ' test_expect_success 'expire with the --no-progress option' ' ( cd dup && - git multi-pack-index --no-progress expire 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --no-progress expire 2>err && test_line_count = 0 err ) '
Signature · 1 line
gitgitgadget
Quoted 59 lines · Derrick Stolee via GitGitGadget
This is a second attempt at redesigning Git's repository maintenance patterns. The first attempt [1] included a way to run jobs in the background using a long-lived process; that idea was rejected and is not included in this series. A future series will use the OS to handle scheduling tasks.
[1]
https://lore.kernel.org/git/[email protected]/As mentioned before, git gc already plays the role of maintaining Git repositories. It has accumulated several smaller pieces in its long history, including:
1. Repacking all reachable objects into one pack-file (and deleting unreachable objects).
2. Packing refs.
3. Expiring reflogs.
4. Clearing rerere logs.
5. Updating the commit-graph file.While expiring reflogs, clearing rererelogs, and deleting unreachable objects are suitable under the guise of "garbage collection", packing refs and updating the commit-graph file are not as obviously fitting. Further, these operations are "all or nothing" in that they rewrite almost all repository data, which does not perform well at extremely large scales. These operations can also be disruptive to foreground Git commands when git gc --auto triggers during routine use.
This series does not intend to change what git gc does, but instead create new choices for automatic maintenance activities, of which git gc remains the only one enabled by default.
The new maintenance tasks are:
* 'commit-graph' : write and verify a single layer of an incremental commit-graph.
* 'loose-objects' : prune packed loose objects, then create a new pack from a batch of loose objects.
* 'pack-files' : expire redundant packs from the multi-pack-index, then repack using the multi-pack-index's incremental repack strategy.
* 'fetch' : fetch from each remote, storing the refs in 'refs/hidden//'.These tasks are all disabled by default, but can be enabled with config options or run explicitly using "git maintenance run --task=". There are additional config options to allow customizing the conditions for which the tasks run during the '--auto' option. ('fetch' will never run with the '--auto' option.)
Because 'gc' is implemented as a maintenance task, the most dramatic change
of this series is to convert the 'git gc --auto' calls into 'git maintenance run --auto' calls at the end of some Git commands. By default, the only change is that 'git gc --auto' will be run below an additional 'git maintenance' process.The 'git maintenance' builtin has a 'run' subcommand so it can be extended later with subcommands that manage background maintenance, such as 'start', 'stop', 'pause', or 'schedule'. These are not the subject of this series, as it is important to focus on the maintenance activities themselves.
Quoted 5 lines
An expert user could set up scheduled background maintenance themselves with the current series. I have the following crontab data set up to run maintenance on an hourly basis:
0 * * * * git -C /<path-to-repo> maintenance run --no-quiet >>/<path-to-repo>/.git/maintenance.log
One thing I wonder about - now I have to go and make a new crontab (which is easy) or Task Scheduler task (which is a pain) for every repo, right?
Is it infeasible to ask for 'git maintenance' to learn something like '--on /<path-to-repo> --on /<path-to-second-repo>'? Or better yet, some config like "maintenance.targetRepo = /<path-to-repo>"?
Quoted 12 lines
My config includes all tasks except the 'gc' task. The hourly run is over-aggressive, but is sufficient for testing. I'll replace it with daily when I feel satisfied.
Hopefully this direction is seen as a positive one. My goal was to add more options for expert users, along with the flexibility to create background maintenance via the OS in a later series.
OUTLINE
=======
Quoted 6 lines
Patches 1-4 remove some references to
the_repositoryinbuiltin/gc.cbefore we start depending on code in that builtin.Patches 5-7 create the 'git maintenance run' builtin and subcommand as a simple shim over 'git gc' and replaces calls to 'git gc --auto' from other commands.
For me, I'd prefer to see 'git maintenance run' get bigger and 'git gc --auto' get smaller or disappear. Is there a plan towards that direction, or is that out of scope for 'git maintenance run'? Similar examples I can think of include 'git annotate' and 'git pickaxe'.
Quoted 10 lines
Patches 8-15 create new maintenance tasks. These are the same tasks sent in the previous RFC.
Patches 16-21 create more customization through config and perform other polish items.
FUTURE WORK
===========
Quoted 7 lines
* Add 'start', 'stop', and 'schedule' subcommands to initialize the commands run in the background.
* Split the 'gc' builtin into smaller maintenance tasks that are enabled by default, but might have different '--auto' conditions and more config options.
Like I mentioned above, for me, I'd rather just see the 'gc' builtin go away :)
Quoted 71 lines
* Replace config like 'gc.writeCommitGraph' and 'fetch.writeCommitGraph' with use of the 'commit-graph' task.
Thanks, -Stolee
Derrick Stolee (21):
gc: usethe_repositoryless often
gc: use repository intoo_many_loose_objects()
gc: use repo config
gc: dropthe_repositoryin log location
maintenance: create basic maintenance runner
maintenance: add --quiet option
maintenance: replacerun_auto_gc()
maintenance: initialize task array and hashmap
maintenance: add commit-graph task
maintenance: add --task option
maintenance: take a lock on the objects directory
maintenance: add fetch task
maintenance: add loose-objects task
maintenance: add pack-files task
maintenance: auto-size pack-files batch
maintenance: create maintenance.<task>.enabled config
maintenance: use pointers to check --auto
maintenance: add auto condition for commit-graph task
maintenance: create auto condition for loose-objects
maintenance: add pack-files auto condition
midx: usestart_delayed_progress().gitignore | 1 + Documentation/config.txt | 2 + Documentation/config/maintenance.txt | 32 + Documentation/fetch-options.txt | 5 +- Documentation/git-clone.txt | 7 +- Documentation/git-maintenance.txt | 124 ++++ builtin.h | 1 + builtin/am.c | 2 +- builtin/commit.c | 2 +- builtin/fetch.c | 6 +- builtin/gc.c | 881 +++++++++++++++++++++++++-- builtin/merge.c | 2 +- builtin/rebase.c | 4 +- commit-graph.c | 8 +- commit-graph.h | 1 + config.c | 24 +- config.h | 2 + git.c | 1 + midx.c | 12 +- midx.h | 1 + object.h | 1 + run-command.c | 7 +- run-command.h | 2 +- t/t5319-multi-pack-index.sh | 14 +- t/t5510-fetch.sh | 2 +- t/t5514-fetch-multiple.sh | 2 +- t/t7900-maintenance.sh | 211 +++++++ 27 files changed, 1265 insertions(+), 92 deletions(-) create mode 100644 Documentation/config/maintenance.txt create mode 100644 Documentation/git-maintenance.txt create mode 100755 t/t7900-maintenance.shbase-commit:
4a0fcf9f760c9774be77f51e1e88a7499b53d2e2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-671%2Fderrickstolee%2Fmaintenance%2Fgc-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-671/derrickstolee/maintenance/gc-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/671Signature · 1 line
gitgitgadget
Quoted 89 lines · Emily Shaffer
Quoted 59 lines · Derrick Stolee via GitGitGadget
This is a second attempt at redesigning Git's repository maintenance patterns. The first attempt [1] included a way to run jobs in the background using a long-lived process; that idea was rejected and is not included in this series. A future series will use the OS to handle scheduling tasks.
[1]
https://lore.kernel.org/git/[email protected]/As mentioned before, git gc already plays the role of maintaining Git repositories. It has accumulated several smaller pieces in its long history, including:
1. Repacking all reachable objects into one pack-file (and deleting unreachable objects).
2. Packing refs.
3. Expiring reflogs.
4. Clearing rerere logs.
5. Updating the commit-graph file.While expiring reflogs, clearing rererelogs, and deleting unreachable objects are suitable under the guise of "garbage collection", packing refs and updating the commit-graph file are not as obviously fitting. Further, these operations are "all or nothing" in that they rewrite almost all repository data, which does not perform well at extremely large scales. These operations can also be disruptive to foreground Git commands when git gc --auto triggers during routine use.
This series does not intend to change what git gc does, but instead create new choices for automatic maintenance activities, of which git gc remains the only one enabled by default.
The new maintenance tasks are:
* 'commit-graph' : write and verify a single layer of an incremental commit-graph.
* 'loose-objects' : prune packed loose objects, then create a new pack from a batch of loose objects.
* 'pack-files' : expire redundant packs from the multi-pack-index, then repack using the multi-pack-index's incremental repack strategy.
* 'fetch' : fetch from each remote, storing the refs in 'refs/hidden//'.These tasks are all disabled by default, but can be enabled with config options or run explicitly using "git maintenance run --task=". There are additional config options to allow customizing the conditions for which the tasks run during the '--auto' option. ('fetch' will never run with the '--auto' option.)
Because 'gc' is implemented as a maintenance task, the most dramatic change
of this series is to convert the 'git gc --auto' calls into 'git maintenance run --auto' calls at the end of some Git commands. By default, the only change is that 'git gc --auto' will be run below an additional 'git maintenance' process.The 'git maintenance' builtin has a 'run' subcommand so it can be extended later with subcommands that manage background maintenance, such as 'start', 'stop', 'pause', or 'schedule'. These are not the subject of this series, as it is important to focus on the maintenance activities themselves.
Quoted 5 lines
An expert user could set up scheduled background maintenance themselves with the current series. I have the following crontab data set up to run maintenance on an hourly basis:
0 * * * * git -C /<path-to-repo> maintenance run --no-quiet >>/<path-to-repo>/.git/maintenance.log
One thing I wonder about - now I have to go and make a new crontab (which is easy) or Task Scheduler task (which is a pain) for every repo, right?
Is it infeasible to ask for 'git maintenance' to learn something like '--on /<path-to-repo> --on /<path-to-second-repo>'? Or better yet, some config like "maintenance.targetRepo = /<path-to-repo>"?
Quoted 15 lines
My config includes all tasks except the 'gc' task. The hourly run is over-aggressive, but is sufficient for testing. I'll replace it with daily when I feel satisfied.
Hopefully this direction is seen as a positive one. My goal was to add more options for expert users, along with the flexibility to create background maintenance via the OS in a later series.
OUTLINE
=======Patches 1-4 remove some references to
the_repositoryinbuiltin/gc.cbefore we start depending on code in that builtin.
Quoted 8 lines
Quoted 3 lines
Patches 5-7 create the 'git maintenance run' builtin and subcommand as a simple shim over 'git gc' and replaces calls to 'git gc --auto' from other commands.
For me, I'd prefer to see 'git maintenance run' get bigger and 'git gc --auto' get smaller or disappear. Is there a plan towards that direction, or is that out of scope for 'git maintenance run'? Similar examples I can think of include 'git annotate' and 'git pickaxe'.
Thanks for these examples of prior work. I'll keep them in mind.
Quoted 6 lines
Quoted 3 lines
* Split the 'gc' builtin into smaller maintenance tasks that are enabled by default, but might have different '--auto' conditions and more config options.
Like I mentioned above, for me, I'd rather just see the 'gc' builtin go away :)
My hope is that we can absolutely do that. I didn't want to start that exercise yet, as I don't want to disrupt existing workflows more than I already am.
It is important to recognize that there are already several "tasks" that run inside 'gc' including:
1. Expiring reflogs.
2. Repacking all reachable objects.
3. Deleting unreachable objects.
4. Packing refs.
Before trying to "remove" the gc builtin, we would want these to be represented in the 'git maintenance run' as tasks.
In that direction, I realized after submitting that I should rename the 'pack-files' task in this submission to 'incremental-repack' instead, allowing a later 'full-repack' task to represent the role of that step in the 'gc' task. Some users will prefer one over the other. Perhaps this incremental/full distinction makes it clear that there are trade-offs in both directions.
Thanks,
-Stolee
Quoted 8 lines · Derrick Stolee
Quoted 7 lines · Emily Shaffer
Quoted 5 lines · Derrick Stolee via GitGitGadget
An expert user could set up scheduled background maintenance themselves with the current series. I have the following crontab data set up to run maintenance on an hourly basis:
0 * * * * git -C /<path-to-repo> maintenance run --no-quiet >>/<path-to-repo>/.git/maintenance.log
Quoted 7 lines
Quoted 7 lines
One thing I wonder about - now I have to go and make a new crontab (which is easy) or Task Scheduler task (which is a pain) for every repo, right?
Is it infeasible to ask for 'git maintenance' to learn something like '--on /<path-to-repo> --on /<path-to-second-repo>'? Or better yet, some config like "maintenance.targetRepo = /<path-to-repo>"?
Sorry that I missed this comment on my first reply.
The intention is that this cron entry will be simpler after I follow up with the "background" part of maintenance. The idea is to use global or system config to register a list of repositories that want background maintenance and have cron execute something like "git maintenance run --all-repos" to span "git -C <repo> maintenance run --scheduled" for all repos in the config.
For now, this manual setup does end up a bit cluttered if you have a lot of repos to maintain.
Thanks,
-Stolee
Quoted 13 lines · Derrick Stolee
Quoted 3 lines
Quoted 3 lines
Is it infeasible to ask for 'git maintenance' to learn something like '--on /<path-to-repo> --on /<path-to-second-repo>'? Or better yet, some config like "maintenance.targetRepo = /<path-to-repo>"?
Sorry that I missed this comment on my first reply.
The intention is that this cron entry will be simpler after I follow up with the "background" part of maintenance. The idea is to use global or system config to register a list of repositories that want background maintenance and have cron execute something like "git maintenance run --all-repos" to span "git -C <repo> maintenance run --scheduled" for all repos in the config.
Quoted 2 lines
For now, this manual setup does end up a bit cluttered if you have a lot of repos to maintain.
I think it might be useful to have a general command to run a subcommand in a bunch of repositories. Something like:
git for-each-repo --recurse /path/to/repos git maintenance ...
which would root around in /path/to/repos for any git-dirs and run "git --git-dir=$GIT_DIR maintenance ..." on each of them.
And/or:
git for-each-repo --config maintenance.repos git maintenance ...
which would pull the set of repos from the named config variable instead of looking around the filesystem.
You could use either as a one-liner in the crontab (depending on which is easier with your repo layout).
-Peff
Quoted 26 lines · Jeff King
Quoted 13 lines · Derrick Stolee
Quoted 3 lines
Quoted 3 lines
Is it infeasible to ask for 'git maintenance' to learn something like '--on /<path-to-repo> --on /<path-to-second-repo>'? Or better yet, some config like "maintenance.targetRepo = /<path-to-repo>"?
Sorry that I missed this comment on my first reply.
The intention is that this cron entry will be simpler after I follow up with the "background" part of maintenance. The idea is to use global or system config to register a list of repositories that want background maintenance and have cron execute something like "git maintenance run --all-repos" to span "git -C <repo> maintenance run --scheduled" for all repos in the config.
Quoted 2 lines
For now, this manual setup does end up a bit cluttered if you have a lot of repos to maintain.
I think it might be useful to have a general command to run a subcommand in a bunch of repositories. Something like:
git for-each-repo --recurse /path/to/repos git maintenance ...
which would root around in /path/to/repos for any git-dirs and run "git --git-dir=$GIT_DIR maintenance ..." on each of them.
Quoted 6 lines
And/or:
git for-each-repo --config maintenance.repos git maintenance ...
which would pull the set of repos from the named config variable instead of looking around the filesystem.
Yes! This! That's a good way to make something generic that solves the problem at hand, but might also have other applications! Most excellent.
Quoted 2 lines
You could use either as a one-liner in the crontab (depending on which is easier with your repo layout).
The hope is that we can have such a clean layout. I'm particularly fond of the config option because users may want to opt-in to background maintenance only on some repos, even if they put them in a consistent location.
In the _far_ future, we might even want to add a repo to this "maintenance.repos" list during 'git init' and 'git clone' so this is automatic. It then becomes opt-out at that point, which is why I saw the _far, far_ future.
Thanks,
-Stolee
Quoted 30 lines · Derrick Stolee
Quoted 28 lines · Jeff King
Quoted 13 lines · Derrick Stolee
Quoted 3 lines
Quoted 3 lines
Is it infeasible to ask for 'git maintenance' to learn something like '--on /<path-to-repo> --on /<path-to-second-repo>'? Or better yet, some config like "maintenance.targetRepo = /<path-to-repo>"?
Sorry that I missed this comment on my first reply.
The intention is that this cron entry will be simpler after I follow up with the "background" part of maintenance. The idea is to use global or system config to register a list of repositories that want background maintenance and have cron execute something like "git maintenance run --all-repos" to span "git -C <repo> maintenance run --scheduled" for all repos in the config.
Quoted 2 lines
For now, this manual setup does end up a bit cluttered if you have a lot of repos to maintain.
I think it might be useful to have a general command to run a subcommand in a bunch of repositories. Something like:
git for-each-repo --recurse /path/to/repos git maintenance ...
which would root around in /path/to/repos for any git-dirs and run "git --git-dir=$GIT_DIR maintenance ..." on each of them.
And/or:
Quoted 8 lines
Quoted 4 lines
git for-each-repo --config maintenance.repos git maintenance ...
which would pull the set of repos from the named config variable instead of looking around the filesystem.
Yes! This! That's a good way to make something generic that solves the problem at hand, but might also have other applications! Most excellent.
I'm glad I wasn't the only one super geeked when I read this idea. I'd use the heck out of this in my .bashrc too. Sounds awesome. I actually had a short-lived fling last year with a script to summarize my uncommitted changes in all repos at the beginning of every session (dropped because it became one more thing to gloss over) and could have really used this command.
Quoted 9 lines
Quoted 3 lines
You could use either as a one-liner in the crontab (depending on which is easier with your repo layout).
The hope is that we can have such a clean layout. I'm particularly fond of the config option because users may want to opt-in to background maintenance only on some repos, even if they put them in a consistent location.
Quoted 4 lines
In the _far_ future, we might even want to add a repo to this "maintenance.repos" list during 'git init' and 'git clone' so this is automatic. It then becomes opt-out at that point, which is why I saw the
_far,far_future.
Oh, I like this idea a lot. Then I can do something silly like
alias reproclone="git clone --no-maintainenance"
and get the benefits on everything else that I plan to be using frequently.
- Emily
Quoted 44 lines · Emily Shaffer
Quoted 30 lines · Derrick Stolee
Quoted 28 lines · Jeff King
Quoted 13 lines · Derrick Stolee
Quoted 3 lines
Quoted 3 lines
Is it infeasible to ask for 'git maintenance' to learn something like '--on /<path-to-repo> --on /<path-to-second-repo>'? Or better yet, some config like "maintenance.targetRepo = /<path-to-repo>"?
Sorry that I missed this comment on my first reply.
The intention is that this cron entry will be simpler after I follow up with the "background" part of maintenance. The idea is to use global or system config to register a list of repositories that want background maintenance and have cron execute something like "git maintenance run --all-repos" to span "git -C <repo> maintenance run --scheduled" for all repos in the config.
Quoted 2 lines
For now, this manual setup does end up a bit cluttered if you have a lot of repos to maintain.
I think it might be useful to have a general command to run a subcommand in a bunch of repositories. Something like:
git for-each-repo --recurse /path/to/repos git maintenance ...
which would root around in /path/to/repos for any git-dirs and run "git --git-dir=$GIT_DIR maintenance ..." on each of them.
And/or:
Quoted 8 lines
Quoted 4 lines
git for-each-repo --config maintenance.repos git maintenance ...
which would pull the set of repos from the named config variable instead of looking around the filesystem.
Yes! This! That's a good way to make something generic that solves the problem at hand, but might also have other applications! Most excellent.
Quoted 6 lines
I'm glad I wasn't the only one super geeked when I read this idea. I'd use the heck out of this in my .bashrc too. Sounds awesome. I actually had a short-lived fling last year with a script to summarize my uncommitted changes in all repos at the beginning of every session (dropped because it became one more thing to gloss over) and could have really used this command.
I was planning to build a CLI tool that help manage multiple repos maintenance like what was just described here.
My experience using my poor-man-scalar [1] bash script is: For multiple repositories, the process count could get out of control quite quickly and there are probably other issues that I have not thought of / encountered...
There is definitely a need to keep all the repos updated with pre-fetch and updated commit-graph, while staying compact / garbage free. Having this in Git does simplify a lot of daily operations for end users.
Quoted 15 lines
Quoted 10 lines
Quoted 4 lines
You could use either as a one-liner in the crontab (depending on which is easier with your repo layout).
The hope is that we can have such a clean layout. I'm particularly fond of the config option because users may want to opt-in to background maintenance only on some repos, even if they put them in a consistent location.
Quoted 4 lines
In the _far_ future, we might even want to add a repo to this "maintenance.repos" list during 'git init' and 'git clone' so this is automatic. It then becomes opt-out at that point, which is why I saw the
_far,far_future.
Quoted 6 lines
Oh, I like this idea a lot. Then I can do something silly like
alias reproclone="git clone --no-maintainenance"
and get the benefits on everything else that I plan to be using frequently.
This started to remind me of automatic updates in some of the popular OS. Where download/install/cleanup update of multiple software components are managed under a single tool.
I wonder if this is the path git should take in the 'new world' that Junio mentioned. [2]
But I am also super geeked reading this. :)
Quoted 2 lines
- Emily
Regards,
Son Luong.
[1]: https://github.com/sluongng/git-care
[2]: https://lore.kernel.org/git/[email protected]/
Quoted 10 lines · Derrick Stolee
It is important to recognize that there are already several "tasks" that run inside 'gc' including:
1. Expiring reflogs.
2. Repacking all reachable objects.
3. Deleting unreachable objects.
4. Packing refs.Before trying to "remove" the gc builtin, we would want these to be represented in the 'git maintenance run' as tasks.
Yup. I like the overall direction of this approach to (1) have a single subcommand that helps all the housekeeping tasks, and to (2) make sure existing housekeeping tasks are supported by the new one.
I can understand why it is tempting to start with a new 'main()' under a new subcommand name because we expect to add a lot more tasks, but the name of that subcommand is much less important.
As can be seen in the list you have above, "gc" already does a lot more than garbage collection (just #3 is the "garbage collection" proper), as it has grown by following the same approach.
What's more important is (2) above. While the tool has grown under the same "gc" name, it was easier to arrange---it fell out naturally as a consequence of the development being an enhancement on top of the prior work. Now that we are reimplementing, we need to actively care. As long as we recognize that, I am perfectly happy with the current effort.
For existing callers, "git gc --auto" may want to be left alive, merely as a thin wrapper around "git maintenance --auto", and as long as the latter is done in the same spirit of the former, i.e. perform a lightweight check to see if the repository is so out of shape and then do a minimum cleaning, it would be welcomed by users if it does a lot more than the current "git gc --auto".
Thanks.
Quoted 6 lines · Junio C Hamano
For existing callers, "git gc --auto" may want to be left alive, merely as a thin wrapper around "git maintenance --auto", and as long as the latter is done in the same spirit of the former, i.e. perform a lightweight check to see if the repository is so out of shape and then do a minimum cleaning, it would be welcomed by users if it does a lot more than the current "git gc --auto".
It's entirely possible that (after the 'maintenance' builtin stabilizes) that we make 'git gc --auto' become an alias of something like 'git maintenance run --task=gc --auto' (or itemize all of the sub-tasks) so that 'git gc --auto' doesn't change behavior.
That's a big motivation for adding all code into builtin/gc.c so we can access these tasks inside GC without needing to move or copy the code. I'm trying to preserve history as much as possible.
Thanks,
-Stolee
Quoted 8 lines · Derrick Stolee
Quoted 6 lines · Junio C Hamano
For existing callers, "git gc --auto" may want to be left alive, merely as a thin wrapper around "git maintenance --auto", and as long as the latter is done in the same spirit of the former, i.e. perform a lightweight check to see if the repository is so out of shape and then do a minimum cleaning, it would be welcomed by users if it does a lot more than the current "git gc --auto".
Quoted 4 lines
It's entirely possible that (after the 'maintenance' builtin stabilizes) that we make 'git gc --auto' become an alias of something like 'git maintenance run --task=gc --auto' (or itemize all of the sub-tasks) so that 'git gc --auto' doesn't change behavior.
Yes, it is possible, but I doubt it is desirable.
The current users of "gc --auto" do not (and should not) care the details of what tasks are performed. We surely have added more stuff that need maintenance since "gc --auto" was originally written, and after people have started using "gc --auto" in their workflows. For example, I think "gc --auto" predates "rerere gc" and those who had "gc --auto" in their script had a moment when suddenly it started to clean stale entries in the rerere database.
Were they got upset when it happened? Will they get upset when it starts cleaning up stale commit-graph leftover files?
As long as "gc --auto" kept the same spirit of doing a lightweight check to see if the repository is so out of shape to require cleaning and performing a minimum maintenance when it started calling "rerere gc", and as long as "maintenance --auto" does the same, I would think the users would be delighted without complaints.
So, I wouldn't worry too much about what exactly happens with the future versions of "gc --auto". The world has changed, and we have more items in the repository that needs maintenance/cruft removal. The command in the new world should deal with these new stuff, too.
Thanks.
Quoted 34 lines · Junio C Hamano
Quoted 8 lines · Derrick Stolee
Quoted 6 lines · Junio C Hamano
For existing callers, "git gc --auto" may want to be left alive, merely as a thin wrapper around "git maintenance --auto", and as long as the latter is done in the same spirit of the former, i.e. perform a lightweight check to see if the repository is so out of shape and then do a minimum cleaning, it would be welcomed by users if it does a lot more than the current "git gc --auto".
Quoted 4 lines
It's entirely possible that (after the 'maintenance' builtin stabilizes) that we make 'git gc --auto' become an alias of something like 'git maintenance run --task=gc --auto' (or itemize all of the sub-tasks) so that 'git gc --auto' doesn't change behavior.
Yes, it is possible, but I doubt it is desirable.
The current users of "gc --auto" do not (and should not) care the details of what tasks are performed. We surely have added more stuff that need maintenance since "gc --auto" was originally written, and after people have started using "gc --auto" in their workflows. For example, I think "gc --auto" predates "rerere gc" and those who had "gc --auto" in their script had a moment when suddenly it started to clean stale entries in the rerere database.
Were they got upset when it happened? Will they get upset when it starts cleaning up stale commit-graph leftover files?
As long as "gc --auto" kept the same spirit of doing a lightweight check to see if the repository is so out of shape to require cleaning and performing a minimum maintenance when it started calling "rerere gc", and as long as "maintenance --auto" does the same, I would think the users would be delighted without complaints.
Quoted 4 lines
So, I wouldn't worry too much about what exactly happens with the future versions of "gc --auto". The world has changed, and we have more items in the repository that needs maintenance/cruft removal. The command in the new world should deal with these new stuff, too.
Sounds good to me. The extra context around this helps a lot!
-Stolee
v2 cover Maintenance builtin, allowing 'gc --auto' customization
In reply to Derrick Stolee via GitGitGadgetThis is a second attempt at redesigning Git's repository maintenance patterns. The first attempt [1] included a way to run jobs in the background using a long-lived process; that idea was rejected and is not included in this series. A future series will use the OS to handle scheduling tasks.
[1]
https://lore.kernel.org/git/[email protected]/
As mentioned before, git gc already plays the role of maintaining Git repositories. It has accumulated several smaller pieces in its long history, including:
1. Repacking all reachable objects into one pack-file (and deleting unreachable objects).
2. Packing refs.
3. Expiring reflogs.
4. Clearing rerere logs.
5. Updating the commit-graph file.
6. Pruning worktrees.
While expiring reflogs, clearing rererelogs, and deleting unreachable objects are suitable under the guise of "garbage collection", packing refs and updating the commit-graph file are not as obviously fitting. Further, these operations are "all or nothing" in that they rewrite almost all repository data, which does not perform well at extremely large scales. These operations can also be disruptive to foreground Git commands when git gc --auto triggers during routine use.
This series does not intend to change what git gc does, but instead create new choices for automatic maintenance activities, of which git gc remains the only one enabled by default.
The new maintenance tasks are:
* 'commit-graph' : write and verify a single layer of an incremental commit-graph.
* 'loose-objects' : prune packed loose objects, then create a new pack from a batch of loose objects.
* 'pack-files' : expire redundant packs from the multi-pack-index, then repack using the multi-pack-index's incremental repack strategy.
* 'fetch' : fetch from each remote, storing the refs in 'refs/prefetch//'.
These tasks are all disabled by default, but can be enabled with config options or run explicitly using "git maintenance run --task=". There are additional config options to allow customizing the conditions for which the tasks run during the '--auto' option. ('fetch' will never run with the '--auto' option.)
Because 'gc' is implemented as a maintenance task, the most dramatic change
of this series is to convert the 'git gc --auto' calls into 'git maintenance run --auto' calls at the end of some Git commands. By default, the only change is that 'git gc --auto' will be run below an additional 'git maintenance' process.
The 'git maintenance' builtin has a 'run' subcommand so it can be extended later with subcommands that manage background maintenance, such as 'start', 'stop', 'pause', or 'schedule'. These are not the subject of this series, as it is important to focus on the maintenance activities themselves.
An expert user could set up scheduled background maintenance themselves with the current series. I have the following crontab data set up to run maintenance on an hourly basis:
0 * * * * git -C /<path-to-repo> maintenance run --no-quiet >>/<path-to-repo>/.git/maintenance.log
My config includes all tasks except the 'gc' task. The hourly run is over-aggressive, but is sufficient for testing. I'll replace it with daily when I feel satisfied.
Hopefully this direction is seen as a positive one. My goal was to add more options for expert users, along with the flexibility to create background maintenance via the OS in a later series.
OUTLINE
=======
Patches 1-4 remove some references to the_repository in builtin/gc.c before we start depending on code in that builtin.
Patches 5-7 create the 'git maintenance run' builtin and subcommand as a simple shim over 'git gc' and replaces calls to 'git gc --auto' from other commands.
Patches 8-15 create new maintenance tasks. These are the same tasks sent in the previous RFC.
Patches 16-21 create more customization through config and perform other polish items.
FUTURE WORK
===========
* Add 'start', 'stop', and 'schedule' subcommands to initialize the commands run in the background. You can see my progress towards this goal
here: https://github.com/gitgitgadget/git/pull/680
* Split the 'gc' builtin into smaller maintenance tasks that are enabled by default, but might have different '--auto' conditions and more config options.
* Replace config like 'gc.writeCommitGraph' and 'fetch.writeCommitGraph' with use of the 'commit-graph' task.
* Update the builtin to use struct repository *r properly, especially when calling subcommands.
UPDATES in V2
=============
I'm sending this between v2.28.0-rc2 adn v2.28.0 as the release things have become a bit quiet.
* The biggest disruption to the range-diff is that I removed the premature use of struct repository *r and instead continue to rely on the_repository. This means several patches were dropped that did prep work in builtin/gc.c.
* I dropped the task hashmap and opted for a linear scan. This task list will always be too small to justify the extra complication of the hashmap.
* struct maintenance_opts is properly static now.
* Some tasks are renamed: fetch -> prefetch, pack-files -> incremental-repack.
* With the rename, the prefetch task uses refs/prefetch/ instead of refs/hidden/.
* A trace2 region around the task executions are added.
Thanks, -Stolee
Derrick Stolee (18):
maintenance: create basic maintenance runner
maintenance: add --quiet option
maintenance: replace run_auto_gc()
maintenance: initialize task array
maintenance: add commit-graph task
maintenance: add --task option
maintenance: take a lock on the objects directory
maintenance: add prefetch task
maintenance: add loose-objects task
maintenance: add incremental-repack task
maintenance: auto-size incremental-repack batch
maintenance: create maintenance.<task>.enabled config
maintenance: use pointers to check --auto
maintenance: add auto condition for commit-graph task
maintenance: create auto condition for loose-objects
maintenance: add incremental-repack auto condition
midx: use start_delayed_progress()
maintenance: add trace2 regions for task execution
.gitignore | 1 + Documentation/config.txt | 2 + Documentation/config/maintenance.txt | 32 ++ Documentation/fetch-options.txt | 5 +- Documentation/git-clone.txt | 7 +- Documentation/git-maintenance.txt | 124 +++++ builtin.h | 1 + builtin/am.c | 2 +- builtin/commit.c | 2 +- builtin/fetch.c | 6 +- builtin/gc.c | 753 +++++++++++++++++++++++++++ builtin/merge.c | 2 +- builtin/rebase.c | 4 +- commit-graph.c | 8 +- commit-graph.h | 1 + git.c | 1 + midx.c | 12 +- midx.h | 1 + object.h | 1 + run-command.c | 7 +- run-command.h | 2 +- t/t5319-multi-pack-index.sh | 14 +- t/t5510-fetch.sh | 2 +- t/t5514-fetch-multiple.sh | 2 +- t/t7900-maintenance.sh | 211 ++++++++ 25 files changed, 1169 insertions(+), 34 deletions(-) create mode 100644 Documentation/config/maintenance.txt create mode 100644 Documentation/git-maintenance.txt create mode 100755 t/t7900-maintenance.sh
base-commit: 4a0fcf9f760c9774be77f51e1e88a7499b53d2e2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-671%2Fderrickstolee%2Fmaintenance%2Fgc-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-671/derrickstolee/maintenance/gc-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/671
Range-diff vs v1:
1: 85dda7db28 < -: ---------- gc: use the_repository less often
2: 303ad4bdc7 < -: ---------- gc: use repository in too_many_loose_objects()
3: 9dfa9e8f6f < -: ---------- gc: use repo config
4: e13d351e9f < -: ---------- gc: drop the_repository in log location
5: 5f89e0ec9b ! 1: 63ec602a07 maintenance: create basic maintenance runner
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
+ NULL
+};
+
-+struct maintenance_opts {
++static struct maintenance_opts {
+ int auto_flag;
+} opts;
+
-+static int maintenance_task_gc(struct repository *r)
++static int maintenance_task_gc(void)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
+ if (opts.auto_flag)
+ argv_array_pushl(&cmd, "--auto", NULL);
+
-+ close_object_store(r->objects);
++ close_object_store(the_repository->objects);
+ result = run_command_v_opt(cmd.argv, RUN_GIT_CMD);
+ argv_array_clear(&cmd);
+
+ return result;
+}
+
-+static int maintenance_run(struct repository *r)
++static int maintenance_run(void)
+{
-+ return maintenance_task_gc(r);
++ return maintenance_task_gc();
+}
+
+int cmd_maintenance(int argc, const char **argv, const char *prefix)
+{
-+ struct repository *r = the_repository;
-+
+ static struct option builtin_maintenance_options[] = {
+ OPT_BOOL(0, "auto", &opts.auto_flag,
+ N_("run tasks based on the state of the repository")),
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
+
+ if (argc == 1) {
+ if (!strcmp(argv[0], "run"))
-+ return maintenance_run(r);
++ return maintenance_run();
+ }
+
+ usage_with_options(builtin_maintenance_usage, 6: 018a9331e2 ! 2: 1d37e55cb7 maintenance: add --quiet option
@@ Documentation/git-maintenance.txt: OPTIONS
## builtin/gc.c ##
@@ builtin/gc.c: static const char * const builtin_maintenance_usage[] = {
- struct maintenance_opts {
+ static struct maintenance_opts {
int auto_flag;
+ int quiet;
} opts;
- static int maintenance_task_gc(struct repository *r)
-@@ builtin/gc.c: static int maintenance_task_gc(struct repository *r)
+ static int maintenance_task_gc(void)
+@@ builtin/gc.c: static int maintenance_task_gc(void)
if (opts.auto_flag)
argv_array_pushl(&cmd, "--auto", NULL);
+ if (opts.quiet)
+ argv_array_pushl(&cmd, "--quiet", NULL);
- close_object_store(r->objects);
+ close_object_store(the_repository->objects);
result = run_command_v_opt(cmd.argv, RUN_GIT_CMD);
@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)
static struct option builtin_maintenance_options[] = {
7: 335a8938c6 = 3: f164d1a0b4 maintenance: replace run_auto_gc()
8: 5cdd38afa6 ! 4: 8e260bccf1 maintenance: initialize task array and hashmap
@@ Metadata
Author: Derrick Stolee <[email protected]>
## Commit message ##
- maintenance: initialize task array and hashmap
+ maintenance: initialize task array
In anticipation of implementing multiple maintenance tasks inside the
- 'maintenance' builtin, use a list and hashmap of structs to describe the
- work to be done.
+ 'maintenance' builtin, use a list of structs to describe the work to be
+ done.
The struct maintenance_task stores the name of the task (as given by a
future command-line argument) along with a function pointer to its
@@ Commit message
contains the "gc" task. This task is also the only task enabled by
default.
- This list is also inserted into a hashmap. This allows command-line
- arguments to quickly find the tasks by name, not sensitive to case. To
- ensure this list and hashmap work well together, the list only contains
- pointers to the struct information. This will allow a sort on the list
- while preserving the hashmap data.
-
Signed-off-by: Derrick Stolee <[email protected]>
## builtin/gc.c ##
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
static const char * const builtin_maintenance_usage[] = {
N_("git maintenance run [<options>]"),
NULL
-@@ builtin/gc.c: static int maintenance_task_gc(struct repository *r)
+@@ builtin/gc.c: static int maintenance_task_gc(void)
return result;
}
-+typedef int maintenance_task_fn(struct repository *r);
++typedef int maintenance_task_fn(void);
+
+struct maintenance_task {
-+ struct hashmap_entry ent;
+ const char *name;
+ maintenance_task_fn *fn;
+ unsigned enabled:1;
+};
+
-+static int task_entry_cmp(const void *unused_cmp_data,
-+ const struct hashmap_entry *eptr,
-+ const struct hashmap_entry *entry_or_key,
-+ const void *keydata)
-+{
-+ const struct maintenance_task *e1, *e2;
-+ const char *name = keydata;
-+
-+ e1 = container_of(eptr, const struct maintenance_task, ent);
-+ e2 = container_of(entry_or_key, const struct maintenance_task, ent);
-+
-+ return strcasecmp(e1->name, name ? name : e2->name);
-+}
++static struct maintenance_task *tasks[MAX_NUM_TASKS];
++static int num_tasks;
+
-+struct maintenance_task *tasks[MAX_NUM_TASKS];
-+int num_tasks;
-+struct hashmap task_map;
-+
- static int maintenance_run(struct repository *r)
+ static int maintenance_run(void)
{
-- return maintenance_task_gc(r);
+- return maintenance_task_gc();
+ int i;
+ int result = 0;
+
+ for (i = 0; !result && i < num_tasks; i++) {
+ if (!tasks[i]->enabled)
+ continue;
-+ result = tasks[i]->fn(r);
++ result = tasks[i]->fn();
+ }
+
+ return result;
@@ builtin/gc.c: static int maintenance_task_gc(struct repository *r)
+ tasks[num_tasks]->fn = maintenance_task_gc;
+ tasks[num_tasks]->enabled = 1;
+ num_tasks++;
-+
-+ hashmap_init(&task_map, task_entry_cmp, NULL, MAX_NUM_TASKS);
-+
-+ for (i = 0; i < num_tasks; i++) {
-+ hashmap_entry_init(&tasks[i]->ent,
-+ strihash(tasks[i]->name));
-+ hashmap_add(&task_map, &tasks[i]->ent);
-+ }
}
int cmd_maintenance(int argc, const char **argv, const char *prefix)
9: c8fbd14d41 ! 5: 04552b1d2e maintenance: add commit-graph task
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
static const char * const builtin_maintenance_usage[] = {
N_("git maintenance run [<options>]"),
-@@ builtin/gc.c: struct maintenance_opts {
+@@ builtin/gc.c: static struct maintenance_opts {
int quiet;
} opts;
-+static int run_write_commit_graph(struct repository *r)
++static int run_write_commit_graph(void)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
+
-+ argv_array_pushl(&cmd, "-C", r->worktree,
-+ "commit-graph", "write",
-+ "--split", "--reachable",
-+ NULL);
++ argv_array_pushl(&cmd, "commit-graph", "write",
++ "--split", "--reachable", NULL);
+
+ if (opts.quiet)
-+ argv_array_pushl(&cmd, "--no-progress", NULL);
++ argv_array_push(&cmd, "--no-progress");
+
+ result = run_command_v_opt(cmd.argv, RUN_GIT_CMD);
+ argv_array_clear(&cmd);
@@ builtin/gc.c: struct maintenance_opts {
+ return result;
+}
+
-+static int run_verify_commit_graph(struct repository *r)
++static int run_verify_commit_graph(void)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
+
-+ argv_array_pushl(&cmd, "-C", r->worktree,
-+ "commit-graph", "verify",
++ argv_array_pushl(&cmd, "commit-graph", "verify",
+ "--shallow", NULL);
+
+ if (opts.quiet)
-+ argv_array_pushl(&cmd, "--no-progress", NULL);
++ argv_array_push(&cmd, "--no-progress");
+
+ result = run_command_v_opt(cmd.argv, RUN_GIT_CMD);
+ argv_array_clear(&cmd);
@@ builtin/gc.c: struct maintenance_opts {
+ return result;
+}
+
-+static int maintenance_task_commit_graph(struct repository *r)
++static int maintenance_task_commit_graph(void)
+{
++ struct repository *r = the_repository;
+ char *chain_path;
+
+ /* Skip commit-graph when --auto is specified. */
@@ builtin/gc.c: struct maintenance_opts {
+ return 0;
+
+ close_object_store(r->objects);
-+ if (run_write_commit_graph(r)) {
++ if (run_write_commit_graph()) {
+ error(_("failed to write commit-graph"));
+ return 1;
+ }
+
-+ if (!run_verify_commit_graph(r))
++ if (!run_verify_commit_graph())
+ return 0;
+
+ warning(_("commit-graph verify caught error, rewriting"));
@@ builtin/gc.c: struct maintenance_opts {
+ }
+ free(chain_path);
+
-+ if (!run_write_commit_graph(r))
++ if (!run_write_commit_graph())
+ return 0;
+
+ error(_("failed to rewrite commit-graph"));
+ return 1;
+}
+
- static int maintenance_task_gc(struct repository *r)
+ static int maintenance_task_gc(void)
{
int result;
@@ builtin/gc.c: static void initialize_tasks(void)
+ tasks[num_tasks]->fn = maintenance_task_gc;
tasks[num_tasks]->enabled = 1;
num_tasks++;
-
++
+ tasks[num_tasks]->name = "commit-graph";
+ tasks[num_tasks]->fn = maintenance_task_commit_graph;
+ num_tasks++;
-+
- hashmap_init(&task_map, task_entry_cmp, NULL, MAX_NUM_TASKS);
+ }
- for (i = 0; i < num_tasks; i++) {
+ int cmd_maintenance(int argc, const char **argv, const char *prefix)
## commit-graph.c ##
@@ commit-graph.c: static char *get_split_graph_filename(struct object_directory *odb,
10: c081a3bd29 ! 6: a09b1c1687 maintenance: add --task option
@@ Documentation/git-maintenance.txt: OPTIONS
## builtin/gc.c ##
@@ builtin/gc.c: static const char * const builtin_maintenance_usage[] = {
- struct maintenance_opts {
+ static struct maintenance_opts {
int auto_flag;
int quiet;
+ int tasks_selected;
} opts;
- static int run_write_commit_graph(struct repository *r)
-@@ builtin/gc.c: struct maintenance_task {
- struct hashmap_entry ent;
+ static int run_write_commit_graph(void)
+@@ builtin/gc.c: typedef int maintenance_task_fn(void);
+ struct maintenance_task {
const char *name;
maintenance_task_fn *fn;
- unsigned enabled:1;
@@ builtin/gc.c: struct maintenance_task {
+ selected:1;
};
- static int task_entry_cmp(const void *unused_cmp_data,
-@@ builtin/gc.c: struct maintenance_task *tasks[MAX_NUM_TASKS];
- int num_tasks;
- struct hashmap task_map;
+ static struct maintenance_task *tasks[MAX_NUM_TASKS];
+ static int num_tasks;
+static int compare_tasks_by_selection(const void *a_, const void *b_)
+{
@@ builtin/gc.c: struct maintenance_task *tasks[MAX_NUM_TASKS];
+ return b->task_order - a->task_order;
+}
+
- static int maintenance_run(struct repository *r)
+ static int maintenance_run(void)
{
int i;
int result = 0;
@@ builtin/gc.c: struct maintenance_task *tasks[MAX_NUM_TASKS];
+ if (!opts.tasks_selected && !tasks[i]->enabled)
continue;
+
- result = tasks[i]->fn(r);
+ result = tasks[i]->fn();
}
@@ builtin/gc.c: static void initialize_tasks(void)
- }
+ num_tasks++;
}
+static int task_option_parse(const struct option *opt,
+ const char *arg, int unset)
+{
-+ struct maintenance_task *task;
-+ struct maintenance_task key;
++ int i;
++ struct maintenance_task *task = NULL;
+
+ BUG_ON_OPT_NEG(unset);
+
@@ builtin/gc.c: static void initialize_tasks(void)
+
+ opts.tasks_selected++;
+
-+ key.name = arg;
-+ hashmap_entry_init(&key.ent, strihash(key.name));
-+
-+ task = hashmap_get_entry(&task_map, &key, ent, NULL);
++ for (i = 0; i < MAX_NUM_TASKS; i++) {
++ if (tasks[i] && !strcasecmp(tasks[i]->name, arg)) {
++ task = tasks[i];
++ break;
++ }
++ }
+
+ if (!task) {
+ error(_("'%s' is not a valid task"), arg);
@@ builtin/gc.c: static void initialize_tasks(void)
+
int cmd_maintenance(int argc, const char **argv, const char *prefix)
{
- struct repository *r = the_repository;
+ static struct option builtin_maintenance_options[] = {
@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)
N_("run tasks based on the state of the repository")),
OPT_BOOL(0, "quiet", &opts.quiet,
11: fc1fb5f3cc ! 7: e9260a9c3f maintenance: take a lock on the objects directory
@@ Commit message
Signed-off-by: Derrick Stolee <[email protected]>
## builtin/gc.c ##
-@@ builtin/gc.c: static int maintenance_run(struct repository *r)
+@@ builtin/gc.c: static int maintenance_run(void)
{
int i;
int result = 0;
+ struct lock_file lk;
++ struct repository *r = the_repository;
+ char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path);
+
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
@@ builtin/gc.c: static int maintenance_run(struct repository *r)
if (opts.tasks_selected)
QSORT(tasks, num_tasks, compare_tasks_by_selection);
-@@ builtin/gc.c: static int maintenance_run(struct repository *r)
- result = tasks[i]->fn(r);
+@@ builtin/gc.c: static int maintenance_run(void)
+ result = tasks[i]->fn();
}
+ rollback_lock_file(&lk);
12: cbaa5ecc4f ! 8: 3165b8916d maintenance: add fetch task
@@ Metadata
Author: Derrick Stolee <[email protected]>
## Commit message ##
- maintenance: add fetch task
+ maintenance: add prefetch task
When working with very large repositories, an incremental 'git fetch'
command can download a large amount of data. If there are many other
@@ Commit message
the background. This can break up a large daily fetch into several
smaller hourly fetches.
+ The task is called "prefetch" because it is work done in advance
+ of a foreground fetch to make that 'git fetch' command much faster.
+
However, if we simply ran 'git fetch <remote>' in the background,
then the user running a foregroudn 'git fetch <remote>' would lose
some important feedback when a new branch appears or an existing
@@ Commit message
2. --refmap= removes the configured refspec which usually updates
refs/remotes/<remote>/* with the refs advertised by the remote.
- 3. By adding a new refspec "+refs/heads/*:refs/hidden/<remote>/*"
+ 3. By adding a new refspec "+refs/heads/*:refs/prefetch/<remote>/*"
we can ensure that we actually load the new values somewhere in
our refspace while not updating refs/heads or refs/remotes. By
storing these refs here, the commit-graph job will update the
commit-graph with the commits from these hidden refs.
- 4. --prune will delete the refs/hidden/<remote> refs that no
+ 4. --prune will delete the refs/prefetch/<remote> refs that no
longer appear on the remote.
We've been using this step as a critical background job in Scalar
@@ Documentation/git-maintenance.txt: since it will not expire `.graph` files that
`commit-graph-chain` file. They will be deleted by a later run based on
the expiration delay.
-+fetch::
-+ The `fetch` job updates the object directory with the latest objects
++prefetch::
++ The `fetch` task updates the object directory with the latest objects
+ from all registered remotes. For each remote, a `git fetch` command
+ is run. The refmap is custom to avoid updating local or remote
+ branches (those in `refs/heads` or `refs/remotes`). Instead, the
-+ remote refs are stored in `refs/hidden/<remote>/`. Also, no tags are
-+ updated.
++ remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are
++ not updated.
++
+This means that foreground fetches are still required to update the
+remote refs, but the users is notified when the branches and tags are
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
static const char * const builtin_maintenance_usage[] = {
N_("git maintenance run [<options>]"),
-@@ builtin/gc.c: static int maintenance_task_commit_graph(struct repository *r)
+@@ builtin/gc.c: static int maintenance_task_commit_graph(void)
return 1;
}
-+static int fetch_remote(struct repository *r, const char *remote)
++static int fetch_remote(const char *remote)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
+ struct strbuf refmap = STRBUF_INIT;
+
-+ argv_array_pushl(&cmd, "-C", r->worktree,
-+ "fetch", remote, "--prune",
++ argv_array_pushl(&cmd, "fetch", remote, "--prune",
+ "--no-tags", "--refmap=", NULL);
+
-+ strbuf_addf(&refmap, "+refs/heads/*:refs/hidden/%s/*", remote);
++ strbuf_addf(&refmap, "+refs/heads/*:refs/prefetch/%s/*", remote);
+ argv_array_push(&cmd, refmap.buf);
+
+ if (opts.quiet)
@@ builtin/gc.c: static int maintenance_task_commit_graph(struct repository *r)
+ return 0;
+}
+
-+static int maintenance_task_fetch(struct repository *r)
++static int maintenance_task_prefetch(void)
+{
+ int result = 0;
+ struct string_list_item *item;
@@ builtin/gc.c: static int maintenance_task_commit_graph(struct repository *r)
+ for (item = remotes.items;
+ item && item < remotes.items + remotes.nr;
+ item++)
-+ fetch_remote(r, item->string);
++ fetch_remote(item->string);
+
+cleanup:
+ string_list_clear(&remotes, 0);
+ return result;
+}
+
- static int maintenance_task_gc(struct repository *r)
+ static int maintenance_task_gc(void)
{
int result;
@@ builtin/gc.c: static void initialize_tasks(void)
for (i = 0; i < MAX_NUM_TASKS; i++)
tasks[i] = xcalloc(1, sizeof(struct maintenance_task));
-+ tasks[num_tasks]->name = "fetch";
-+ tasks[num_tasks]->fn = maintenance_task_fetch;
++ tasks[num_tasks]->name = "prefetch";
++ tasks[num_tasks]->fn = maintenance_task_prefetch;
+ num_tasks++;
+
tasks[num_tasks]->name = "gc";
@@ t/t7900-maintenance.sh: test_expect_success 'run --task duplicate' '
test_i18ngrep "cannot be selected multiple times" err
'
-+test_expect_success 'run --task=fetch with no remotes' '
-+ git maintenance run --task=fetch 2>err &&
++test_expect_success 'run --task=prefetch with no remotes' '
++ git maintenance run --task=prefetch 2>err &&
+ test_must_be_empty err
+'
+
-+test_expect_success 'fetch multiple remotes' '
++test_expect_success 'prefetch multiple remotes' '
+ git clone . clone1 &&
+ git clone . clone2 &&
+ git remote add remote1 "file://$(pwd)/clone1" &&
@@ t/t7900-maintenance.sh: test_expect_success 'run --task duplicate' '
+ git -C clone2 switch -c two &&
+ test_commit -C clone1 one &&
+ test_commit -C clone2 two &&
-+ GIT_TRACE2_EVENT="$(pwd)/run-fetch.txt" git maintenance run --task=fetch &&
-+ grep ",\"fetch\",\"remote1\"" run-fetch.txt &&
-+ grep ",\"fetch\",\"remote2\"" run-fetch.txt &&
++ GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch &&
++ grep ",\"fetch\",\"remote1\"" run-prefetch.txt &&
++ grep ",\"fetch\",\"remote2\"" run-prefetch.txt &&
+ test_path_is_missing .git/refs/remotes &&
-+ test_cmp clone1/.git/refs/heads/one .git/refs/hidden/remote1/one &&
-+ test_cmp clone2/.git/refs/heads/two .git/refs/hidden/remote2/two &&
-+ git log hidden/remote1/one &&
-+ git log hidden/remote2/two
++ test_cmp clone1/.git/refs/heads/one .git/refs/prefetch/remote1/one &&
++ test_cmp clone2/.git/refs/heads/two .git/refs/prefetch/remote2/two &&
++ git log prefetch/remote1/one &&
++ git log prefetch/remote2/two
+'
+
test_done
13: 66a1f662ce ! 9: 83648f4865 maintenance: add loose-objects task
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
static const char * const builtin_maintenance_usage[] = {
N_("git maintenance run [<options>]"),
-@@ builtin/gc.c: static int maintenance_task_gc(struct repository *r)
+@@ builtin/gc.c: static int maintenance_task_gc(void)
return result;
}
-+
-+static int prune_packed(struct repository *r)
++static int prune_packed(void)
+{
+ struct argv_array cmd = ARGV_ARRAY_INIT;
-+ argv_array_pushl(&cmd, "-C", r->worktree, "prune-packed", NULL);
++ argv_array_pushl(&cmd, "prune-packed", NULL);
+
+ if (opts.quiet)
+ argv_array_push(&cmd, "--quiet");
@@ builtin/gc.c: static int maintenance_task_gc(struct repository *r)
+ return ++(d->count) > d->batch_size;
+}
+
-+static int pack_loose(struct repository *r)
++static int pack_loose(void)
+{
++ struct repository *r = the_repository;
+ int result = 0;
+ struct write_loose_object_data data;
+ struct strbuf prefix = STRBUF_INIT;
@@ builtin/gc.c: static int maintenance_task_gc(struct repository *r)
+ strbuf_addstr(&prefix, r->objects->odb->path);
+ strbuf_addstr(&prefix, "/pack/loose");
+
-+ argv_array_pushl(&pack_proc->args, "git", "-C", r->worktree,
-+ "pack-objects", NULL);
++ argv_array_pushl(&pack_proc->args, "git", "pack-objects", NULL);
+ if (opts.quiet)
+ argv_array_push(&pack_proc->args, "--quiet");
+ argv_array_push(&pack_proc->args, prefix.buf);
@@ builtin/gc.c: static int maintenance_task_gc(struct repository *r)
+ return result;
+}
+
-+static int maintenance_task_loose_objects(struct repository *r)
++static int maintenance_task_loose_objects(void)
+{
-+ return prune_packed(r) || pack_loose(r);
++ return prune_packed() || pack_loose();
+}
+
- typedef int maintenance_task_fn(struct repository *r);
+ typedef int maintenance_task_fn(void);
struct maintenance_task {
@@ builtin/gc.c: static void initialize_tasks(void)
- tasks[num_tasks]->fn = maintenance_task_fetch;
+ tasks[num_tasks]->fn = maintenance_task_prefetch;
num_tasks++;
+ tasks[num_tasks]->name = "loose-objects";
@@ builtin/gc.c: static void initialize_tasks(void)
tasks[num_tasks]->enabled = 1;
## t/t7900-maintenance.sh ##
-@@ t/t7900-maintenance.sh: test_expect_success 'fetch multiple remotes' '
- git log hidden/remote2/two
+@@ t/t7900-maintenance.sh: test_expect_success 'prefetch multiple remotes' '
+ git log prefetch/remote2/two
'
+test_expect_success 'loose-objects task' '
14: f98790024f ! 10: b6328c2106 maintenance: add pack-files task
@@ Metadata
Author: Derrick Stolee <[email protected]>
## Commit message ##
- maintenance: add pack-files task
+ maintenance: add incremental-repack task
The previous change cleaned up loose objects using the
'loose-objects' that can be run safely in the background. Add a
@@ Commit message
2019-06-10) and 'git multi-pack-index repack' was added in ce1e4a1
(midx: implement midx_repack(), 2019-06-10).
- The 'pack-files' job runs the following steps:
+ The 'incremental-repack' task runs the following steps:
1. 'git multi-pack-index write' creates a multi-pack-index file if
one did not exist, and otherwise will update the multi-pack-index
@@ Commit message
intention is that the resulting pack-file will be close in size
to the provided batch size.
- The next run of the pack-files job will delete these repacked
- pack-files during the 'expire' step.
+ The next run of the incremental-repack task will delete these
+ repacked pack-files during the 'expire' step.
In this version, the batch size is set to "0" which ignores the
size restrictions when selecting the pack-files. It instead
@@ Documentation/git-maintenance.txt: loose-objects::
thousand objects to prevent the job from taking too long on a
repository with many loose objects.
-+pack-files::
-+ The `pack-files` job incrementally repacks the object directory
++incremental-repack::
++ The `incremental-repack` job repacks the object directory
+ using the `multi-pack-index` feature. In order to prevent race
+ conditions with concurrent Git commands, it follows a two-step
+ process. First, it deletes any pack-files included in the
@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)
static const char * const builtin_maintenance_usage[] = {
N_("git maintenance run [<options>]"),
-@@ builtin/gc.c: static int maintenance_task_loose_objects(struct repository *r)
- return prune_packed(r) || pack_loose(r);
+@@ builtin/gc.c: static int maintenance_task_loose_objects(void)
+ return prune_packed() || pack_loose();
}
-+static int multi_pack_index_write(struct repository *r)
++static int multi_pack_index_write(void)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
-+ argv_array_pushl(&cmd, "-C", r->worktree,
-+ "multi-pack-index", "write", NULL);
++ argv_array_pushl(&cmd, "multi-pack-index", "write", NULL);
+
+ if (opts.quiet)
+ argv_array_push(&cmd, "--no-progress");
@@ builtin/gc.c: static int maintenance_task_loose_objects(struct repository *r)
+ return result;
+}
+
-+static int rewrite_multi_pack_index(struct repository *r)
++static int rewrite_multi_pack_index(void)
+{
++ struct repository *r = the_repository;
+ char *midx_name = get_midx_filename(r->objects->odb->path);
+
+ unlink(midx_name);
+ free(midx_name);
+
-+ if (multi_pack_index_write(r)) {
++ if (multi_pack_index_write()) {
+ error(_("failed to rewrite multi-pack-index"));
+ return 1;
+ }
@@ builtin/gc.c: static int maintenance_task_loose_objects(struct repository *r)
+ return 0;
+}
+
-+static int multi_pack_index_verify(struct repository *r)
++static int multi_pack_index_verify(void)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
-+ argv_array_pushl(&cmd, "-C", r->worktree,
-+ "multi-pack-index", "verify", NULL);
++ argv_array_pushl(&cmd, "multi-pack-index", "verify", NULL);
+
+ if (opts.quiet)
+ argv_array_push(&cmd, "--no-progress");
@@ builtin/gc.c: static int maintenance_task_loose_objects(struct repository *r)
+ return result;
+}
+
-+static int multi_pack_index_expire(struct repository *r)
++static int multi_pack_index_expire(void)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
-+ argv_array_pushl(&cmd, "-C", r->worktree,
-+ "multi-pack-index", "expire", NULL);
++ argv_array_pushl(&cmd, "multi-pack-index", "expire", NULL);
+
+ if (opts.quiet)
+ argv_array_push(&cmd, "--no-progress");
+
-+ close_object_store(r->objects);
++ close_object_store(the_repository->objects);
+ result = run_command_v_opt(cmd.argv, RUN_GIT_CMD);
+ argv_array_clear(&cmd);
+
+ return result;
+}
+
-+static int multi_pack_index_repack(struct repository *r)
++static int multi_pack_index_repack(void)
+{
+ int result;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
-+ argv_array_pushl(&cmd, "-C", r->worktree,
-+ "multi-pack-index", "repack", NULL);
++ argv_array_pushl(&cmd, "multi-pack-index", "repack", NULL);
+
+ if (opts.quiet)
+ argv_array_push(&cmd, "--no-progress");
+
+ argv_array_push(&cmd, "--batch-size=0");
+
-+ close_object_store(r->objects);
++ close_object_store(the_repository->objects);
+ result = run_command_v_opt(cmd.argv, RUN_GIT_CMD);
+
-+ if (result && multi_pack_index_verify(r)) {
++ if (result && multi_pack_index_verify()) {
+ warning(_("multi-pack-index verify failed after repack"));
-+ result = rewrite_multi_pack_index(r);
++ result = rewrite_multi_pack_index();
+ }
+
+ return result;
+}
+
-+static int maintenance_task_pack_files(struct repository *r)
++static int maintenance_task_incremental_repack(void)
+{
-+ if (multi_pack_index_write(r)) {
++ if (multi_pack_index_write()) {
+ error(_("failed to write multi-pack-index"));
+ return 1;
+ }
+
-+ if (multi_pack_index_verify(r)) {
++ if (multi_pack_index_verify()) {
+ warning(_("multi-pack-index verify failed after initial write"));
-+ return rewrite_multi_pack_index(r);
++ return rewrite_multi_pack_index();
+ }
+
-+ if (multi_pack_index_expire(r)) {
++ if (multi_pack_index_expire()) {
+ error(_("multi-pack-index expire failed"));
+ return 1;
+ }
+
-+ if (multi_pack_index_verify(r)) {
++ if (multi_pack_index_verify()) {
+ warning(_("multi-pack-index verify failed after expire"));
-+ return rewrite_multi_pack_index(r);
++ return rewrite_multi_pack_index();
+ }
+
-+ if (multi_pack_index_repack(r)) {
++ if (multi_pack_index_repack()) {
+ error(_("multi-pack-index repack failed"));
+ return 1;
+ }
@@ builtin/gc.c: static int maintenance_task_loose_objects(struct repository *r)
+ return 0;
+}
+
- typedef int maintenance_task_fn(struct repository *r);
+ typedef int maintenance_task_fn(void);
struct maintenance_task {
@@ builtin/gc.c: static void initialize_tasks(void)
tasks[num_tasks]->fn = maintenance_task_loose_objects;
num_tasks++;
-+ tasks[num_tasks]->name = "pack-files";
-+ tasks[num_tasks]->fn = maintenance_task_pack_files;
++ tasks[num_tasks]->name = "incremental-repack";
++ tasks[num_tasks]->fn = maintenance_task_incremental_repack;
+ num_tasks++;
+
tasks[num_tasks]->name = "gc";
@@ t/t7900-maintenance.sh: test_expect_success 'loose-objects task' '
test_cmp packs-between packs-after
'
-+test_expect_success 'pack-files task' '
++test_expect_success 'incremental-repack task' '
+ packDir=.git/objects/pack &&
+ for i in $(test_seq 1 5)
+ do
@@ t/t7900-maintenance.sh: test_expect_success 'loose-objects task' '
+
+ # the job repacks the two into a new pack, but does not
+ # delete the old ones.
-+ git maintenance run --task=pack-files &&
++ git maintenance run --task=incremental-repack &&
+ ls $packDir/*.pack >packs-between &&
+ test_line_count = 4 packs-between &&
+
+ # the job deletes the two old packs, and does not write
+ # a new one because only one pack remains.
-+ git maintenance run --task=pack-files &&
++ git maintenance run --task=incremental-repack &&
+ ls .git/objects/pack/*.pack >packs-after &&
+ test_line_count = 1 packs-after
+' 15: 8be89707d2 ! 11: 478c7f1d0b maintenance: auto-size pack-files batch
@@ Metadata
Author: Derrick Stolee <[email protected]>
## Commit message ##
- maintenance: auto-size pack-files batch
+ maintenance: auto-size incremental-repack batch
- When repacking during the 'pack-files' job, we use the --batch-size
- option in 'git multi-pack-index repack'. The initial setting used
- --batch-size=0 to repack everything into a single pack-file. This is not
- sustaintable for a large repository. The amount of work required is also
- likely to use too many system resources for a background job.
+ When repacking during the 'incremental-repack' task, we use the
+ --batch-size option in 'git multi-pack-index repack'. The initial setting
+ used --batch-size=0 to repack everything into a single pack-file. This is
+ not sustaintable for a large repository. The amount of work required is
+ also likely to use too many system resources for a background job.
- Update the 'pack-files' maintenance task by dynamically computing a
+ Update the 'incremental-repack' task by dynamically computing a
--batch-size option based on the current pack-file structure.
The dynamic default size is computed with this idea in mind for a client
@@ Commit message
## builtin/gc.c ##
-@@ builtin/gc.c: static int multi_pack_index_expire(struct repository *r) +@@ builtin/gc.c: static int multi_pack_index_expire(void)
return result;
}
+#define TWO_GIGABYTES (2147483647)
+#define UNSET_BATCH_SIZE ((unsigned long)-1)
+
-+static off_t get_auto_pack_size(struct repository *r) ++static off_t get_auto_pack_size(void)
+{
+ /*
+ * The "auto" value is special: we optimize for
@@ builtin/gc.c: static int multi_pack_index_expire(struct repository *r)
+ off_t second_largest_size = 0;
+ off_t result_size;
+ struct packed_git *p;
++ struct repository *r = the_repository;
+
+ reprepare_packed_git(r);
+ for (p = get_all_packs(r); p; p = p->next) {
@@ builtin/gc.c: static int multi_pack_index_expire(struct repository *r)
+ return result_size;
+}
+
- static int multi_pack_index_repack(struct repository *r)
+ static int multi_pack_index_repack(void)
{
int result;
struct argv_array cmd = ARGV_ARRAY_INIT;
+ struct strbuf batch_arg = STRBUF_INIT;
+
- argv_array_pushl(&cmd, "-C", r->worktree,
- "multi-pack-index", "repack", NULL);
+ argv_array_pushl(&cmd, "multi-pack-index", "repack", NULL);
if (opts.quiet)
argv_array_push(&cmd, "--no-progress");
- argv_array_push(&cmd, "--batch-size=0");
+ strbuf_addf(&batch_arg, "--batch-size=%"PRIuMAX,
-+ (uintmax_t)get_auto_pack_size(r));
++ (uintmax_t)get_auto_pack_size());
+ argv_array_push(&cmd, batch_arg.buf);
- close_object_store(r->objects);
+ close_object_store(the_repository->objects);
result = run_command_v_opt(cmd.argv, RUN_GIT_CMD);
+ strbuf_release(&batch_arg);
- if (result && multi_pack_index_verify(r)) {
+ if (result && multi_pack_index_verify()) {
warning(_("multi-pack-index verify failed after repack"));
## t/t7900-maintenance.sh ##
-@@ t/t7900-maintenance.sh: test_expect_success 'pack-files task' ' +@@ t/t7900-maintenance.sh: test_expect_success 'incremental-repack task' '
test_line_count = 4 packs-between &&
# the job deletes the two old packs, and does not write
- # a new one because only one pack remains.
+ # a new one because the batch size is not high enough to
+ # pack the largest pack-file.
- git maintenance run --task=pack-files &&
+ git maintenance run --task=incremental-repack &&
ls .git/objects/pack/*.pack >packs-after &&
- test_line_count = 1 packs-after
+ test_line_count = 2 packs-after
16: 1551aec4fd ! 12: a3c64930a0 maintenance: create maintenance.<task>.enabled config
@@ Documentation/git-maintenance.txt: SUBCOMMANDS
-----
## builtin/gc.c ##
-@@ builtin/gc.c: static int maintenance_run(struct repository *r)
- return result;
- }
-
--static void initialize_tasks(void)
-+static void initialize_tasks(struct repository *r)
+@@ builtin/gc.c: static int maintenance_run(void)
+ static void initialize_tasks(void)
{
int i;
+ struct strbuf config_name = STRBUF_INIT;
@@ builtin/gc.c: static int maintenance_run(struct repository *r)
for (i = 0; i < MAX_NUM_TASKS; i++)
@@ builtin/gc.c: static void initialize_tasks(void)
- hashmap_init(&task_map, task_entry_cmp, NULL, MAX_NUM_TASKS);
-
- for (i = 0; i < num_tasks; i++) {
-+ int config_value;
+ tasks[num_tasks]->name = "commit-graph";
+ tasks[num_tasks]->fn = maintenance_task_commit_graph;
+ num_tasks++;
+
- hashmap_entry_init(&tasks[i]->ent,
- strihash(tasks[i]->name));
- hashmap_add(&task_map, &tasks[i]->ent);
++ for (i = 0; i < num_tasks; i++) {
++ int config_value;
+
+ strbuf_setlen(&config_name, 0);
+ strbuf_addf(&config_name, "maintenance.%s.enabled", tasks[i]->name);
+
-+ if (!repo_config_get_bool(r, config_name.buf, &config_value))
++ if (!git_config_get_bool(config_name.buf, &config_value))
+ tasks[i]->enabled = config_value;
- }
++ }
+
+ strbuf_release(&config_name);
}
static int task_option_parse(const struct option *opt,
-@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)
- builtin_maintenance_options);
-
- opts.quiet = !isatty(2);
-- initialize_tasks();
-+ initialize_tasks(r);
-
- argc = parse_options(argc, argv, prefix,
- builtin_maintenance_options,
## t/t7900-maintenance.sh ##
@@ t/t7900-maintenance.sh: test_expect_success 'run [--auto|--quiet]' '
17: 130130b662 ! 13: dbacc2b76c maintenance: use pointers to check --auto
@@ Commit message
Signed-off-by: Derrick Stolee <[email protected]>
## builtin/gc.c ##
-@@ builtin/gc.c: static int maintenance_task_pack_files(struct repository *r)
+@@ builtin/gc.c: static int maintenance_task_incremental_repack(void)
- typedef int maintenance_task_fn(struct repository *r);
+ typedef int maintenance_task_fn(void);
+/*
+ * An auto condition function returns 1 if the task should run
+ * and 0 if the task should NOT run. See needs_to_gc() for an
+ * example.
+ */
-+typedef int maintenance_auto_fn(struct repository *r);
++typedef int maintenance_auto_fn(void);
+
struct maintenance_task {
- struct hashmap_entry ent;
const char *name;
maintenance_task_fn *fn;
+ maintenance_auto_fn *auto_condition;
int task_order;
unsigned enabled:1,
selected:1;
-@@ builtin/gc.c: static int maintenance_run(struct repository *r)
+@@ builtin/gc.c: static int maintenance_run(void)
if (!opts.tasks_selected && !tasks[i]->enabled)
continue;
+ if (opts.auto_flag &&
+ (!tasks[i]->auto_condition ||
-+ !tasks[i]->auto_condition(r)))
++ !tasks[i]->auto_condition()))
+ continue;
+
- result = tasks[i]->fn(r);
+ result = tasks[i]->fn();
}
-@@ builtin/gc.c: static void initialize_tasks(struct repository *r)
+@@ builtin/gc.c: static void initialize_tasks(void)
tasks[num_tasks]->name = "gc";
tasks[num_tasks]->fn = maintenance_task_gc;
@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefi
builtin_maintenance_options);
opts.quiet = !isatty(2);
-+ gc_config(r);
- initialize_tasks(r);
++ gc_config();
+ initialize_tasks();
argc = parse_options(argc, argv, prefix, 18: 37fa3f6157 ! 14: 9af2309f08 maintenance: add auto condition for commit-graph task
@@ builtin/gc.c
#define FAILED_RUN "failed to run %s"
-@@ builtin/gc.c: struct maintenance_opts {
+@@ builtin/gc.c: static struct maintenance_opts {
int tasks_selected;
} opts;
@@ builtin/gc.c: struct maintenance_opts {
+ return result;
+}
+
-+static int should_write_commit_graph(struct repository *r)
++static int should_write_commit_graph(void)
+{
+ int result;
+
-+ repo_config_get_int(r, "maintenance.commit-graph.auto",
-+ &limit_commits_not_in_graph);
++ git_config_get_int("maintenance.commit-graph.auto",
++ &limit_commits_not_in_graph);
+
+ if (!limit_commits_not_in_graph)
+ return 0;
@@ builtin/gc.c: struct maintenance_opts {
+ return result;
+}
+
- static int run_write_commit_graph(struct repository *r)
+ static int run_write_commit_graph(void)
{
int result;
-@@ builtin/gc.c: static void initialize_tasks(struct repository *r)
+@@ builtin/gc.c: static void initialize_tasks(void)
tasks[num_tasks]->name = "commit-graph";
tasks[num_tasks]->fn = maintenance_task_commit_graph;
+ tasks[num_tasks]->auto_condition = should_write_commit_graph;
num_tasks++;
- hashmap_init(&task_map, task_entry_cmp, NULL, MAX_NUM_TASKS);
+ for (i = 0; i < num_tasks; i++) {
## object.h ##
@@ object.h: struct object_array {
19: 4744fdaae9 ! 15: 42e316ca58 maintenance: create auto condition for loose-objects
@@ builtin/gc.c: struct write_loose_object_data {
+ return 0;
+}
+
-+static int loose_object_auto_condition(struct repository *r)
++static int loose_object_auto_condition(void)
+{
+ int count = 0;
+
-+ repo_config_get_int(r, "maintenance.loose-objects.auto",
-+ &loose_object_auto_limit);
++ git_config_get_int("maintenance.loose-objects.auto",
++ &loose_object_auto_limit);
+
+ if (!loose_object_auto_limit)
+ return 0;
+ if (loose_object_auto_limit < 0)
+ return 1;
+
-+ return for_each_loose_file_in_objdir(r->objects->odb->path,
++ return for_each_loose_file_in_objdir(the_repository->objects->odb->path,
+ loose_object_count,
+ NULL, NULL, &count);
+}
@@ builtin/gc.c: struct write_loose_object_data {
static int loose_object_exists(const struct object_id *oid,
const char *path,
void *data)
-@@ builtin/gc.c: static void initialize_tasks(struct repository *r)
+@@ builtin/gc.c: static void initialize_tasks(void)
tasks[num_tasks]->name = "loose-objects";
tasks[num_tasks]->fn = maintenance_task_loose_objects;
+ tasks[num_tasks]->auto_condition = loose_object_auto_condition;
num_tasks++;
- tasks[num_tasks]->name = "pack-files";
+ tasks[num_tasks]->name = "incremental-repack";
## t/t7900-maintenance.sh ##
@@ t/t7900-maintenance.sh: test_expect_success 'loose-objects task' '
@@ t/t7900-maintenance.sh: test_expect_success 'loose-objects task' '
+ done
+'
+
- test_expect_success 'pack-files task' '
+ test_expect_success 'incremental-repack task' '
packDir=.git/objects/pack &&
for i in $(test_seq 1 5)
20: fbe03b9af9 ! 16: 3d527cb0dd maintenance: add pack-files auto condition
@@ Metadata
Author: Derrick Stolee <[email protected]>
## Commit message ##
- maintenance: add pack-files auto condition
+ maintenance: add incremental-repack auto condition
- The pack-files task updates the multi-pack-index by deleting pack-files
- that have been replaced with new packs, then repacking a batch of small
- pack-files into a larger pack-file. This incremental repack is faster
+ The incremental-repack task updates the multi-pack-index by deleting pack-
+ files that have been replaced with new packs, then repacking a batch of
+ small pack-files into a larger pack-file. This incremental repack is faster
than rewriting all object data, but is slower than some other
maintenance activities.
- The 'maintenance.pack-files.auto' config option specifies how many
+ The 'maintenance.incremental-repack.auto' config option specifies how many
pack-files should exist outside of the multi-pack-index before running
the step. These pack-files could be created by 'git fetch' commands or
by the loose-objects task. The default value is 10.
@@ Documentation/config/maintenance.txt: maintenance.loose-objects.auto::
loose objects is at least the value of `maintenance.loose-objects.auto`.
The default value is 100.
+
-+maintenance.pack-files.auto::
-+ This integer config option controls how often the `pack-files` task
-+ should be run as part of `git maintenance run --auto`. If zero, then
-+ the `pack-files` task will not run with the `--auto` option. A
-+ negative value will force the task to run every time. Otherwise, a
-+ positive value implies the command should run when the number of
-+ pack-files not in the multi-pack-index is at least the value of
-+ `maintenance.pack-files.auto`. The default value is 10.
++maintenance.incremental-repack.auto::
++ This integer config option controls how often the `incremental-repack`
++ task should be run as part of `git maintenance run --auto`. If zero,
++ then the `incremental-repack` task will not run with the `--auto`
++ option. A negative value will force the task to run every time.
++ Otherwise, a positive value implies the command should run when the
++ number of pack-files not in the multi-pack-index is at least the value
++ of `maintenance.incremental-repack.auto`. The default value is 10.
## builtin/gc.c ##
@@
@@ builtin/gc.c
#define FAILED_RUN "failed to run %s"
-@@ builtin/gc.c: static int maintenance_task_loose_objects(struct repository *r)
- return prune_packed(r) || pack_loose(r);
+@@ builtin/gc.c: static int maintenance_task_loose_objects(void)
+ return prune_packed() || pack_loose();
}
-+static int pack_files_auto_condition(struct repository *r)
++static int incremental_repack_auto_condition(void)
+{
+ struct packed_git *p;
+ int enabled;
-+ int pack_files_auto_limit = 10;
++ int incremental_repack_auto_limit = 10;
+ int count = 0;
+
-+ if (repo_config_get_bool(r, "core.multiPackIndex", &enabled) ||
++ if (git_config_get_bool("core.multiPackIndex", &enabled) ||
+ !enabled)
+ return 0;
+
-+ repo_config_get_int(r, "maintenance.pack-files.auto",
-+ &pack_files_auto_limit);
++ git_config_get_int("maintenance.incremental-repack.auto",
++ &incremental_repack_auto_limit);
+
-+ if (!pack_files_auto_limit)
++ if (!incremental_repack_auto_limit)
+ return 0;
-+ if (pack_files_auto_limit < 0)
++ if (incremental_repack_auto_limit < 0)
+ return 1;
+
-+ for (p = get_packed_git(r);
-+ count < pack_files_auto_limit && p;
++ for (p = get_packed_git(the_repository);
++ count < incremental_repack_auto_limit && p;
+ p = p->next) {
+ if (!p->multi_pack_index)
+ count++;
+ }
+
-+ return count >= pack_files_auto_limit;
++ return count >= incremental_repack_auto_limit;
+}
+
- static int multi_pack_index_write(struct repository *r)
+ static int multi_pack_index_write(void)
{
int result;
-@@ builtin/gc.c: static void initialize_tasks(struct repository *r)
+@@ builtin/gc.c: static void initialize_tasks(void)
- tasks[num_tasks]->name = "pack-files";
- tasks[num_tasks]->fn = maintenance_task_pack_files;
-+ tasks[num_tasks]->auto_condition = pack_files_auto_condition;
+ tasks[num_tasks]->name = "incremental-repack";
+ tasks[num_tasks]->fn = maintenance_task_incremental_repack;
++ tasks[num_tasks]->auto_condition = incremental_repack_auto_condition;
num_tasks++;
tasks[num_tasks]->name = "gc";
## t/t7900-maintenance.sh ##
-@@ t/t7900-maintenance.sh: test_expect_success 'pack-files task' '
+@@ t/t7900-maintenance.sh: test_expect_success 'incremental-repack task' '
test_line_count = 2 packs-after
'
-+test_expect_success 'maintenance.pack-files.auto' '
++test_expect_success 'maintenance.incremental-repack.auto' '
+ git repack -adk &&
+ git config core.multiPackIndex true &&
+ git multi-pack-index write &&
-+ GIT_TRACE2_EVENT=1 git -c maintenance.pack-files.auto=1 maintenance \
-+ run --auto --task=pack-files >out &&
++ GIT_TRACE2_EVENT=1 git -c maintenance.incremental-repack.auto=1 \
++ maintenance run --auto --task=incremental-repack >out &&
+ ! grep "\"multi-pack-index\"" out &&
+ for i in 1 2
+ do
@@ t/t7900-maintenance.sh: test_expect_success 'pack-files task' '
+ ^HEAD~1
+ EOF
+ GIT_TRACE2_EVENT=$(pwd)/trace-A-$i git \
-+ -c maintenance.pack-files.auto=2 \
-+ maintenance run --auto --task=pack-files &&
++ -c maintenance.incremental-repack.auto=2 \
++ maintenance run --auto --task=incremental-repack &&
+ ! grep "\"multi-pack-index\"" trace-A-$i &&
+ test_commit B-$i &&
+ git pack-objects --revs .git/objects/pack/pack <<-\EOF &&
@@ t/t7900-maintenance.sh: test_expect_success 'pack-files task' '
+ ^HEAD~1
+ EOF
+ GIT_TRACE2_EVENT=$(pwd)/trace-B-$i git \
-+ -c maintenance.pack-files.auto=2 \
-+ maintenance run --auto --task=pack-files >out &&
++ -c maintenance.incremental-repack.auto=2 \
++ maintenance run --auto --task=incremental-repack >out &&
+ grep "\"multi-pack-index\"" trace-B-$i >/dev/null || return 1
+ done
+'
21: f7fdf72e9d = 17: a0f00f8ab8 midx: use start_delayed_progress()
-: ---------- > 18: f24db7739f maintenance: add trace2 regions for task executionSignature · 1 line
gitgitgadget
PATCH v2 1/18 maintenance: create basic maintenance runner
From: Derrick Stolee <[email protected]>
The 'gc' builtin is our current entrypoint for automatically maintaining a repository. This one tool does many operations, such as repacking the repository, packing refs, and rewriting the commit-graph file. The name implies it performs "garbage collection" which means several different things, and some users may not want to use this operation that rewrites the entire object database.
Create a new 'maintenance' builtin that will become a more general- purpose command. To start, it will only support the 'run' subcommand, but will later expand to add subcommands for scheduling maintenance in the background.
For now, the 'maintenance' builtin is a thin shim over the 'gc' builtin. In fact, the only option is the '--auto' toggle, which is handed directly to the 'gc' builtin. The current change is isolated to this simple operation to prevent more interesting logic from being lost in all of the boilerplate of adding a new builtin.
Use existing builtin/gc.c file because we want to share code between the two builtins. It is possible that we will have 'maintenance' replace the 'gc' builtin entirely at some point, leaving 'git gc' as an alias for some specific arguments to 'git maintenance run'.
--- .gitignore | 1 + Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++ builtin.h | 1 + builtin/gc.c | 59 +++++++++++++++++++++++++++++++ git.c | 1 + t/t7900-maintenance.sh | 22 ++++++++++++ 6 files changed, 141 insertions(+) create mode 100644 Documentation/git-maintenance.txt create mode 100755 t/t7900-maintenance.sh
.gitignore +1 -0
@@ -90,6 +90,7 @@ /git-ls-tree /git-mailinfo /git-mailsplit +/git-maintenance /git-merge /git-merge-base /git-merge-index
Documentation/git-maintenance.txt new file +57 -0
57 lines not on this page · raw message
builtin.h +1 -0
@@ -167,6 +167,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);int cmd_ls_remote (int argc,const char **argv,const char *prefix);int cmd_mailinfo (int argc,const char **argv,const char *prefix);int cmd_mailsplit (int argc,const char **argv,const char *prefix); +int cmd_maintenance (int argc,const char **argv,const char *prefix);int cmd_merge (int argc,const char **argv,const char *prefix);int cmd_merge_base (int argc,const char **argv,const char *prefix);int cmd_merge_index (int argc,const char **argv,const char *prefix);
builtin/gc.c +59 -0
62 lines not on this page · raw message
git.c +1 -0
@@ -527,6 +527,7 @@ static struct cmd_struct commands[] = { { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "mailsplit", cmd_mailsplit, NO_PARSEOPT }, + { "maintenance", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
t/t7900-maintenance.sh new file +22 -0
@@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='git maintenance builtin' + +GIT_TEST_COMMIT_GRAPH=0 +GIT_TEST_MULTI_PACK_INDEX=0 + +. ./test-lib.sh + +test_expect_success 'help text' ' + test_must_fail git maintenance -h 2>err && + test_i18ngrep "usage: git maintenance run" err +' + +test_expect_success 'gc [--auto]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run && + GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && + grep ",\"gc\"]" run-no-auto.txt && + grep ",\"gc\",\"--auto\"]" run-auto.txt +' + +test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 1 line · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
Everything in this patch looks very sensible to me, and I think that implementing 'git maintenance' in the same file as the current 'git gc' builtin makes a lot of sense to me.
Thanks,
Taylor
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
In reply to Derrick Stolee via GitGitGadgetQuoted 10 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
t/t7900-maintenance.sh new file +3
-0@@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='git maintenance builtin'
t/t7900-maintenance.sh line 13
Quoted 10 lines
t/t7900-maintenance.sh new file +10
-0+ +GIT_TEST_COMMIT_GRAPH=0 +GIT_TEST_MULTI_PACK_INDEX=0 + +. ./test-lib.sh + +test_expect_success 'help text' ' + test_must_fail git maintenance -h 2>err && + test_i18ngrep "usage: git maintenance run" err +'
I think this test has been tested better already in t0012, Anyway, if we would like to check the word "git maintenance run" explicitly here, it would be clearer to
s/test_must_fail/test_expect_code 129/
Quoted 12 lines
t/t7900-maintenance.sh new file +9
-0+ +test_expect_success 'gc [--auto]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run && + GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && + grep ",\"gc\"]" run-no-auto.txt && + grep ",\"gc\",\"--auto\"]" run-auto.txt +' + +test_doneSignature · 1 line
gitgitgadget
Signature · 1 line
Danh
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
In reply to Derrick Stolee via GitGitGadgetHi,
Quoted 1 line · Derrick Stolee
[Subject: maintenance: create basic maintenance runner]
Seems sensible, and a good way to set up for the later patches. Let's take a look at how it does that.
[...]
Quoted 3 lines
Documentation/git-maintenance.txt new file +0
-0@@ -0,0 +1,57 @@
[...]
Documentation/git-maintenance.txt line 33
Quoted 5 lines
Documentation/git-maintenance.txt new file +5
-0+SUBCOMMANDS +----------- + +run:: + Run one or more maintenance tasks.
[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
run <task>...::
What is the exit code convention for "git maintenance run"? (Many Git commands don't document this, but since we're making a new command it seems worth building the habit.)
[...]
Quoted 3 lines
builtin/gc.c +0
-0@@ -699,3 +699,62 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
[...]
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 710
Quoted 3 lines
builtin/gc.c +3
-0+static struct maintenance_opts { +int auto_flag; +} opts;
Packing this in a struct feels a bit unusual. Is the struct going to be passed somewhere, or could these be individual locals in cmd_maintenance?
[...]
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 720
Quoted 10 lines
builtin/gc.c +10
-0+ +static int maintenance_task_gc (void ) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ; + +argv_array_pushl (&cmd,"gc" ,NULL ); + +if (opts.auto_flag) +argv_array_pushl (&cmd,"--auto" ,NULL );
These are both pushing single strings, so they can use argv_array_push.
[...]
t/t7900-maintenance.sh line 6
Quoted 9 lines
t/t7900-maintenance.sh new file +6
-0@@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='git maintenance builtin' + +GIT_TEST_COMMIT_GRAPH=0 +GIT_TEST_MULTI_PACK_INDEX=0
Why does this disable commit graph and multipack index? Is that setting up for something that comes later?
[...]
t/t7900-maintenance.sh line 19
Quoted 5 lines
t/t7900-maintenance.sh new file +5
-0+test_expect_success 'gc [--auto]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run && + GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && + grep ",\"gc\"]" run-no-auto.txt && + grep ",\"gc\",\"--auto\"]" run-auto.txt
This feels a little odd in two ways:
- the use of "git gc" isn't a user-facing detail, so this is testing implementation instead of the user-facing behavior. That's okay, though --- it can be useful to test internals sometimes.
- the way that this tests for "git gc" feels brittle: if the trace emitter changes some day to include a space after the comma, for example, then the test would start failing. I thought that in the spirit of fakes, it could make sense to write a custom "git gc" command using test_write_script, but that isn't likely to work either since gc is a builtin.
Perhaps this is suggesting that we need some central test helper for parsing traces so we can do this reliably in one place. What do you think?
Thanks,
Jonathan
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 24 lines · Jonathan Nieder
Hi,
Quoted 1 line · Derrick Stolee
[Subject: maintenance: create basic maintenance runner]
Seems sensible, and a good way to set up for the later patches. Let's take a look at how it does that.
[...]
Quoted 3 lines
Documentation/git-maintenance.txt new file +0
-0@@ -0,0 +1,57 @@[...]
Documentation/git-maintenance.txt line 33
Quoted 5 lines
Documentation/git-maintenance.txt new file +5
-0+SUBCOMMANDS +----------- + +run:: + Run one or more maintenance tasks.[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
Quoted 1 line
run <task>...::
Hopefully this is documented to your satisfaction when the ability to customize the tasks is implemented.
Quoted 3 lines
What is the exit code convention for "git maintenance run"? (Many Git commands don't document this, but since we're making a new command it seems worth building the habit.)
Is this worth doing? Do we really want every command to document that "0 means success, everything else means failure, and some of those exit codes mean a specific kind of failure that is global to Git"?
Quoted 5 lines
[...]
Quoted 3 lines
builtin/gc.c +0
-0@@ -699,3 +699,62 @@ int cmd_gc(int argc, const char **argv, const char *prefix)[...]
Quoted 7 lines
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 710
Quoted 3 lines
builtin/gc.c +3
-0+static struct maintenance_opts { +int auto_flag; +} opts;Packing this in a struct feels a bit unusual. Is the struct going to be passed somewhere, or could these be individual locals in
cmd_maintenance?
This will grow, and I'd rather have one global struct than many individual global items. It makes it clearer when I use "opts.auto_flag" that this corresponds to whether "--auto" was provided as a command-line option.
Quoted 13 lines
[...]
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 720
Quoted 10 lines
builtin/gc.c +10
-0+ +static int maintenance_task_gc (void ) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ; + +argv_array_pushl (&cmd,"gc" ,NULL ); + +if (opts.auto_flag) +argv_array_pushl (&cmd,"--auto" ,NULL );These are both pushing single strings, so they can use
argv_array_push.
Thanks. I noticed a few of these myself. Luckily, I'll be going through all of these invocations and replacing them with new methods soon ;)
[1] https://lore.kernel.org/git/[email protected]/
Quoted 13 lines
[...]
t/t7900-maintenance.sh line 6
Quoted 9 lines
t/t7900-maintenance.sh new file +6
-0@@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='git maintenance builtin' + +GIT_TEST_COMMIT_GRAPH=0 +GIT_TEST_MULTI_PACK_INDEX=0Why does this disable commit graph and multipack index? Is that setting up for something that comes later?
Yes, these don't need to be here yet.
Quoted 9 lines
[...]
t/t7900-maintenance.sh line 19
Quoted 5 lines
t/t7900-maintenance.sh new file +5
-0+test_expect_success 'gc [--auto]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run && + GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && + grep ",\"gc\"]" run-no-auto.txt && + grep ",\"gc\",\"--auto\"]" run-auto.txtThis feels a little odd in two ways:
Quoted 3 lines
- the use of "git gc" isn't a user-facing detail, so this is testing implementation instead of the user-facing behavior. That's okay, though --- it can be useful to test internals sometimes.
Consider this a "unit test" of the builtin. I'm testing whether the command-line arguments had an effect on the child process.
Quoted 10 lines
- the way that this tests for "git gc" feels brittle: if the trace emitter changes some day to include a space after the comma, for example, then the test would start failing. I thought that in the spirit of fakes, it could make sense to write a custom "git gc" command using
test_write_script, but that isn't likely to work either since gc is a builtin.Perhaps this is suggesting that we need some central test helper for parsing traces so we can do this reliably in one place. What do you think?
I'm specifically using GIT_TRACE2_EVENT, which is intended for consumption by computer, not humans. Adding whitespace or otherwise changing the output format would be an unnecessary disruption that is more likely to have downstream effects with external tools.
In some way, adding extra dependencies like this in our own test suite can help ensure the output format is more stable.
If there is a better way to ask "Did my command call 'git gc' (with no arguments|with these arguments)?" then I'm happy to consider it.
Thanks,
-Stolee
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 10 lines · Derrick Stolee
Quoted 5 lines · Jonathan Nieder
[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
run <task>...::Hopefully this is documented to your satisfaction when the ability to customize the tasks is implemented.
Hm, do you mean that this change is too difficult to squash into the first patch?
Quoted 8 lines
Quoted 3 lines
What is the exit code convention for "git maintenance run"? (Many Git commands don't document this, but since we're making a new command it seems worth building the habit.)
Is this worth doing? Do we really want every command to document that "0 means success, everything else means failure, and some of those exit codes mean a specific kind of failure that is global to Git"?
I mean things like "what exit code does it run if there is no maintenance to do?"
[...]
Quoted 8 lines
Quoted 7 lines
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 710
Quoted 3 lines
builtin/gc.c +3
-0+static struct maintenance_opts { +int auto_flag; +} opts;Packing this in a struct feels a bit unusual. Is the struct going to be passed somewhere, or could these be individual locals in
cmd_maintenance?
Quoted 4 lines
This will grow, and I'd rather have one global struct than many individual global items. It makes it clearer when I use "opts.auto_flag" that this corresponds to whether "--auto" was provided as a command-line option.
That doesn't seem idiomatic within the Git codebase.
Can they be locals instead?
[...]
Quoted 11 lines
Quoted 3 lines
Perhaps this is suggesting that we need some central test helper for parsing traces so we can do this reliably in one place. What do you think?
I'm specifically using
GIT_TRACE2_EVENT, which is intended for consumption by computer, not humans. Adding whitespace or otherwise changing the output format would be an unnecessary disruption that is more likely to have downstream effects with external tools.In some way, adding extra dependencies like this in our own test suite can help ensure the output format is more stable.
Hm, I thought the contract for GIT_TRACE2_EVENT is that it is JSON, which is a well defined format. To that end, I would sincerely hope that external tools are not assuming anything beyond that the format is JSON.
If that wasn't the intent, I'd rather that we not use a JSON-like format at all. That would make the expectations for evolving this interface clearer.
Quoted 2 lines
If there is a better way to ask "Did my command call 'git gc' (with no arguments|with these arguments)?" then I'm happy to consider it.
My proposal was just to factor this out into a function in test-lib-functions.sh so it's easy to evolve over time in one place.
Thanks and hope that helps,
Jonathan
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 7 lines · Jonathan Nieder
Quoted 6 lines · Derrick Stolee
Quoted 4 lines · Jonathan Nieder
[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
Quoted 7 lines
Quoted 4 lines
Quoted 1 line
run <task>...::Hopefully this is documented to your satisfaction when the ability to customize the tasks is implemented.
Hm, do you mean that this change is too difficult to squash into the first patch?
I mean that there is only one task right now. Until the commit-graph task is implemented, there is no need to have a --task=<task> option.
Creating a --task=<task> option in this patch would unnecessarily bloat the patch by implementing multiple tasks while filling in the boilerplate of creating a new builtin.
Quoted 21 lines
Quoted 8 lines
Quoted 3 lines
What is the exit code convention for "git maintenance run"? (Many Git commands don't document this, but since we're making a new command it seems worth building the habit.)
Is this worth doing? Do we really want every command to document that "0 means success, everything else means failure, and some of those exit codes mean a specific kind of failure that is global to Git"?
I mean things like "what exit code does it run if there is no maintenance to do?"
[...]
Quoted 8 lines
Quoted 7 lines
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 710
Quoted 3 lines
builtin/gc.c +3
-0+static struct maintenance_opts { +int auto_flag; +} opts;Packing this in a struct feels a bit unusual. Is the struct going to be passed somewhere, or could these be individual locals in
cmd_maintenance?
Quoted 8 lines
Quoted 4 lines
This will grow, and I'd rather have one global struct than many individual global items. It makes it clearer when I use "opts.auto_flag" that this corresponds to whether "--auto" was provided as a command-line option.
That doesn't seem idiomatic within the Git codebase.
Can they be locals instead?
Which part do you want to be idiomatic? The fact that the parse-options library typically refers to static global values or the fact that the data is not organized in a struct?
Turning these into locals is _even worse_ than the current model of static globals because that then requires updating the prototypes of all the (static) local methods that operate on the arguments. Every time a new option is added, that would cascade in method prototypes all over again.
The natural thing to do to "fix" that situation is to create a struct that holds the information from the parsed command-line arguments. But then why is it a local parameter that is passed through all of the local methods instead of a global (as presented here in this patch)?
Quoted 22 lines
[...]
Quoted 11 lines
Quoted 3 lines
Perhaps this is suggesting that we need some central test helper for parsing traces so we can do this reliably in one place. What do you think?
I'm specifically using
GIT_TRACE2_EVENT, which is intended for consumption by computer, not humans. Adding whitespace or otherwise changing the output format would be an unnecessary disruption that is more likely to have downstream effects with external tools.In some way, adding extra dependencies like this in our own test suite can help ensure the output format is more stable.
Hm, I thought the contract for
GIT_TRACE2_EVENTis that it is JSON, which is a well defined format. To that end, I would sincerely hope that external tools are not assuming anything beyond that the format is JSON.If that wasn't the intent, I'd rather that we not use a JSON-like format at all. That would make the expectations for evolving this interface clearer.
Quoted 5 lines
Quoted 2 lines
If there is a better way to ask "Did my command call 'git gc' (with no arguments|with these arguments)?" then I'm happy to consider it.
My proposal was just to factor this out into a function in
test-lib-functions.shso it's easy to evolve over time in one place.
This is a valuable suggestion, but this series is already too large to make such a change in addition to the patches already here. I'd consider this more if there were more tests that checked "did we run this subcommand with these arguments?" Better to extract a helper when this is actually used in multiple places.
Thanks,
-Stolee
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 12 lines · Derrick Stolee
Quoted 11 lines · Jonathan Nieder
Quoted 10 lines · Derrick Stolee
Quoted 5 lines · Jonathan Nieder
[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
run <task>...::Hopefully this is documented to your satisfaction when the ability to customize the tasks is implemented.
[...]
Quoted 2 lines
I mean that there is only one task right now. Until the commit-graph task is implemented, there is no need to have a --task=<task> option.
Ah, that wasn't clear to me from the docs.
You're saying that "git maintenance run" runs the "gc" task? Can the documentation say so?
[...]
Quoted 13 lines
Quoted 13 lines
Quoted 12 lines
Quoted 7 lines
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 710
Quoted 3 lines
builtin/gc.c +3
-0+static struct maintenance_opts { +int auto_flag; +} opts;Packing this in a struct feels a bit unusual. Is the struct going to be passed somewhere, or could these be individual locals in
cmd_maintenance?This will grow, and I'd rather have one global struct than many individual global items. It makes it clearer when I use "opts.auto_flag" that this corresponds to whether "--auto" was provided as a command-line option.
Quoted 7 lines
Quoted 3 lines
That doesn't seem idiomatic within the Git codebase.
Can they be locals instead?
Which part do you want to be idiomatic? The fact that the parse-options library typically refers to static global values or the fact that the data is not organized in a struct?
parse-options has no requirement about the values being global. Some older code does that, but newer code tends to use locals when appropriate.
Putting it in a struct is fine as long as that struct is being passed around. What isn't idiomatic in Git is using a global struct for namespacing.
[...]
Quoted 4 lines
The natural thing to do to "fix" that situation is to create a struct that holds the information from the parsed command-line arguments. But then why is it a local parameter that is passed through all of the local methods instead of a global (as presented here in this patch)?
I'm having trouble parsing that last sentence. You're saying a global is preferable over passing around a pointer to a local "opts" struct?
[...]
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
If there is a better way to ask "Did my command call 'git gc' (with no arguments|with these arguments)?" then I'm happy to consider it.
My proposal was just to factor this out into a function in
test-lib-functions.shso it's easy to evolve over time in one place.This is a valuable suggestion, but this series is already too large to make such a change in addition to the patches already here.
Hm, it's not clear to me that this would make the series significantly larger.
And on the contrary, it would make the code less fragile. I think this is important.
https://chromium.googlesource.com/chromium/src/+/master/docs/cl_respect.md#remember-communication-can-be-hard:
if you'd like to meet out of band, let me know, and I'd be happy to go into this further.
Thanks,
Jonathan
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 65 lines · Jonathan Nieder
Quoted 12 lines · Derrick Stolee
Quoted 11 lines · Jonathan Nieder
Quoted 10 lines · Derrick Stolee
Quoted 5 lines · Jonathan Nieder
[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
run <task>...::Hopefully this is documented to your satisfaction when the ability to customize the tasks is implemented.
[...]
Quoted 2 lines
I mean that there is only one task right now. Until the commit-graph task is implemented, there is no need to have a --task=<task> option.
Ah, that wasn't clear to me from the docs.
You're saying that "git maintenance run" runs the "gc" task? Can the documentation say so?
[...]
Quoted 13 lines
Quoted 13 lines
Quoted 12 lines
Quoted 7 lines
builtin/gc.c int cmd_gc(int argc, const char **argv, const char *prefix) · line 710
Quoted 3 lines
builtin/gc.c +3
-0+static struct maintenance_opts { +int auto_flag; +} opts;Packing this in a struct feels a bit unusual. Is the struct going to be passed somewhere, or could these be individual locals in
cmd_maintenance?This will grow, and I'd rather have one global struct than many individual global items. It makes it clearer when I use "opts.auto_flag" that this corresponds to whether "--auto" was provided as a command-line option.
Quoted 7 lines
Quoted 3 lines
That doesn't seem idiomatic within the Git codebase.
Can they be locals instead?
Which part do you want to be idiomatic? The fact that the parse-options library typically refers to static global values or the fact that the data is not organized in a struct?
parse-options has no requirement about the values being global. Some older code does that, but newer code tends to use locals when appropriate.
Putting it in a struct is fine as long as that struct is being passed around. What isn't idiomatic in Git is using a global struct for namespacing.
[...]
Quoted 4 lines
The natural thing to do to "fix" that situation is to create a struct that holds the information from the parsed command-line arguments. But then why is it a local parameter that is passed through all of the local methods instead of a global (as presented here in this patch)?
I'm having trouble parsing that last sentence. You're saying a global is preferable over passing around a pointer to a local "opts" struct?
[...]
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
If there is a better way to ask "Did my command call 'git gc' (with no arguments|with these arguments)?" then I'm happy to consider it.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
My proposal was just to factor this out into a function in
test-lib-functions.shso it's easy to evolve over time in one place.This is a valuable suggestion, but this series is already too large to make such a change in addition to the patches already here.
Hm, it's not clear to me that this would make the series significantly larger.
I think what Stolee is trying to say is less about a change that would make the series larger, it's about changing an already-large series.
Quoted 2 lines
And on the contrary, it would make the code less fragile. I think this is important.
I'm not sure that I see your argument. What we are really discussing is whether or not we should have a static struct outside of 'cmd_gc()', or a zero-initialized frame local struct within 'cmd_gc()'. I fully understand the arguments in favor of one or the other, but I struggle to grasp that this is worth our time to debate in this much detail.
Quoted 3 lines
https://chromium.googlesource.com/chromium/src/+/master/docs/cl_respect.md#remember-communication-can-be-hard:
if you'd like to meet out of band, let me know, and I'd be happy to go into this further.
I assume that the part of this document you were linking to was about meeting out-of-band to discuss an issue instead of emailing back and forth.
I appreciate your willingness to resolve this expediently, but I would much prefer if these discussions (that is, the ones about the technical content of this series) happened on-list for the benefit of other reviewers.
Quoted 2 lines
Thanks,
Jonathan
Thanks,
Taylor
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 20 lines · Taylor Blau
Quoted 7 lines · Jonathan Nieder
Quoted 6 lines · Derrick Stolee
Quoted 5 lines · Jonathan Nieder
Quoted 2 lines · Derrick Stolee
If there is a better way to ask "Did my command call 'git gc' (with no arguments|with these arguments)?" then I'm happy to consider it.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
My proposal was just to factor this out into a function in
test-lib-functions.shso it's easy to evolve over time in one place.This is a valuable suggestion, but this series is already too large to make such a change in addition to the patches already here.
Hm, it's not clear to me that this would make the series significantly larger.
I think what Stolee is trying to say is less about a change that would make the series larger, it's about changing an already-large series.
Quoted 8 lines
Quoted 2 lines
And on the contrary, it would make the code less fragile. I think this is important.
I'm not sure that I see your argument. What we are really discussing is whether or not we should have a static struct outside of '
cmd_gc()', or a zero-initialized frame local struct within 'cmd_gc()'. I fully understand the arguments in favor of one or the other, but I struggle to grasp that this is worth our time to debate in this much detail.
Sorry for the lack of clarity. The proposal Stolee is pushing back on above is to have a helper in test-lib-functions.sh that tells a test whether an event it cares about happened in traces (in this example, invocation of "git gc").
I consider it important because if we are getting into the habit of having our tests assume the current exact byte-for-byte trace JSON output, then that is an assumption that is going to be painful to unravel. Factoring out a helper would also make the test easier to read, but that is less important than "an ounce of prevention is worth a pound of cure" to me in this example.
Jonathan
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 30 lines · Jonathan Nieder
Quoted 20 lines · Taylor Blau
Quoted 7 lines · Jonathan Nieder
Quoted 6 lines · Derrick Stolee
Quoted 5 lines · Jonathan Nieder
Quoted 2 lines · Derrick Stolee
If there is a better way to ask "Did my command call 'git gc' (with no arguments|with these arguments)?" then I'm happy to consider it.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
My proposal was just to factor this out into a function in
test-lib-functions.shso it's easy to evolve over time in one place.This is a valuable suggestion, but this series is already too large to make such a change in addition to the patches already here.
Hm, it's not clear to me that this would make the series significantly larger.
I think what Stolee is trying to say is less about a change that would make the series larger, it's about changing an already-large series.
Quoted 8 lines
Quoted 2 lines
And on the contrary, it would make the code less fragile. I think this is important.
I'm not sure that I see your argument. What we are really discussing is whether or not we should have a static struct outside of '
cmd_gc()', or a zero-initialized frame local struct within 'cmd_gc()'. I fully understand the arguments in favor of one or the other, but I struggle to grasp that this is worth our time to debate in this much detail.
Quoted 4 lines
Sorry for the lack of clarity. The proposal Stolee is pushing back on above is to have a helper in
test-lib-functions.shthat tells a test whether an event it cares about happened in traces (in this example, invocation of "git gc").
Serves me right for reading too quickly ;).
Quoted 6 lines
I consider it important because if we are getting into the habit of having our tests assume the current exact byte-for-byte trace JSON output, then that is an assumption that is going to be painful to unravel. Factoring out a helper would also make the test easier to read, but that is less important than "an ounce of prevention is worth a pound of cure" to me in this example.
I still only partially buy into this, though. I had to deal with some rather fragile grepping through trace2 JSON in t4216 (? or something, the log-bloom tests) recently, and found it a little fragile, but not overly so.
I'd rather just move forward, but I do feel strongly that we discuss these matters on the list.
Quoted 1 line
Jonathan
Thanks,
Taylor
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 6 lines · Taylor Blau
I still only partially buy into this, though. I had to deal with some rather fragile grepping through trace2 JSON in t4216 (? or something, the log-bloom tests) recently, and found it a little fragile, but not overly so.
I'd rather just move forward,
This means we're promising to never change the JSON serialization, or that we're willing to pay with time in the future to update all tests that assume the current JSON serialization.
I do not want to make that promise. I've lived through the same work of updating tests that assumed particular sha1s and having to change them to be more generic and do not want to have to do that again.
If you're saying that you are willing to do that work when the time comes, then that counts for a lot. If you're saying that we should not change the JSON serialization, then it makes me worry that we made a mistake in choosing JSON as a format, since using a well defined format with an ecosystem of serialization + deserialization libraries was the whole point.
Jonathan
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 5 lines · Jonathan Nieder
... If you're saying that we should
not change the JSON serialization, then it makes me worry that we made a mistake in choosing JSON as a format, since using a well defined format with an ecosystem of serialization + deserialization libraries was the whole point.
Well said.
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
In reply to Jonathan NiederQuoted 17 lines · Jonathan Nieder
Quoted 6 lines · Taylor Blau
I still only partially buy into this, though. I had to deal with some rather fragile grepping through trace2 JSON in t4216 (? or something, the log-bloom tests) recently, and found it a little fragile, but not overly so.
I'd rather just move forward,
This means we're promising to never change the JSON serialization, or that we're willing to pay with time in the future to update all tests that assume the current JSON serialization.
I do not want to make that promise. I've lived through the same work of updating tests that assumed particular sha1s and having to change them to be more generic and do not want to have to do that again.
Quoted 6 lines
If you're saying that you are willing to do that work when the time comes, then that counts for a lot. If you're saying that we should not change the JSON serialization, then it makes me worry that we made a mistake in choosing JSON as a format, since using a well defined format with an ecosystem of serialization + deserialization libraries was the whole point.
The benefit of JSON is that it is simple to be _additive_. We can always jam more data into the format.
What I'm doing is making the following assumptions:
1. There will always be an event line corresponding to launching a subcommand.
2. This line will contain the list of arguments used for that subcommand in a JSON array.
3. We don't add extra whitespace in that JSON array.
The first two items are important promises made by the trace2 event output. By depending on them, I'm only adding more places where we expect the JSON *schema* to remain consistent. Rather, I only require that the JSON schema does not _remove_ that data which is expected by tools that are reading and reporting that data.
Only the third item is something that adds an additional restriction on the JSON format. But also: why would we ever add extraneous whitespace characters to the output intended for computer parsing? This concern raises a giant "YAGNI" warning in my head.
What is seems like you are asking instead is for me to create a tool in the test suite that parses each JSON line, extracts a specific member from that JSON object, reconstructs a command-line invocation from the JSON array, and reports whether that process worked for any line in the event output.
That seems overly complicated and puts even more requirements on the JSON schema than I have placed in my current implementation.
If this is to truly be a hard requirement for these tests to move forward, then I would rather do an alternate approach that would be a better test mechanism than scraping trace data: mock the subcommands.
I would create a new directory, "t/mock", and create a new 'git' executable there. By setting --exec-path in our test commands, we would launch this mocked version of Git instead of our built version. The mock could report each set of command-line arguments to a known file. With some care, we could even provide an instructions file to tell it which error code to return or whether it should write data to stdout. This would allow testing things like the "rewrite after verify fails" behavior already in the maintenance builtin.
If I'm to spend time engineering something more complicated just to check "did this subcommand run with these arguments?" then I'd rather do it this way.
Thanks,
-Stolee
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 5 lines · Derrick Stolee
What is seems like you are asking instead is for me to create a tool in the test suite that parses each JSON line, extracts a specific member from that JSON object, reconstructs a command-line invocation from the JSON array, and reports whether that process worked for any line in the event output.
No, that isn't what I'm asking.
I'm asking for this patch series to take the existing "grep" lines and put them in a function in test-lib-functions.sh, so that we can change them in one place when the trace emitter changes.
[...]
Quoted 2 lines
If this is to truly be a hard requirement for these tests to move forward,
Yes, from my point of view it really is.
But that "is this truly a hard requirement?" comes up tells me I have not done a good job of communicating in this review. A review is about participants in the project working together to improve a patch, not people making demands at each other.
[...]
Quoted 2 lines
If I'm to spend time engineering something more complicated just to check "did this subcommand run with these arguments?" then
I don't see why this is more complicated than what is in patch 1. In fact, I think it would be a little more simple.
Jonathan
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 8 lines · Jonathan Nieder
Quoted 5 lines · Derrick Stolee
What is seems like you are asking instead is for me to create a tool in the test suite that parses each JSON line, extracts a specific member from that JSON object, reconstructs a command-line invocation from the JSON array, and reports whether that process worked for any line in the event output.
Quoted 5 lines
No, that isn't what I'm asking.
I'm asking for this patch series to take the existing "grep" lines and put them in a function in
test-lib-functions.sh, so that we can change them in one place when the trace emitter changes.
Thanks for clarifying, clearing up my misinterpretation of your request. I'm coming around to this idea.
Quoted 10 lines
[...]
Quoted 2 lines
If this is to truly be a hard requirement for these tests to move forward,
Yes, from my point of view it really is.
But that "is this truly a hard requirement?" comes up tells me I have not done a good job of communicating in this review. A review is about participants in the project working together to improve a patch, not people making demands at each other.
It's also about a balance. A patch author and a reviewer can have disagreements, and it is important to discover exactly how hard each is holding to their opinion.
Quoted 6 lines
[...]
Quoted 2 lines
If I'm to spend time engineering something more complicated just to check "did this subcommand run with these arguments?" then
I don't see why this is more complicated than what is in patch 1. In fact, I think it would be a little more simple.
My interpretation (parsing JSON) _was_ more complicated, hence my reservations. The helper still requires non-trivial script-fu (for someone like me who is bad at scripting languages) but centralizing the grep has value.
Here is my attempt so far:
trace2_subcommand_data () {
command="$1" &&
commas=$(echo $command | sed s/\ /\",\"/g) &&
printf "[\"$commas\"]"
}test_subcommand_called () {
data=$(trace2_subcommand_data $1) &&
grep $data $2
}test_subcommand_not_called () {
! test_subcommand_called $1 $2
}
with callers looking like
test_subcommand_called "gc" run-no-auto.txt && test_subcommand_not_called "gc --auto" run-auto.txt && test_subcommand_called "gc --quiet" run-quiet.txt
Thanks,
-Stolee
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 1 line · Derrick Stolee
Here is my attempt so far:
[...]
Quoted 3 lines
test_subcommand_called "gc" run-no-auto.txt && test_subcommand_not_called "gc --auto" run-auto.txt && test_subcommand_called "gc --quiet" run-quiet.txt
I like it. Some tweaks:
- can simplify by using 'printf' instead of sed
- passing the trace in stdin
- following the convention that helpers like test_i18ngrep use of accepting ! as a parameter to negate the exit code
That would make (untested)
# Check that the given command was invoked as part of the
# trace2-format trace on stdin.
#
# test_subcommand_called [!] <command> <args>... < <trace>
#
# For example, to look for an invocation of "git upload-pack
# /path/to/repo"
#
# GIT_TRACE2_EVENT=event.log git fetch ... &&
# test_subcommand_called git upload-pack "$PATH" <event.log
#
# If the first parameter passed is !, this instead checks that
# the given command was not called.
#
test_subcommand_called () {
local negate=
if test "$1" = "!"
then
negate=t
shift
fi local expr=$(printf '"%s",' "$@")
expr="${expr%,}"
if test -n "$negate"
then
! grep "\[$expr\]"
else
grep "\[$expr\]"
fi
}Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
Quoted 38 lines · Jonathan Nieder
Quoted 1 line · Derrick Stolee
Here is my attempt so far:
[...]
Quoted 3 lines
test_subcommand_called "gc" run-no-auto.txt && test_subcommand_not_called "gc --auto" run-auto.txt && test_subcommand_called "gc --quiet" run-quiet.txtI like it. Some tweaks:
- can simplify by using 'printf' instead of sed
- passing the trace in stdin
- following the convention that helpers liketest_i18ngrepuse of accepting ! as a parameter to negate the exit codeThat would make (untested)
# Check that the given command was invoked as part of the
# trace2-format trace on stdin. # # test_subcommand_called [!] <command> <args>... < <trace> # # For example, to look for an invocation of "git upload-pack # /path/to/repo" # # GIT_TRACE2_EVENT=event.log git fetch ... && # test_subcommand_called git upload-pack "$PATH" <event.log # # If the first parameter passed is !, this instead checks that # the given command was not called. # test_subcommand_called () { local negate= if test "$1" = "!" then negate=t shift fi
Quoted 10 lines
local expr=$(printf '"%s",' "$@")
expr="${expr%,}"
if test -n "$negate"
then
! grep "\[$expr\]"
else
grep "\[$expr\]"
fi
}This works! Thanks. With the ! usage, I think a rename to 'test_subcommand' makes sense. Further, I was somehow wrong about not including "git" in the beginning, so here is my example use of this method;
test_subcommand git gc <run-no-auto.txt && test_subcommand ! git gc --auto <run-auto.txt && test_subcommand git gc --quiet <run-quiet.txt
Thanks,
-Stolee
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
In reply to Jonathan NiederQuoted 4 lines · Jonathan Nieder
Quoted 3 lines · Derrick Stolee
Quoted 2 lines · Jonathan Nieder
Quoted 1 line · Derrick Stolee
On 7/29/2020 6:19 PM, Jonathan Nieder wrote:
Revisiting the first part of this thread, as it got lost in the discussion about trace2 and JSON parsing...
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines
[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
run <task>...::Hopefully this is documented to your satisfaction when the ability to customize the tasks is implemented.
Quoted 8 lines
[...]
Quoted 2 lines
I mean that there is only one task right now. Until the commit-graph task is implemented, there is no need to have a --task=<task> option.
Ah, that wasn't clear to me from the docs.
You're saying that "git maintenance run" runs the "gc" task? Can the documentation say so?
In this patch, the documentation says
Run tasks to optimize Git repository data
the plural is a bit incorrect at this point, but not a huge deal.
The run subcommand says "Run one or more maintenance tasks" which again is a bit misleading since "or more" is not technically possible. This becomes a bit obvious when we see that the task list only contains a 'gc' task.
So no, the documentation in this patch doesn't say "Run the 'gc' task" because that could be gathered from context _and_ it will be incorrect by the time this patch is integrated as part of the series.
So I struggle with finding a way to improve the documentation in this patch to satisfy your concerns. Any change made here will just be un-done by a later patch in the same series.
I'd rather have something vague now and improve the documentation incrementally along with the implementation.
Another point you made earlier, but seems to be dropped in this email:
you wanted the return code documented. This also seems like wasteful text, because the best I can think of is
This command will return 0 unless a maintenance task failed in an unrecoverable way, or other typical failure cases such as incorrect usage or the command being run outside of a Git repository.
Please recommend something better, because that description suffers from being too generic/obvious when talking about the maintenance task and being an obvious repetition of Git's existing error code behavior.
Perhaps this would be easier if there was an established pattern of documenting the return code. So, I went digging.
The examples I could find are:
1. 'git cat-file -e'
2. 'git check-ref-format'
3. 'git commit-graph --object-dir=<dir>'
4. 'git cvsimport'
5. 'git fast-import'
6. 'git filter-branch'
7. 'git grep --quiet'
8. 'git gui citool'
9. 'git ls-remote --exit-code'
10. 'git merge-base --is-ancestor'
11. 'git push --recurse-submodules'
12. 'git reflog exists'
13. 'git rev-parse --parseopt'
14. 'git update-index --test-untracked-cache'
Most of these are for specific options, and the exit code is documented with relation to how that specific option may lead to a non-zero exit code (1, 3, 7, 8, 9, 10, 11, 12, 13, 14).
The one that is most clearly part of the main description is for the plumbing command 'git check-ref-format':
Checks if a given 'refname' is acceptable, and exits with a non-zero status if it is not.
This seems appropriate for something intended for use as a helper in script, with a very simple behavior. The exit code is the only way it provides any feedback to the caller! This use of the exit code as the only output is used by some other cases (1, 7, 9, 10, 11, 12, 13, 14).
Of course, we also have examples like this in git-cvsimport.txt:
Otherwise, success is indicated the Unix way, i.e. by simply exiting with a zero exit status.
This phrase should be _assumed_ as part of Git itself, instead of written in the documentation for specific commands.
The documentation for 'git fast-import' is more useful here:
If fast-import is supplied invalid input it will terminate with a non-zero exit status and create a crash report...
But, you can see that the exit code is not the focus, but the crash report _is_ important to document.
'git filter-branch' has a special exit code for the case that no commits were found to update.
'git gui citool' returns an error code if the window is closed before the command can complete. This is also not a core feature, so is not a good example.
My only conclusion from this investigation is that the exit code is documented when it is useful, but otherwise the standard Unix pattern is assumed and not worth documenting.
If this is something we want to change, then I don't think it valuable to have that discussion here. Instead, let's start that discussion elsewhere with a series that updates existing docs to catch up to a new standard of documenting the exit code.
[1] https://git-scm.com/docs/git-cat-file#Documentation/git-cat-file.txt--e
[2] https://git-scm.com/docs/git-check-ref-format#_description
[3] https://git-scm.com/docs/git-commit-graph#Documentation/git-commit-graph.txt---object-dir
[4] https://git-scm.com/docs/git-cvsimport#_output
[5] https://git-scm.com/docs/git-fast-import#_crash_reports
[6] https://git-scm.com/docs/git-filter-branch#_exit_status
[7] https://git-scm.com/docs/git-grep#Documentation/git-grep.txt---quiet
[8] https://git-scm.com/docs/git-gui#Documentation/git-gui.txt-codegitguicitoolcode
[9] https://git-scm.com/docs/git-ls-remote#Documentation/git-ls-remote.txt---exit-code
[10] https://git-scm.com/docs/git-merge-base#Documentation/git-merge-base.txt---is-ancestor
[11] https://git-scm.com/docs/git-push#Documentation/git-push.txt---recurse-submodulescheckon-demandonlyno
[12] https://git-scm.com/docs/git-reflog
[13] https://git-scm.com/docs/git-rev-parse#_parseopt
[14] https://git-scm.com/docs/git-update-index#Documentation/git-update-index.txt---test-untracked-cache
Quoted 18 lines
[...]
Quoted 17 lines
Quoted 16 lines
Quoted 8 lines
Quoted 7 lines
Review
Quoted 3 lines
(quoted diff) +3
-0+static struct maintenance_opts { + int auto_flag; +} opts;Packing this in a struct feels a bit unusual. Is the struct going to be passed somewhere, or could these be individual locals in
cmd_maintenance?Quoted 4 lines
This will grow, and I'd rather have one global struct than many individual global items. It makes it clearer when I use "opts.auto_flag" that this corresponds to whether "--auto" was provided as a command-line option.
That doesn't seem idiomatic within the Git codebase.
Can they be locals instead?
Quoted 7 lines
Quoted 3 lines
Which part do you want to be idiomatic? The fact that the parse-options library typically refers to static global values or the fact that the data is not organized in a struct?
parse-options has no requirement about the values being global. Some older code does that, but newer code tends to use locals when appropriate.
You're right. Global is not required. The struct needs to be 'static' or initialized for compilation.
While trying to get rid of globals based on your recommendation, I see that the callback functions in the option-parsing _do_ require global state. This is used in "maintenance: add --task option" with the global tasks array, but it also uses a global count of "how many tasks have been selected" currently in the struct. Two ways to get around that issue include (1) making a global int, or (2) looping through the task list to count how many have been selected so far.
Neither of these solutions is clean. (In the diff below, I chose option 2.)
Quoted 3 lines
Putting it in a struct is fine as long as that struct is being passed around. What isn't idiomatic in Git is using a global struct for namespacing.
In my opinion, using a global struct would be better than the existing pattern of a bunch of global variables, but I get your point about precedence. More on this below.
Quoted 8 lines
[...]
Quoted 4 lines
The natural thing to do to "fix" that situation is to create a struct that holds the information from the parsed command-line arguments. But then why is it a local parameter that is passed through all of the local methods instead of a global (as presented here in this patch)?
I'm having trouble parsing that last sentence. You're saying a global is preferable over passing around a pointer to a local "opts" struct?
Using a "singleton" like the opts struct here, the code is definitely much simpler to read. There is only ever one instance, because the struct definition comes along with the instance declaration.
By making it a local instance, we then need to pass around the struct as a parameter to all of the static methods in the builtin, even though there is only ever one instance of this struct per process.
The two points above are the ones that led me to use the global instance in the first place.
To get back to your point about being idiomatic, these builtins use a struct to group options, then pass it around as a parameter:
* cat-file (struct batch_options)
* checkout (struct checkout_opts)
* rebase (struct rebase_options)
* tag (struct create_tag_options)
* worktree (struct add_opts)
Unfortunately, there _is_ an example of the behavior I was following in this series: my own example in the sparse-checkout builtin. So, I'm just repeating my own mistakes that were not challenged before.
I updated my v4-in-progress branch with that change, and the tip-to-tip diff is below. It required some non-trivial care around the --task=<task> callback (which can't see the local options struct) and other cases.
Thanks,
-Stolee
---
builtin/gc.c +61 -59
@@ -709,11 +709,10 @@ static const char * const builtin_maintenance_usage[] = { NULL }; -static struct maintenance_opts { +struct maintenance_opts {int auto_flag;int quiet; -int tasks_selected; -} opts; +};/* Remember to update object flag allocation in object.h */ #define PARENT1 (1u<<16)
@@ -789,7 +788,7 @@ static int should_write_commit_graph(void)return result; } -static int run_write_commit_graph (void ) +static int run_write_commit_graph (struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ;
@@ -797,13 +796,13 @@ static int run_write_commit_graph(void)strvec_pushl (&child.args,"commit-graph" ,"write" ,"--split" ,"--reachable" ,NULL ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--no-progress" );return !!run_command (&child); } -static int run_verify_commit_graph (void ) +static int run_verify_commit_graph (struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ;
@@ -811,24 +810,24 @@ static int run_verify_commit_graph(void)strvec_pushl (&child.args,"commit-graph" ,"verify" ,"--shallow" ,NULL ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--no-progress" );return !!run_command (&child); } -static int maintenance_task_commit_graph (void ) +static int maintenance_task_commit_graph (struct maintenance_opts *opts) {struct repository *r = the_repository;char *chain_path;close_object_store (r->objects); -if (run_write_commit_graph ()) { +if (run_write_commit_graph (opts)) {error (_ ("failed to write commit-graph" ));return 1 ; } -if (!run_verify_commit_graph ()) +if (!run_verify_commit_graph (opts))return 0 ;warning (_ ("commit-graph verify caught error, rewriting" ));
@@ -840,14 +839,14 @@ static int maintenance_task_commit_graph(void) }free (chain_path); -if (!run_write_commit_graph ()) +if (!run_write_commit_graph (opts))return 0 ;error (_ ("failed to rewrite commit-graph" ));return 1 ; } -static int fetch_remote (const char *remote) +static int fetch_remote (const char *remote,struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ;
@@ -855,7 +854,7 @@ static int fetch_remote(const char *remote)strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--refmap=" ,NULL ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--quiet" );strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote);
@@ -871,7 +870,7 @@ static int fill_each_remote(struct remote *remote, void *cbdata)return 0 ; } -static int maintenance_task_prefetch (void ) +static int maintenance_task_prefetch (struct maintenance_opts *opts) {int result =0 ;struct string_list_item *item;
@@ -886,23 +885,23 @@ static int maintenance_task_prefetch(void)for (item = remotes.items; item && item < remotes.items + remotes.nr; item++) - result |=fetch_remote (item->string); + result |=fetch_remote (item->string, opts); cleanup:string_list_clear (&remotes,0 );return result; } -static int maintenance_task_gc (void ) +static int maintenance_task_gc (struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ;strvec_push (&child.args,"gc" ); -if (opts.auto_flag) +if (opts->auto_flag)strvec_push (&child.args,"--auto" ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--quiet" );else strvec_push (&child.args,"--no-quiet" );
@@ -911,14 +910,14 @@ static int maintenance_task_gc(void)return run_command (&child); } -static int prune_packed (void ) +static int prune_packed (struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ;strvec_push (&child.args,"prune-packed" ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--quiet" );return !!run_command (&child);
@@ -977,7 +976,7 @@ static int write_loose_object_to_stdin(const struct object_id *oid,return ++(d->count) > d->batch_size; } -static int pack_loose (void ) +static int pack_loose (struct maintenance_opts *opts) {struct repository *r = the_repository;int result =0 ;
@@ -996,7 +995,7 @@ static int pack_loose(void) pack_proc.git_cmd =1 ;strvec_push (&pack_proc.args,"pack-objects" ); -if (opts.quiet) +if (opts->quiet)strvec_push (&pack_proc.args,"--quiet" );strvec_pushf (&pack_proc.args,"%s/pack/loose" , r->objects->odb->path);
@@ -1027,9 +1026,9 @@ static int pack_loose(void)return result; } -static int maintenance_task_loose_objects (void ) +static int maintenance_task_loose_objects (struct maintenance_opts *opts) { -return prune_packed () ||pack_loose (); +return prune_packed (opts) ||pack_loose (opts); }static int incremental_repack_auto_condition (void )
@@ -1061,14 +1060,14 @@ static int incremental_repack_auto_condition(void)return count >= incremental_repack_auto_limit; } -static int multi_pack_index_write (void ) +static int multi_pack_index_write (struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ;strvec_pushl (&child.args,"multi-pack-index" ,"write" ,NULL ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--no-progress" );if (run_command (&child))
@@ -1077,7 +1076,7 @@ static int multi_pack_index_write(void)return 0 ; } -static int rewrite_multi_pack_index (void ) +static int rewrite_multi_pack_index (struct maintenance_opts *opts) {struct repository *r = the_repository;char *midx_name =get_midx_filename (r->objects->odb->path);
@@ -1085,17 +1084,18 @@ static int rewrite_multi_pack_index(void)unlink (midx_name);free (midx_name); -return multi_pack_index_write (); +return multi_pack_index_write (opts); } -static int multi_pack_index_verify (const char *message) +static int multi_pack_index_verify (struct maintenance_opts *opts, +const char *message) {struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ;strvec_pushl (&child.args,"multi-pack-index" ,"verify" ,NULL ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--no-progress" );if (run_command (&child)) {
@@ -1106,14 +1106,14 @@ static int multi_pack_index_verify(const char *message)return 0 ; } -static int multi_pack_index_expire (void ) +static int multi_pack_index_expire (struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ;strvec_pushl (&child.args,"multi-pack-index" ,"expire" ,NULL ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--no-progress" );close_object_store (the_repository->objects);
@@ -1164,14 +1164,14 @@ static off_t get_auto_pack_size(void)return result_size; } -static int multi_pack_index_repack (void ) +static int multi_pack_index_repack (struct maintenance_opts *opts) {struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ;strvec_pushl (&child.args,"multi-pack-index" ,"repack" ,NULL ); -if (opts.quiet) +if (opts->quiet)strvec_push (&child.args,"--no-progress" ); strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
@@ -1185,7 +1185,7 @@ static int multi_pack_index_repack(void)return 0 ; } -static int maintenance_task_incremental_repack (void ) +static int maintenance_task_incremental_repack (struct maintenance_opts *opts) {prepare_repo_settings (the_repository);if (!the_repository->settings.core_multi_pack_index) {
@@ -1193,22 +1193,22 @@ static int maintenance_task_incremental_repack(void)return 0 ; } -if (multi_pack_index_write ()) +if (multi_pack_index_write (opts))return 1 ; -if (multi_pack_index_verify ("after initial write" )) -return rewrite_multi_pack_index (); -if (multi_pack_index_expire ()) +if (multi_pack_index_verify (opts,"after initial write" )) +return rewrite_multi_pack_index (opts); +if (multi_pack_index_expire (opts))return 1 ; -if (multi_pack_index_verify ("after expire step" )) -return !!rewrite_multi_pack_index (); -if (multi_pack_index_repack ()) +if (multi_pack_index_verify (opts,"after expire step" )) +return !!rewrite_multi_pack_index (opts); +if (multi_pack_index_repack (opts))return 1 ; -if (multi_pack_index_verify ("after repack step" )) -return !!rewrite_multi_pack_index (); +if (multi_pack_index_verify (opts,"after repack step" )) +return !!rewrite_multi_pack_index (opts);return 0 ; } -typedef int maintenance_task_fn (void ); +typedef int maintenance_task_fn (struct maintenance_opts *opts);/* * An auto condition function returns 1 if the task should run
@@ -1275,9 +1275,9 @@ static int compare_tasks_by_selection(const void *a_, const void *b_)return b->selected_order - a->selected_order; } -static int maintenance_run (void ) +static int maintenance_run (struct maintenance_opts *opts) { -int i; +int i, found_selected =0 ;int result =0 ;struct lock_file lk;struct repository *r = the_repository;
@@ -1291,7 +1291,7 @@ static int maintenance_run(void)* recursive process stack. Do not report an error in * that case. */ -if (!opts.auto_flag && !opts.quiet) +if (!opts->auto_flag && !opts->quiet)error (_ ("lock file '%s' exists, skipping maintenance" ), lock_path);free (lock_path);
@@ -1299,23 +1299,26 @@ static int maintenance_run(void) }free (lock_path); -if (opts.tasks_selected) +for (i =0 ; !found_selected && i <TASK__COUNT ; i++) + found_selected = tasks[i].selected; + +if (found_selected)QSORT (tasks,TASK__COUNT , compare_tasks_by_selection);for (i =0 ; i <TASK__COUNT ; i++) { -if (opts.tasks_selected && !tasks[i].selected) +if (found_selected && !tasks[i].selected)continue ; -if (!opts.tasks_selected && !tasks[i].enabled) +if (!found_selected && !tasks[i].enabled)continue ; -if (opts.auto_flag && +if (opts->auto_flag && (!tasks[i].auto_condition || !tasks[i].auto_condition ()))continue ;trace2_region_enter ("maintenance" , tasks[i].name, r); -if (tasks[i].fn ()) { +if (tasks[i].fn (opts)) {error (_ ("task '%s' failed" ), tasks[i].name); result =1 ; }
@@ -1349,17 +1352,15 @@ static void initialize_task_config(void)static int task_option_parse (const struct option *opt,const char *arg,int unset) { -int i; +int i, num_selected =0 ;struct maintenance_task *task =NULL ;BUG_ON_OPT_NEG (unset); - opts.tasks_selected++; -for (i =0 ; i <TASK__COUNT ; i++) { + num_selected += tasks[i].selected;if (!strcasecmp (tasks[i].name, arg)) { task = &tasks[i]; -break ; } }
@@ -1374,13 +1375,14 @@ static int task_option_parse(const struct option *opt, } task->selected =1 ; - task->selected_order = opts.tasks_selected; + task->selected_order = num_selected +1 ;return 0 ; }int cmd_maintenance (int argc,const char **argv,const char *prefix) { +static struct maintenance_opts opts;static struct option builtin_maintenance_options[] = {OPT_BOOL (0 ,"auto" , &opts.auto_flag,N_ ("run tasks based on the state of the repository" )),
@@ -1408,7 +1410,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)if (argc ==1 ) {if (!strcmp (argv[0 ],"run" )) -return maintenance_run (); +return maintenance_run (&opts); } usage_with_options(builtin_maintenance_usage,
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
In reply to Derrick StoleeQuoted 17 lines · Derrick Stolee
Quoted 16 lines · Jonathan Nieder
Quoted 3 lines · Derrick Stolee
Documentation/git-maintenance.txt new file +0
-0@@ -0,0 +1,57 @@[...]
Documentation/git-maintenance.txt line 33
Quoted 5 lines
Documentation/git-maintenance.txt new file +5
-0+SUBCOMMANDS +----------- + +run:: + Run one or more maintenance tasks.[jrnieder] How do I supply the tasks on the command line? Are they parameters to this subcommand? If so, it could make sense for this to say something like
Quoted 4 lines
Quoted 1 line
run <task>...::Hopefully this is documented to your satisfaction when the ability to customize the tasks is implemented.
I think my last reply to this part missed the point a little, so let me try again.
In multi-patch series, requests to make improvements to early parts that would be obsoleted by later parts may seem strange --- isn't this just wasted work? Sure, occasionally someone will stumble on the early part using "git bisect" or "git log", but isn't that rare enough to not be worth worrying too much about?
And I'd agree with that reaction --- for a documentation nit like this one, that benefit alone is probably not enough to be worth the fuss of tweaking things.
When I focus more on early parts of a series, it is for a different
reason: from a release management point of view, it is useful to get the early part of a series "cooked" as soon as possible so it can be exposed to users in "next" and then become a foundation for later patches in "master". So we try to get as much of the early part of a series into that state as we can, and to merge the early part even without the later part as soon as the former is ready and valuable enough to users on its own.
Relatedly, ensuring each commit is "complete" in this sense on its own gives us more flexibility in case a problem is discovered later: we can revert a patch that is causing issues without having to revert the preceding part.
I think that might be part of the context I didn't convey well, and why I want to get this in tip-top shape and start using it.
Thanks,
Jonathan
Re: [PATCH v2 01/18] maintenance: create basic maintenance runner
In reply to Derrick StoleeQuoted 6 lines · Derrick Stolee
Quoted 3 lines · Jonathan Nieder
Perhaps this is suggesting that we need some central test helper for parsing traces so we can do this reliably in one place. What do you think?
Quoted 7 lines
I'm specifically using
GIT_TRACE2_EVENT, which is intended for consumption by computer, not humans. Adding whitespace or otherwise changing the output format would be an unnecessary disruption that is more likely to have downstream effects with external tools.In some way, adding extra dependencies like this in our own test suite can help ensure the output format is more stable.
I've discussed this a bit more at [1]: I do think this is output that we cannot promise to keep stable. Can you say a little more about why extracting a helper would be problematic? For example, would doing that in a separate patch at the end of the series be workable for you?
More generally, one theme I'm sensing in the responses to the code review comments I sent is that the series may have gotten too long to be able to easily amend the beginning of it. To that end, I'm happy to make a reroll or make this easier in any other way I can. This also suggests to me that it may make sense to split off an early part of the series and get that into a shape you're comfortable with before rebasing the rest of the series on top as a separate topic (a tried and true method, used recently for the "git bugreport" command, for example).
Thoughts?
Thanks,
Jonathan
PATCH v2 2/18 maintenance: add --quiet option
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Maintenance activities are commonly used as steps in larger scripts. Providing a '--quiet' option allows those scripts to be less noisy when run on a terminal window. Turn this mode on by default when stderr is not a terminal.
Pipe the option to the 'git gc' child process.
--- Documentation/git-maintenance.txt | 3 +++ builtin/gc.c | 7 +++++++ t/t7900-maintenance.sh | 8 +++++--- 3 files changed, 15 insertions(+), 3 deletions(-)
Documentation/git-maintenance.txt +3 -0
@@ -52,6 +52,9 @@ OPTIONS in the `gc.auto` config setting, or when the number of pack-files exceeds the `gc.autoPackLimit` config setting. +--quiet:: + Do not report progress or other information over `stderr`. + GIT --- Part of the linkgit:git[1] suite
builtin/gc.c +7 -0
@@ -707,6 +707,7 @@ static const char * const builtin_maintenance_usage[] = {static struct maintenance_opts {int auto_flag; +int quiet; } opts;static int maintenance_task_gc (void )
@@ -718,6 +719,8 @@ static int maintenance_task_gc(void)if (opts.auto_flag)argv_array_pushl (&cmd,"--auto" ,NULL ); +if (opts.quiet) +argv_array_pushl (&cmd,"--quiet" ,NULL );close_object_store (the_repository->objects); result =run_command_v_opt (cmd.argv,RUN_GIT_CMD );
@@ -736,6 +739,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)static struct option builtin_maintenance_options[] = {OPT_BOOL (0 ,"auto" , &opts.auto_flag,N_ ("run tasks based on the state of the repository" )), +OPT_BOOL (0 ,"quiet" , &opts.quiet, +N_ ("do not report progress or other information over stderr" )),OPT_END () };
@@ -745,6 +750,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)usage_with_options (builtin_maintenance_usage, builtin_maintenance_options); + opts.quiet = !isatty (2 ); + argc = parse_options(argc, argv, prefix, builtin_maintenance_options, builtin_maintenance_usage,
t/t7900-maintenance.sh +5 -3
@@ -12,11 +12,13 @@ test_expect_success 'help text' ' test_i18ngrep "usage: git maintenance run" err ' -test_expect_success 'gc [--auto]' ' - GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run && +test_expect_success 'gc [--auto|--quiet]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run --no-quiet && GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && + GIT_TRACE2_EVENT="$(pwd)/run-quiet.txt" git maintenance run --quiet && grep ",\"gc\"]" run-no-auto.txt && - grep ",\"gc\",\"--auto\"]" run-auto.txt + grep ",\"gc\",\"--auto\"" run-auto.txt && + grep ",\"gc\",\"--quiet\"" run-quiet.txt ' test_done
Signature · 1 line
gitgitgadget
PATCH v2 3/18 maintenance: replace run_auto_gc()
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The run_auto_gc() method is used in several places to trigger a check for repo maintenance after some Git commands, such as 'git commit' or 'git fetch'.
To allow for extra customization of this maintenance activity, replace the 'git gc --auto [--quiet]' call with one to 'git maintenance run --auto [--quiet]'. As we extend the maintenance builtin with other steps, users will be able to select different maintenance activities.
Rename run_auto_gc() to run_auto_maintenance() to be clearer what is happening on this call, and to expose all callers in the current diff.
Since 'git fetch' already allows disabling the 'git gc --auto' subprocess, add an equivalent option with a different name to be more descriptive of the new behavior: '--[no-]maintenance'. Update the documentation to include these options at the same time.
--- Documentation/fetch-options.txt | 5 +++-- Documentation/git-clone.txt | 7 ++++--- builtin/am.c | 2 +- builtin/commit.c | 2 +- builtin/fetch.c | 6 ++++-- builtin/merge.c | 2 +- builtin/rebase.c | 4 ++-- run-command.c | 7 +++++-- run-command.h | 2 +- t/t5510-fetch.sh | 2 +- 10 files changed, 23 insertions(+), 16 deletions(-)
Documentation/fetch-options.txt +3 -2
@@ -86,9 +86,10 @@ ifndef::git-pull[] Allow several <repository> and <group> arguments to be specified. No <refspec>s may be specified. +--[no-]maintenance:: --[no-]auto-gc:: - Run `git gc --auto` at the end to perform garbage collection - if needed. This is enabled by default. + Run `git maintenance run --auto` at the end to perform garbage + collection if needed. This is enabled by default. --[no-]write-commit-graph:: Write a commit-graph after fetching. This overrides the config
Documentation/git-clone.txt +4 -3
@@ -78,9 +78,10 @@ repository using this option and then delete branches (or use any other Git command that makes any existing commit unreferenced) in the source repository, some objects may become unreferenced (or dangling). These objects may be removed by normal Git operations (such as `git commit`) -which automatically call `git gc --auto`. (See linkgit:git-gc[1].) -If these objects are removed and were referenced by the cloned repository, -then the cloned repository will become corrupt. +which automatically call `git maintenance run --auto` and `git gc --auto`. +(See linkgit:git-maintenance[1] and linkgit:git-gc[1].) If these objects +are removed and were referenced by the cloned repository, then the cloned +repository will become corrupt. + Note that running `git repack` without the `--local` option in a repository cloned with `--shared` will copy objects from the source repository into a pack
builtin/am.c +1 -1
@@ -1795,7 +1795,7 @@ static void am_run(struct am_state *state, int resume)if (!state->rebasing) {am_destroy (state);close_object_store (the_repository->objects); -run_auto_gc (state->quiet); +run_auto_maintenance (state->quiet); } }
builtin/commit.c +1 -1
@@ -1702,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)git_test_write_commit_graph_or_die ();repo_rerere (the_repository,0 ); -run_auto_gc (quiet); +run_auto_maintenance (quiet);run_commit_hook (use_editor,get_index_file (),"post-commit" ,NULL );if (amend && !no_post_rewrite) {commit_post_rewrite (the_repository, current_head, &oid);
builtin/fetch.c +4 -2
@@ -196,8 +196,10 @@ static struct option builtin_fetch_options[] = {OPT_STRING_LIST (0 ,"negotiation-tip" , &negotiation_tip,N_ ("revision" ),N_ ("report that we have only objects reachable from this object" )),OPT_PARSE_LIST_OBJECTS_FILTER (&filter_options), +OPT_BOOL (0 ,"maintenance" , &enable_auto_gc, +N_ ("run 'maintenance --auto' after fetching" )),OPT_BOOL (0 ,"auto-gc" , &enable_auto_gc, -N_ ("run 'gc --auto' after fetching" )), +N_ ("run 'maintenance --auto' after fetching" )),OPT_BOOL (0 ,"show-forced-updates" , &fetch_show_forced_updates,N_ ("check for forced-updates on all updated branches" )), OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph,
@@ -1882,7 +1884,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)close_object_store (the_repository->objects);if (enable_auto_gc) -run_auto_gc (verbosity <0 ); +run_auto_maintenance (verbosity <0 );return result; }
builtin/merge.c +1 -1
@@ -457,7 +457,7 @@ static void finish(struct commit *head_commit,* user should see them. */ close_object_store (the_repository->objects); -run_auto_gc (verbosity <0 ); +run_auto_maintenance (verbosity <0 ); } }if (new_head && show_diffstat) {
builtin/rebase.c +2 -2
@@ -728,10 +728,10 @@ static int finish_rebase(struct rebase_options *opts)apply_autostash (state_dir_path ("autostash" , opts));close_object_store (the_repository->objects);/* -* We ignore errors in 'gc --auto', since the +* We ignore errors in 'git maintenance run --auto', since the * user should see them. */ -run_auto_gc (!(opts->flags & (REBASE_NO_QUIET |REBASE_VERBOSE ))); +run_auto_maintenance (!(opts->flags & (REBASE_NO_QUIET |REBASE_VERBOSE )));if (opts->type ==REBASE_MERGE ) {struct replay_opts replay =REPLAY_OPTS_INIT ;
run-command.c +5 -2
@@ -1865,14 +1865,17 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,return result; } -int run_auto_gc (int quiet) +int run_auto_maintenance (int quiet) {struct argv_array argv_gc_auto =ARGV_ARRAY_INIT ;int status; -argv_array_pushl (&argv_gc_auto,"gc" ,"--auto" ,NULL ); +argv_array_pushl (&argv_gc_auto,"maintenance" ,"run" ,"--auto" ,NULL );if (quiet)argv_array_push (&argv_gc_auto,"--quiet" ); +else +argv_array_push (&argv_gc_auto,"--no-quiet" ); + status =run_command_v_opt (argv_gc_auto.argv,RUN_GIT_CMD );argv_array_clear (&argv_gc_auto);return status;
run-command.h +1 -1
@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);/* * Trigger an auto-gc */ -int run_auto_gc (int quiet); +int run_auto_maintenance (int quiet);#define RUN_COMMAND_NO_STDIN 1#define RUN_GIT_CMD 2/*If this is to be git sub-command */
t/t5510-fetch.sh +1 -1
@@ -919,7 +919,7 @@ test_expect_success 'fetching with auto-gc does not lock up' ' git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && - GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 && + GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 && test_i18ngrep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out )
Signature · 1 line
gitgitgadget
Re: [PATCH v2 03/18] maintenance: replace run_auto_gc()
Documentation/fetch-options.txt ifndef::git-pull[] · line 92
Quoted 6 lines · Derrick Stolee via GitGitGadget
+--[no-]maintenance::
--[no-]auto-gc::Documentation/fetch-options.txt +2
-2- Run `git gc --auto` at the end to perform garbage collection - if needed. This is enabled by default. + Run `git maintenance run --auto` at the end to perform garbage + collection if needed. This is enabled by default.
Shouldn't the new synonym be called --auto-maintenance or an abbreviation thereof? It is not like we will run the full maintenance suite when "--no-maintenance" is omitted, which certainly is not the impression we want to give our readers.
Documentation/git-clone.txt repository using this option and then delete branches (or use any · line 81
Quoted 5 lines
These objects may be removed by normal Git operations (such as
git commit)Documentation/git-clone.txt +1
-3-which automatically call `git gc --auto`. (See linkgit:git-gc[1].) -If these objects are removed and were referenced by the cloned repository, -then the cloned repository will become corrupt. +which automatically call `git maintenance run --auto` and `git gc --auto`.
Hmph. Perhaps the picture may change in the end of the series but I got an impression that "gc --auto" would eventually become just part of "maintenance --auto" and the users won't have to be even aware of its existence? Wouldn't we rather want to say something like
--[no-]auto-maintenance::
--[no-]auto-gc::
Run git maintenance run --auto at the end to perform garbage collection if needed (--[no-]auto-gc is a synonym). This is enabled by default.
builtin/fetch.c static struct option builtin_fetch_options[] = { · line 203
Quoted 13 lines
builtin/fetch.c +3
-0@@ -196,8 +196,10 @@ static struct option builtin_fetch_options[] = {OPT_STRING_LIST (0 ,"negotiation-tip" , &negotiation_tip,N_ ("revision" ),N_ ("report that we have only objects reachable from this object" )),OPT_PARSE_LIST_OBJECTS_FILTER (&filter_options), +OPT_BOOL (0 ,"maintenance" , &enable_auto_gc, +N_ ("run 'maintenance --auto' after fetching" )),OPT_BOOL (0 ,"auto-gc" , &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")),
OK, so this is truly a backward-compatible synonym at this point.
Quoted 14 lines
run-command.c +1
-1@@ -1865,14 +1865,17 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,return result; } -int run_auto_gc (int quiet) +int run_auto_maintenance (int quiet) {struct argv_array argv_gc_auto =ARGV_ARRAY_INIT ;int status;
run-command.c int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task, · line 1881
Quoted 10 lines
run-command.c +4
-1-argv_array_pushl (&argv_gc_auto,"gc" ,"--auto" ,NULL ); +argv_array_pushl (&argv_gc_auto,"maintenance" ,"run" ,"--auto" ,NULL );if (quiet)argv_array_push (&argv_gc_auto,"--quiet" ); +else +argv_array_push (&argv_gc_auto,"--no-quiet" ); + status =run_command_v_opt (argv_gc_auto.argv,RUN_GIT_CMD );argv_array_clear (&argv_gc_auto);return status;
Don't we want to replace all _gc_ with _maintenance_ in this function? I think the first business before we can do so would be to rethink if spelling out "maintenance" fully in code is a good idea in the first space. It would make names for variables, structures and fields unnecessarily long without contributing to ease of understanding an iota, and a easy-to-remember short-form or an abbreviation may be needed. Using a short-form/abbreviation wouldn't worsen the end-user experience, and not the developer experience for that matter.
If we choose "gc" as the short-hand, most of the change in this step would become unnecessary. I also do not mind if we some other words or word-fragment (perhaps "maint"???) is chosen.
Quoted 26 lines
run-command.h +1
-1@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);/* * Trigger an auto-gc */ -int run_auto_gc (int quiet); +int run_auto_maintenance (int quiet);#define RUN_COMMAND_NO_STDIN 1#define RUN_GIT_CMD 2/*If this is to be git sub-command */ t/t5510-fetch.sh +1
-1@@ -919,7 +919,7 @@ test_expect_success 'fetching with auto-gc does not lock up' ' git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && - GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 && + GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 && test_i18ngrep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out )
Re: [PATCH v2 03/18] maintenance: replace run_auto_gc()
Quoted 40 lines · Junio C Hamano
Documentation/fetch-options.txt ifndef::git-pull[] · line 92
Quoted 6 lines · Derrick Stolee via GitGitGadget
+--[no-]maintenance::
--[no-]auto-gc::Documentation/fetch-options.txt +2
-2- Run `git gc --auto` at the end to perform garbage collection - if needed. This is enabled by default. + Run `git maintenance run --auto` at the end to perform garbage + collection if needed. This is enabled by default.Shouldn't the new synonym be called --auto-maintenance or an abbreviation thereof? It is not like we will run the full maintenance suite when "--no-maintenance" is omitted, which certainly is not the impression we want to give our readers.
Documentation/git-clone.txt repository using this option and then delete branches (or use any · line 81
Quoted 5 lines
These objects may be removed by normal Git operations (such as
git commit)Documentation/git-clone.txt +1
-3-which automatically call `git gc --auto`. (See linkgit:git-gc[1].) -If these objects are removed and were referenced by the cloned repository, -then the cloned repository will become corrupt. +which automatically call `git maintenance run --auto` and `git gc --auto`.Hmph. Perhaps the picture may change in the end of the series but I got an impression that "gc --auto" would eventually become just part of "maintenance --auto" and the users won't have to be even aware of its existence? Wouldn't we rather want to say something like
--[no-]auto-maintenance::
--[no-]auto-gc::
Rungit maintenance run --autoat the end to perform garbage collection if needed (--[no-]auto-gcis a synonym). This is enabled by default.Quoted 8 lines
builtin/fetch.c +0
-0@@ -196,8 +196,10 @@ static struct option builtin_fetch_options[] = {OPT_STRING_LIST (0 ,"negotiation-tip" , &negotiation_tip,N_ ("revision" ),N_ ("report that we have only objects reachable from this object" )), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
Quoted 6 lines
builtin/fetch.c static struct option builtin_fetch_options[] = { · line 202
Quoted 4 lines
builtin/fetch.c +3
-0+OPT_BOOL (0 ,"maintenance" , &enable_auto_gc, +N_ ("run 'maintenance --auto' after fetching" )),OPT_BOOL (0 ,"auto-gc" , &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")),OK, so this is truly a backward-compatible synonym at this point.
I wouldn't be opposed to making the 'auto-gc' option an 'OPT_HIDDEN_BOOL', but I realize that users may not want to move as quickly as that. Perhaps we should visit this in a couple of releases (or perhaps you are getting to it in a later patch that I haven't read yet).
Quoted 35 lines
Quoted 14 lines
run-command.c +1
-1@@ -1865,14 +1865,17 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,return result; } -int run_auto_gc (int quiet) +int run_auto_maintenance (int quiet) {struct argv_array argv_gc_auto =ARGV_ARRAY_INIT ;int status;run-command.c int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task, · line 1881
Quoted 10 lines
run-command.c +4
-1-argv_array_pushl (&argv_gc_auto,"gc" ,"--auto" ,NULL ); +argv_array_pushl (&argv_gc_auto,"maintenance" ,"run" ,"--auto" ,NULL );if (quiet)argv_array_push (&argv_gc_auto,"--quiet" ); +else +argv_array_push (&argv_gc_auto,"--no-quiet" ); + status =run_command_v_opt (argv_gc_auto.argv,RUN_GIT_CMD );argv_array_clear (&argv_gc_auto);return status;Don't we want to replace all _gc_ with _maintenance_ in this function? I think the first business before we can do so would be to rethink if spelling out "maintenance" fully in code is a good idea in the first space. It would make names for variables, structures and fields unnecessarily long without contributing to ease of understanding an iota, and a easy-to-remember short-form or an abbreviation may be needed. Using a short-form/abbreviation wouldn't worsen the end-user experience, and not the developer experience for that matter.
Quoted 3 lines
If we choose "gc" as the short-hand, most of the change in this step would become unnecessary. I also do not mind if we some other words or word-fragment (perhaps "maint"???) is chosen.
Yeah, writing out 'maintenance' every time in the code and in command-line arguments is kind of a mouthful. I'm more willing to accept that '--maintenance' is something that users would write or script around, but 'maint' makes sense to me as a shorthand in the code.
I could go either way on calling the command-line flag '--maint', though.
Quoted 12 lines
Quoted 12 lines
run-command.h +1
-1@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);/* * Trigger an auto-gc */ -int run_auto_gc (int quiet); +int run_auto_maintenance (int quiet);
Quoted 15 lines
Quoted 7 lines
run-command.h +0
-0#define RUN_COMMAND_NO_STDIN 1#define RUN_GIT_CMD 2/*If this is to be git sub-command */ t/t5510-fetch.sh +0
-0t/t5510-fetch.sh test_expect_success 'fetching with auto-gc does not lock up' ' · line 925
Quoted 8 lines
t/t5510-fetch.sh +1
-1@@ -919,7 +919,7 @@ test_expect_success 'fetching with auto-gc does not lock up' ' git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && - GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 && + GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 && test_i18ngrep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out )
Thanks,
Taylor
Re: [PATCH v2 03/18] maintenance: replace run_auto_gc()
In reply to Junio C HamanoQuoted 13 lines · Junio C Hamano
Documentation/fetch-options.txt ifndef::git-pull[] · line 92
Quoted 6 lines · Derrick Stolee via GitGitGadget
+--[no-]maintenance::
--[no-]auto-gc::Documentation/fetch-options.txt +2
-2- Run `git gc --auto` at the end to perform garbage collection - if needed. This is enabled by default. + Run `git maintenance run --auto` at the end to perform garbage + collection if needed. This is enabled by default.Shouldn't the new synonym be called --auto-maintenance or an abbreviation thereof? It is not like we will run the full maintenance suite when "--no-maintenance" is omitted, which certainly is not the impression we want to give our readers.
Adding "auto-" to the argument can be informative.
I would think that abbreviating the option may make understanding the argument more difficult for users where English is not their first language. Of course, I'm a bad judge of that.
I also don't think this option is called often by users at the command-line and instead is done by scripts and third-party tools. Abbreviating the word may have less value there.
(This reminds me that is would be good to have a config option that prevents running this process _at all_. Sure, gc.auto=0 prevents the command from doing anything, but there is still an extra cost of starting a process. This is more of a problem on Windows. Making a note to self here.)
Quoted 11 lines
Documentation/git-clone.txt repository using this option and then delete branches (or use any · line 81
Quoted 5 lines
These objects may be removed by normal Git operations (such as
git commit)Documentation/git-clone.txt +1
-3-which automatically call `git gc --auto`. (See linkgit:git-gc[1].) -If these objects are removed and were referenced by the cloned repository, -then the cloned repository will become corrupt. +which automatically call `git maintenance run --auto` and `git gc --auto`.Hmph. Perhaps the picture may change in the end of the series but I got an impression that "gc --auto" would eventually become just part of "maintenance --auto" and the users won't have to be even aware of its existence? Wouldn't we rather want to say something like
Quoted 5 lines
--[no-]auto-maintenance::
--[no-]auto-gc::
Rungit maintenance run --autoat the end to perform garbage collection if needed (--[no-]auto-gcis a synonym). This is enabled by default.
I don't completely eliminate 'git gc' at the end of this series, but instead hope that we can peel it apart slowly in follow-up series. However, you are correct that I should be more careful to obliterate it from the documentation instead of keeping references around.
Quoted 51 lines
builtin/fetch.c static struct option builtin_fetch_options[] = { · line 203
Quoted 13 lines
builtin/fetch.c +3
-0@@ -196,8 +196,10 @@ static struct option builtin_fetch_options[] = {OPT_STRING_LIST (0 ,"negotiation-tip" , &negotiation_tip,N_ ("revision" ),N_ ("report that we have only objects reachable from this object" )),OPT_PARSE_LIST_OBJECTS_FILTER (&filter_options), +OPT_BOOL (0 ,"maintenance" , &enable_auto_gc, +N_ ("run 'maintenance --auto' after fetching" )),OPT_BOOL (0 ,"auto-gc" , &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")),OK, so this is truly a backward-compatible synonym at this point.
Quoted 14 lines
run-command.c +1
-1@@ -1865,14 +1865,17 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,return result; } -int run_auto_gc (int quiet) +int run_auto_maintenance (int quiet) {struct argv_array argv_gc_auto =ARGV_ARRAY_INIT ;int status;run-command.c int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task, · line 1881
Quoted 10 lines
run-command.c +4
-1-argv_array_pushl (&argv_gc_auto,"gc" ,"--auto" ,NULL ); +argv_array_pushl (&argv_gc_auto,"maintenance" ,"run" ,"--auto" ,NULL );if (quiet)argv_array_push (&argv_gc_auto,"--quiet" ); +else +argv_array_push (&argv_gc_auto,"--no-quiet" ); + status =run_command_v_opt (argv_gc_auto.argv,RUN_GIT_CMD );argv_array_clear (&argv_gc_auto);return status;Don't we want to replace all _gc_ with _maintenance_ in this function? I think the first business before we can do so would be to rethink if spelling out "maintenance" fully in code is a good idea in the first space. It would make names for variables, structures and fields unnecessarily long without contributing to ease of understanding an iota, and a easy-to-remember short-form or an abbreviation may be needed. Using a short-form/abbreviation wouldn't worsen the end-user experience, and not the developer experience for that matter.
Quoted 3 lines
If we choose "gc" as the short-hand, most of the change in this step would become unnecessary. I also do not mind if we some other words or word-fragment (perhaps "maint"???) is chosen.
Yes, I should have noticed that. Also, with Peff's feedback from another thread, the method can look a bit simpler this way:
int run_auto_maintenance(int quiet)
{
struct child_process maint = CHILD_PROCESS_INIT;
maint.git_cmd = 1;
argv_array_pushl(&maint.argv, "maintenance", "run", "--auto", NULL);
if (quiet)
argv_array_push(&maint.argv, "--quiet");
else
argv_array_push(&maint.argv, "--no-quiet");
return run_command(&maint.argv);
}(I will update it again to work on Peff's argv_array work, but hopefully it is clear how this is simpler.)
Thanks,
-Stolee
Re: [PATCH v2 03/18] maintenance: replace run_auto_gc()
Quoted 11 lines · Derrick Stolee
Quoted 10 lines · Junio C Hamano
Don't we want to replace all _gc_ with _maintenance_ in this function? I think the first business before we can do so would be to rethink if spelling out "maintenance" fully in code is a good idea in the first space. It would make names for variables, structures and fields unnecessarily long without contributing to ease of understanding an iota, and a easy-to-remember short-form or an abbreviation may be needed. Using a short-form/abbreviation wouldn't worsen the end-user experience, and not the developer experience for that matter.
Quoted 6 lines
Quoted 3 lines
If we choose "gc" as the short-hand, most of the change in this step would become unnecessary. I also do not mind if we some other words or word-fragment (perhaps "maint"???) is chosen.
Yes, I should have noticed that. Also, with Peff's feedback from another thread, the method can look a bit simpler this way:
It would help if I actually _compile_ code before sending it. Here is the fixed version:
int run_auto_maintenance(int quiet)
{
struct child_process maint = CHILD_PROCESS_INIT;
maint.git_cmd = 1;
argv_array_pushl(&maint.args, "maintenance", "run", "--auto", NULL);
if (quiet)
argv_array_push(&maint.args, "--quiet");
else
argv_array_push(&maint.args, "--no-quiet");
return run_command(&maint);
}Re: [PATCH v2 03/18] maintenance: replace run_auto_gc()
Quoted 10 lines · Derrick Stolee
int
run_auto_maintenance(int quiet){ struct child_process maint = CHILD_PROCESS_INIT; maint.git_cmd = 1; argv_array_pushl(&maint.args, "maintenance", "run", "--auto", NULL); if (quiet) argv_array_push(&maint.args, "--quiet"); else argv_array_push(&maint.args, "--no-quiet");
It's subjective, but this might be a good fit for the ternary operator:
argv_array_pushl(&maint.args, "maintenance", "run", "--auto", NULL);
argv_array_push(&maint.args, quiet ? "--quiet" : "--no-quiet");Or even:
argv_array_pushl(&maint.args, "maintenance", "run", "--auto",
quiet ? "--quiet" : "--no-quiet", NULL);The latter is a bit less readable to me.
Re: [PATCH v2 03/18] maintenance: replace run_auto_gc()
Quoted 12 lines · Eric Sunshine
Quoted 10 lines · Derrick Stolee
int
run_auto_maintenance(int quiet){ struct child_process maint = CHILD_PROCESS_INIT; maint.git_cmd = 1; argv_array_pushl(&maint.args, "maintenance", "run", "--auto", NULL); if (quiet) argv_array_push(&maint.args, "--quiet"); else argv_array_push(&maint.args, "--no-quiet");
Quoted 4 lines
It's subjective, but this might be a good fit for the ternary operator:
argv_array_pushl(&maint.args, "maintenance", "run", "--auto", NULL); argv_array_push(&maint.args, quiet ? "--quiet" : "--no-quiet");
Good idea! Thanks.
Quoted 6 lines
Or even:
argv_array_pushl(&maint.args, "maintenance", "run", "--auto", quiet ? "--quiet" : "--no-quiet", NULL);The latter is a bit less readable to me.
I agree.
-Stolee
PATCH v2 4/18 maintenance: initialize task array
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
In anticipation of implementing multiple maintenance tasks inside the 'maintenance' builtin, use a list of structs to describe the work to be done.
The struct maintenance_task stores the name of the task (as given by a future command-line argument) along with a function pointer to its implementation and a boolean for whether the step is enabled.
A list of pointers to these structs are initialized with the full list of implemented tasks along with a default order. For now, this list only contains the "gc" task. This task is also the only task enabled by default.
--- builtin/gc.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-)
builtin/gc.c +38 -1
@@ -700,6 +700,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)return 0 ; } +#define MAX_NUM_TASKS 1 +static const char *const builtin_maintenance_usage[] = {N_ ("git maintenance run [<options>]" ),NULL
@@ -729,9 +731,43 @@ static int maintenance_task_gc(void)return result; } +typedef int maintenance_task_fn (void ); + +struct maintenance_task { +const char *name; +maintenance_task_fn *fn; +unsigned enabled:1 ; +}; + +static struct maintenance_task *tasks[MAX_NUM_TASKS ]; +static int num_tasks; +static int maintenance_run (void ) { -return maintenance_task_gc (); +int i; +int result =0 ; + +for (i =0 ; !result && i < num_tasks; i++) { +if (!tasks[i]->enabled) +continue ; + result = tasks[i]->fn (); + } + +return result; +} + +static void initialize_tasks (void ) +{ +int i; + num_tasks =0 ; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) + tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task )); + + tasks[num_tasks]->name ="gc" ; + tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->enabled =1 ; + num_tasks++; }int cmd_maintenance (int argc,const char **argv,const char *prefix)
@@ -751,6 +787,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix) builtin_maintenance_options); opts.quiet = !isatty (2 ); +initialize_tasks (); argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
Signature · 1 line
gitgitgadget
Re: [PATCH v2 04/18] maintenance: initialize task array
builtin/gc.c static int maintenance_task_gc(void) · line 770
Quoted 12 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +12
-0+static void initialize_tasks (void ) +{ +int i; + num_tasks =0 ; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) + tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task )); + + tasks[num_tasks]->name ="gc" ; + tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->enabled =1 ; + num_tasks++;
Are we going to have 47 different tasks initialized by code like this in the future? I would have expected that you'd have a table of tasks that serves as the blueprint copy and copy it to the table to be used if there is some need to mutate the table-to-be-used.
Quoted 11 lines
}
int
cmd_maintenance(int argc, const char **argv, const char *prefix)builtin/gc.c +1
-0@@ -751,6 +787,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix) builtin_maintenance_options); opts.quiet = !isatty (2 ); +initialize_tasks (); argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
Re: [PATCH v2 04/18] maintenance: initialize task array
Quoted 11 lines · Junio C Hamano
Quoted 9 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +9
-0+static void initialize_tasks (void ) +{ +int i; + num_tasks =0 ; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) + tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task )); + + tasks[num_tasks]->name ="gc" ;
Quoted 8 lines
builtin/gc.c static int maintenance_task_gc(void) · line 770
Quoted 3 lines
builtin/gc.c +3
-0+ tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->enabled =1 ; + num_tasks++;Are we going to have 47 different tasks initialized by code like this in the future? I would have expected that you'd have a table of tasks that serves as the blueprint copy and copy it to the table to be used if there is some need to mutate the table-to-be-used.
Making it a table will likely make it easier to read. I hadn't thought of it.
At the start, I thought that the diff would look awful as we add members to the struct. However, the members that are not specified are set to zero, so I should be able to craft this into something not too terrible.
Thanks,
-Stolee
Re: [PATCH v2 04/18] maintenance: initialize task array
Quoted 21 lines · Derrick Stolee
Quoted 11 lines · Junio C Hamano
Quoted 9 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +9
-0+static void initialize_tasks (void ) +{ +int i; + num_tasks =0 ; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) + tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task )); + + tasks[num_tasks]->name ="gc" ;Quoted 8 lines
builtin/gc.c static int maintenance_task_gc(void) · line 770
Quoted 3 lines
builtin/gc.c +3
-0+ tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->enabled =1 ; + num_tasks++;Are we going to have 47 different tasks initialized by code like this in the future? I would have expected that you'd have a table of tasks that serves as the blueprint copy and copy it to the table to be used if there is some need to mutate the table-to-be-used.
Quoted 7 lines
Making it a table will likely make it easier to read. I hadn't thought of it.
At the start, I thought that the diff would look awful as we add members to the struct. However, the members that are not specified are set to zero, so I should be able to craft this into something not too terrible.
OK, my attempt has led to this final table:
const struct maintenance_task default_tasks[] = {
{
"prefetch",
maintenance_task_prefetch,
},
{
"loose-objects",
maintenance_task_loose_objects,
loose_object_auto_condition,
},
{
"incremental-repack",
maintenance_task_incremental_repack,
incremental_repack_auto_condition,
},
{
"gc",
maintenance_task_gc,
need_to_gc,
1,
},
{
"commit-graph",
maintenance_task_commit_graph,
should_write_commit_graph,
}
};
num_tasks = sizeof(default_tasks) / sizeof(struct maintenance_task);This is followed by allocating and copying the data to the 'tasks' array, allowing it to be sorted and modified according to command-line arguments and config.
Is this what you intended?
Thanks,
-Stolee
Re: [PATCH v2 04/18] maintenance: initialize task array
Quoted 16 lines · Derrick Stolee
OK, my attempt has led to this final table:
const struct maintenance_task default_tasks[] = { { "prefetch", maintenance_task_prefetch, }, ... { "commit-graph", maintenance_task_commit_graph, should_write_commit_graph, } }; num_tasks = sizeof(default_tasks) / sizeof(struct maintenance_task);
Quoted 5 lines
This is followed by allocating and copying the data to the 'tasks' array, allowing it to be sorted and modified according to command-line arguments and config.
Is this what you intended?
I do not know how important it is for your overall design to keep the blueprint/master-copy table that is separate from the working copy of the table that gets sorted, enabled/chosen bit set, etc. IIUC, you were modifying the entries' fields at runtime, so perhaps a pristine copy is not all that important (in which case you can just lose "const" and do without extra copying)? I dunno.
Re: [PATCH v2 04/18] maintenance: initialize task array
In reply to Derrick StoleeQuoted 61 lines · Derrick Stolee
Quoted 21 lines · Derrick Stolee
Quoted 11 lines · Junio C Hamano
Quoted 9 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +9
-0+static void initialize_tasks (void ) +{ +int i; + num_tasks =0 ; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) + tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task )); + + tasks[num_tasks]->name ="gc" ;Quoted 8 lines
builtin/gc.c static int maintenance_task_gc(void) · line 770
Quoted 3 lines
builtin/gc.c +3
-0+ tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->enabled =1 ; + num_tasks++;Are we going to have 47 different tasks initialized by code like this in the future? I would have expected that you'd have a table of tasks that serves as the blueprint copy and copy it to the table to be used if there is some need to mutate the table-to-be-used.
Quoted 7 lines
Making it a table will likely make it easier to read. I hadn't thought of it.
At the start, I thought that the diff would look awful as we add members to the struct. However, the members that are not specified are set to zero, so I should be able to craft this into something not too terrible.
OK, my attempt has led to this final table:
const struct maintenance_task default_tasks[] = { { "prefetch", maintenance_task_prefetch, }, { "loose-objects", maintenance_task_loose_objects, loose_object_auto_condition, }, { "incremental-repack", maintenance_task_incremental_repack, incremental_repack_auto_condition, }, { "gc", maintenance_task_gc, need_to_gc, 1, }, { "commit-graph", maintenance_task_commit_graph, should_write_commit_graph, } }; num_tasks = sizeof(default_tasks) / sizeof(struct maintenance_task);
Quoted 5 lines
This is followed by allocating and copying the data to the 'tasks' array, allowing it to be sorted and modified according to command-line arguments and config.
Is this what you intended?
I'm not sure if Junio intended what I'm going to suggest, but I think that you could make looking up these "blueprint" tasks a little easier by using the designated index initializer. For what it's worth, I wasn't sure if we allow this in the codebase, but some quick perusing through Documentation/CodingGuidelines turns up 512f41cfac (clean.c: use designated initializer, 2017-07-14), which does use this style.
Maybe something like:
enum maintenance_task_kind {
TASK_PREFETCH = 0,
TASK_LOOSE_OBJECTS,
/* ... */
TASK__COUNT
};
const struct maintenance_task default_tasks[TASK__COUNT] = {
[TASK_PREFETCH] = {
"prefetch",
maintenance_task_prefetch,
},
[...] = ...
};and then you should be able to pick those out with 'default_tasks[TASK_PREFETCH]'. I'm not sure if you are able to rely on those tasks appearing in a certain order in which case you can feel free to discard this suggestion.
If nothing else, I'm glad that we can use the '[...] = '-style initializers :-).
Quoted 2 lines
Thanks,
-Stolee
Thanks,
Taylor
Re: [PATCH v2 04/18] maintenance: initialize task array
In reply to Derrick Stolee via GitGitGadgetbuiltin/gc.c static int maintenance_task_gc(void) · line 765
Quoted 7 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +7
-0+static void initialize_tasks (void ) +{ +int i; + num_tasks =0 ; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) + tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task ));
[jonathan tan] I wonder why this is an array of pointers, not of objects? If they were objects, we could use an initializer. But in a later commit I see the array is sorted, OK.
Quoted 5 lines
builtin/gc.c +5
-0+ + tasks[num_tasks]->name ="gc" ; + tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->enabled =1 ; + num_tasks++;
PATCH v2 5/18 maintenance: add commit-graph task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The first new task in the 'git maintenance' builtin is the 'commit-graph' job. It is based on the sequence of events in the 'commit-graph' job in Scalar [1]. This sequence is as follows:
1. git commit-graph write --reachable --split
2. git commit-graph verify --shallow
3. If the verify succeeds, stop.
4. Delete the commit-graph-chain file.
5. git commit-graph write --reachable --split
By writing an incremental commit-graph file using the "--split" option we minimize the disruption from this operation. The default behavior is to merge layers until the new "top" layer is less than half the size of the layer below. This provides quick writes most of the time, with the longer writes following a power law distribution.
Most importantly, concurrent Git processes only look at the commit-graph-chain file for a very short amount of time, so they will verly likely not be holding a handle to the file when we try to replace it. (This only matters on Windows.)
If a concurrent process reads the old commit-graph-chain file, but our job expires some of the .graph files before they can be read, then those processes will see a warning message (but not fail). This could be avoided by a future update to use the --expire-time argument when writing the commit-graph.
By using 'git commit-graph verify --shallow' we can ensure that the file we just wrote is valid. This is an extra safety precaution that is faster than our 'write' subcommand. In the rare situation that the newest layer of the commit-graph is corrupt, we can "fix" the corruption by deleting the commit-graph-chain file and rewrite the full commit-graph as a new one-layer commit graph. This does not completely prevent _that_ file from being corrupt, but it does recompute the commit-graph by parsing commits from the object database. In our use of this step in Scalar and VFS for Git, we have only seen this issue arise because our microsoft/git fork reverted 43d3561 ("commit-graph write: don't die if the existing graph is corrupt" 2019-03-25) for a while to keep commit-graph writes very fast. We dropped the revert when updating to v2.23.0. The verify still has potential for catching corrupt data across the layer boundary: if the new file has commit X with parent Y in an old file but the commit ID for Y in the old file had a bitswap, then we will notice that in the 'verify' command.
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/CommitGraphStep.cs
--- Documentation/git-maintenance.txt | 18 ++++++++ builtin/gc.c | 74 ++++++++++++++++++++++++++++++- commit-graph.c | 8 ++-- commit-graph.h | 1 + t/t7900-maintenance.sh | 2 +- 5 files changed, 97 insertions(+), 6 deletions(-)
Documentation/git-maintenance.txt +18 -0
@@ -35,6 +35,24 @@ run:: TASKS ----- +commit-graph:: + The `commit-graph` job updates the `commit-graph` files incrementally, + then verifies that the written data is correct. If the new layer has an + issue, then the chain file is removed and the `commit-graph` is + rewritten from scratch. ++ +The verification only checks the top layer of the `commit-graph` chain. +If the incremental write merged the new commits with at least one +existing layer, then there is potential for on-disk corruption being +carried forward into the new file. This will be noticed and the new +commit-graph file will be clean as Git reparses the commit data from +the object database. ++ +The incremental write is safe to run alongside concurrent Git processes +since it will not expire `.graph` files that were in the previous +`commit-graph-chain` file. They will be deleted by a later run based on +the expiration delay. + gc:: Cleanup unnecessary files and optimize the local repository. "GC" stands for "garbage collection," but this task performs many
builtin/gc.c +73 -1
92 lines not on this page · raw message
commit-graph.c +4 -4
@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb, oid_hex); } -static char *get_chain_filename (struct object_directory *odb) +char *get_commit_graph_chain_filename (struct object_directory *odb) {return xstrfmt ("%s/info/commit-graphs/commit-graph-chain" , odb->path); }
@@ -520,7 +520,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,struct stat st;struct object_id *oids;int i =0 , valid =1 , count; -char *chain_name =get_chain_filename (odb); +char *chain_name =get_commit_graph_chain_filename (odb);FILE *fp;int stat_res;
@@ -1635,7 +1635,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) }if (ctx->split) { -char *lock_name =get_chain_filename (ctx->odb); +char *lock_name =get_commit_graph_chain_filename (ctx->odb);hold_lock_file_for_update_mode (&lk, lock_name,LOCK_DIE_ON_ERROR ,0444 );
@@ -2012,7 +2012,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)if (ctx->split_opts && ctx->split_opts->expire_time) expire_time = ctx->split_opts->expire_time;if (!ctx->split) { -char *chain_file_name =get_chain_filename (ctx->odb); +char *chain_file_name =get_commit_graph_chain_filename (ctx->odb);unlink (chain_file_name);free (chain_file_name); ctx->num_commit_graphs_after =0 ;
commit-graph.h +1 -0
@@ -25,6 +25,7 @@ struct commit;struct bloom_filter_settings ;char *get_commit_graph_filename (struct object_directory *odb); +char *get_commit_graph_chain_filename (struct object_directory *odb);int open_commit_graph (const char *graph_file,int *fd,struct stat *st);/*
t/t7900-maintenance.sh +1 -1
@@ -12,7 +12,7 @@ test_expect_success 'help text' ' test_i18ngrep "usage: git maintenance run" err ' -test_expect_success 'gc [--auto|--quiet]' ' +test_expect_success 'run [--auto|--quiet]' ' GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run --no-quiet && GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && GIT_TRACE2_EVENT="$(pwd)/run-quiet.txt" git maintenance run --quiet &&
Signature · 1 line
gitgitgadget
Re: [PATCH v2 05/18] maintenance: add commit-graph task
builtin/gc.c static struct maintenance_opts { · line 756
Quoted 8 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +8
-0+static int maintenance_task_commit_graph (void ) +{ +struct repository *r = the_repository; +char *chain_path; + +/* Skip commit-graph when --auto is specified. */ +if (opts.auto_flag) +return 0 ;
Stepping back a bit, back in "git gc" days, "--auto" had two distinct meanings rolled into one. Check if it even needs to be done, and perform only the lightweight variant if needed.
For this task, there is no "lightweight variant" is possible, so returning without checking the need to do a lightweight one makes perfect sense here.
But wouldn't it suggest perhaps we could name "auto" field of the options struct in a more meaningful way? Perhaps "quick" (i.e. only the quicker-variant of the maintenance job) or something?
Quoted 5 lines
builtin/gc.c +5
-0+close_object_store (r->objects); +if (run_write_commit_graph ()) { +error (_ ("failed to write commit-graph" )); +return 1 ; + }
builtin/gc.c static struct maintenance_opts { · line 772
Quoted 10 lines
builtin/gc.c +10
-0+ +if (!run_verify_commit_graph ()) +return 0 ; + +warning (_ ("commit-graph verify caught error, rewriting" )); + + chain_path =get_commit_graph_chain_filename (r->objects->odb); +if (unlink (chain_path)) { +UNLEAK (chain_path); +die (_ ("failed to remove commit-graph at %s" ), chain_path);
OK.
builtin/gc.c static struct maintenance_opts { · line 781
Quoted 9 lines
builtin/gc.c +9
-0+ } +free (chain_path); + +if (!run_write_commit_graph ()) +return 0 ; + +error (_ ("failed to rewrite commit-graph" )); +return 1 ; +}
Error convention is "positive for error, zero for success?" That is a bit unusual for our internal API.
builtin/gc.c static void initialize_tasks(void) · line 842
Quoted 11 lines
static int
maintenance_task_gc(void)
{builtin/gc.c +4
-0int result;@@ -768,6 +836,10 @@ static void initialize_tasks(void) tasks[num_tasks]->fn = maintenance_task_gc; tasks[num_tasks]->enabled =1 ; num_tasks++; + + tasks[num_tasks]->name ="commit-graph" ; + tasks[num_tasks]->fn = maintenance_task_commit_graph; + num_tasks++;
Again, I am not sure if we want to keep piling on this.
t/t7900-maintenance.sh test_expect_success 'help text' ' · line 15
Quoted 10 lines
t/t7900-maintenance.sh +1
-1@@ -12,7 +12,7 @@ test_expect_success 'help text' ' test_i18ngrep "usage: git maintenance run" err ' -test_expect_success 'gc [--auto|--quiet]' ' +test_expect_success 'run [--auto|--quiet]' '
It does not look like this change belongs here. If "run" is appropriate title for this test at this step, it must have been so in the previous step.
Quoted 3 lines
t/t7900-maintenance.sh +0
-0GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run --no-quiet && GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && GIT_TRACE2_EVENT="$(pwd)/run-quiet.txt" git maintenance run --quiet &&
Re: [PATCH v2 05/18] maintenance: add commit-graph task
Quoted 10 lines · Junio C Hamano
builtin/gc.c static struct maintenance_opts { · line 756
Quoted 8 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +8
-0+static int maintenance_task_commit_graph (void ) +{ +struct repository *r = the_repository; +char *chain_path; + +/* Skip commit-graph when --auto is specified. */ +if (opts.auto_flag) +return 0 ;
Now that you point this out, this is actually a stray condition from an earlier version. We now have the ".enabled" config and the auto condition function pointer. That handles all of that "should we run this when --auto is specified?" logic outside of the task itself.
Quoted 11 lines
Stepping back a bit, back in "git gc" days, "--auto" had two distinct meanings rolled into one. Check if it even needs to be done, and perform only the lightweight variant if needed.
For this task, there is no "lightweight variant" is possible, so returning without checking the need to do a lightweight one makes perfect sense here.
But wouldn't it suggest perhaps we could name "auto" field of the options struct in a more meaningful way? Perhaps "quick" (i.e. only the quicker-variant of the maintenance job) or something?
But you are discussing here how the _behavior_ can change when --auto is specified. And specifically, "git gc --auto" really meant "This is running after a foreground command, so only do work if necessary and do it quickly to minimize blocking time."
I'd be happy to replace "--auto" with "--quick" in the maintenance builtin.
This opens up some extra design space for how the individual tasks perform depending on "--quick" being specified or not. My intention was to create tasks that are already in "quick" mode:
* loose-objects have a maximum batch size.
* incremental-repack is capped in size.
* commit-graph uses the --split option.
But this "quick" distinction might be important for some of the tasks we intend to extract from the gc builtin.
Quoted 18 lines
Quoted 5 lines
builtin/gc.c +5
-0+close_object_store (r->objects); +if (run_write_commit_graph ()) { +error (_ ("failed to write commit-graph" )); +return 1 ; + }builtin/gc.c static struct maintenance_opts { · line 772
Quoted 10 lines
builtin/gc.c +10
-0+ +if (!run_verify_commit_graph ()) +return 0 ; + +warning (_ ("commit-graph verify caught error, rewriting" )); + + chain_path =get_commit_graph_chain_filename (r->objects->odb); +if (unlink (chain_path)) { +UNLEAK (chain_path); +die (_ ("failed to remove commit-graph at %s" ), chain_path);OK.
Quoted 12 lines
builtin/gc.c static struct maintenance_opts { · line 781
Quoted 9 lines
builtin/gc.c +9
-0+ } +free (chain_path); + +if (!run_write_commit_graph ()) +return 0 ; + +error (_ ("failed to rewrite commit-graph" )); +return 1 ; +}Error convention is "positive for error, zero for success?" That is a bit unusual for our internal API.
Since the tasks are frequently running subcommands, returning 0 for success and non-zero for error matches the error codes returned by those subcommands.
Should I instead change the behavior and clearly document that task functions matching maintenance_task_fn follow this error pattern?
Quoted 8 lines
Quoted 8 lines
t/t7900-maintenance.sh +0
-0@@ -12,7 +12,7 @@ test_expect_success 'help text' ' test_i18ngrep "usage: git maintenance run" err '
Quoted 6 lines
t/t7900-maintenance.sh test_expect_success 'help text' ' · line 15
Quoted 2 lines
t/t7900-maintenance.sh +1
-1-test_expect_success 'gc [--auto|--quiet]' ' +test_expect_success 'run [--auto|--quiet]' 'It does not look like this change belongs here. If "run" is appropriate title for this test at this step, it must have been so in the previous step.
Thanks. Will fix.
-Stolee
Re: [PATCH v2 05/18] maintenance: add commit-graph task
Quoted 13 lines · Derrick Stolee
But you are discussing here how the _behavior_ can change when --auto is specified. And specifically, "git gc --auto" really meant "This is running after a foreground command, so only do work if necessary and do it quickly to minimize blocking time."
I'd be happy to replace "--auto" with "--quick" in the maintenance builtin.
This opens up some extra design space for how the individual tasks perform depending on "--quick" being specified or not. My intention was to create tasks that are already in "quick" mode:
Quoted 6 lines
* loose-objects have a maximum batch size.
* incremental-repack is capped in size.
* commit-graph uses the --split option.But this "quick" distinction might be important for some of the tasks we intend to extract from the gc builtin.
Yup. To be honest, I came to this topic from a completely different direction. The field name "auto" alone (and no other field name) had to have an extra cruft (i.e. "_flag") attached to it, which is understandable but ugly. Then I started thinking if 'auto(matic)' is really the right word to describe what we want out of the option, and came to the realization that there may be better words.
Quoted 3 lines
Since the tasks are frequently running subcommands, returning 0 for success and non-zero for error matches the error codes returned by those subcommands.
As long as these will _never_ be called from other helper functions but from the cmd_foo() top-level and their return values are only used directly as the top-level's return value, I do not mind too much.
But whenever I am writing such a code, I find myself not brave enough to make such a bold promise (I saw other people call the helpers I wrote in unintended ways and had to adjust the semantics of them to accomodate the new callers too many times), so I'd rather see the caller do "return !!helper_fn()" to allow helper_fn() to be written more naturally (e.g. letting them return error(...)).
Thanks.
Re: [PATCH v2 05/18] maintenance: add commit-graph task
Quoted 22 lines · Junio C Hamano
Quoted 13 lines · Derrick Stolee
But you are discussing here how the _behavior_ can change when --auto is specified. And specifically, "git gc --auto" really meant "This is running after a foreground command, so only do work if necessary and do it quickly to minimize blocking time."
I'd be happy to replace "--auto" with "--quick" in the maintenance builtin.
This opens up some extra design space for how the individual tasks perform depending on "--quick" being specified or not. My intention was to create tasks that are already in "quick" mode:
Quoted 6 lines
* loose-objects have a maximum batch size.
* incremental-repack is capped in size.
* commit-graph uses the --split option.But this "quick" distinction might be important for some of the tasks we intend to extract from the gc builtin.
Quoted 6 lines
Yup. To be honest, I came to this topic from a completely different direction. The field name "auto" alone (and no other field name) had to have an extra cruft (i.e. "
_flag") attached to it, which is understandable but ugly. Then I started thinking if 'auto(matic)' is really the right word to describe what we want out of the option, and came to the realization that there may be better words.
I wonder what the quick and slow paths are here. For the commit-graph code, what you wrote here seems to match what I'd expect with passing '--auto' in the sense of running 'git gc'. That is, I'm leaving it up to the commit-graph machinery's idea of the normal '--split' rules to figure out when to roll up layers of a commit-graph, as opposed to creating a new layer and extending the chain.
So, I think that makes sense if the caller gave '--auto'. But, I'm not sure that it makes sense if they didn't, in which case I'd imagine something quicker to happen. There, I'd expect something more like:
1. Run 'git commit-graph write --reachable --split=no-merge'.
2. Run 'git commit-graph verify'.
3. If 'git commit-graph verify' failed, drop the existing commit graph and rebuild it with 'git commit-graph --reachable --split=replace'.
4. Otherwise, do nothing.
I'm biased, of course, but I think that that matches roughly what I'd expect to happen in the fast/slow path. Granted, the steps to rebuild the commit graph are going to be slow no matter what (depending on the size of the repository), and so in that case maybe the commit-graph should just be dropped. I'm not really sure what to do about that...
Quoted 9 lines
Quoted 3 lines
Since the tasks are frequently running subcommands, returning 0 for success and non-zero for error matches the error codes returned by those subcommands.
As long as these will _never_ be called from other helper functions but from the
cmd_foo()top-level and their return values are only used directly as the top-level's return value, I do not mind too much.
Quoted 8 lines
But whenever I am writing such a code, I find myself not brave enough to make such a bold promise (I saw other people call the helpers I wrote in unintended ways and had to adjust the semantics of them to accomodate the new callers too many times), so I'd rather see the caller do "return !!helper_fn()" to allow
helper_fn()to be written more naturally (e.g. letting them return error(...)).Thanks.
Thanks,
Taylor
Re: [PATCH v2 05/18] maintenance: add commit-graph task
Quoted 30 lines · Taylor Blau
Quoted 22 lines · Junio C Hamano
Quoted 13 lines · Derrick Stolee
But you are discussing here how the _behavior_ can change when --auto is specified. And specifically, "git gc --auto" really meant "This is running after a foreground command, so only do work if necessary and do it quickly to minimize blocking time."
I'd be happy to replace "--auto" with "--quick" in the maintenance builtin.
This opens up some extra design space for how the individual tasks perform depending on "--quick" being specified or not. My intention was to create tasks that are already in "quick" mode:
Quoted 6 lines
* loose-objects have a maximum batch size.
* incremental-repack is capped in size.
* commit-graph uses the --split option.But this "quick" distinction might be important for some of the tasks we intend to extract from the gc builtin.
Quoted 6 lines
Yup. To be honest, I came to this topic from a completely different direction. The field name "auto" alone (and no other field name) had to have an extra cruft (i.e. "
_flag") attached to it, which is understandable but ugly. Then I started thinking if 'auto(matic)' is really the right word to describe what we want out of the option, and came to the realization that there may be better words.
Quoted 6 lines
I wonder what the quick and slow paths are here. For the commit-graph code, what you wrote here seems to match what I'd expect with passing '--auto' in the sense of running 'git gc'. That is, I'm leaving it up to the commit-graph machinery's idea of the normal '--split' rules to figure out when to roll up layers of a commit-graph, as opposed to creating a new layer and extending the chain.
I had intended all of my new tasks to be the "quick" version of their operations. The "slow" version would abandon hope of doing a small amount of work to create the best possible world for the repository. This would include:
* The commit-graph would collapse all layers into one file.
* The multi-pack-index repack would rewrite all object data into one pack-file.
* The loose-objects task would not stop at a maximum number of loose objects (and would probably want to repack everything, anyway).
I'm open to making this possibility more explicit by renaming "--auto" and just performing a translation to 'git gc --auto'. So, what should the name be? Here are a few options to consider:
--quick
--fast
--limited
--incremental
-O[0|1|2...] (think GCC optimization flags, exposing granularity) --[non-]aggressive
Regardless, this makes me rethink that the --[no-]maintenance option from PATCH 03/18 is better than --[no-]auto-maintenance, since we are really saying "run _some_ maintenance or _no_ maintenance" and the "how" of the maintenance is left intentionally vague. I've already made the change locally to add "auto-" so I'll wait for confirmation before reverting that change.
Quoted 10 lines
So, I think that makes sense if the caller gave '--auto'. But, I'm not sure that it makes sense if they didn't, in which case I'd imagine something quicker to happen. There, I'd expect something more like:
1. Run 'git commit-graph write --reachable --split=no-merge'.
2. Run 'git commit-graph verify'.
3. If 'git commit-graph verify' failed, drop the existing commit graph and rebuild it with 'git commit-graph --reachable --split=replace'.
4. Otherwise, do nothing.
Quoted 5 lines
I'm biased, of course, but I think that that matches roughly what I'd expect to happen in the fast/slow path. Granted, the steps to rebuild the commit graph are going to be slow no matter what (depending on the size of the repository), and so in that case maybe the commit-graph should just be dropped. I'm not really sure what to do about that...
I think this approach is the best we can do given the current behavior inside the commit-graph builtin. Perhaps in the future we could change the commit-graph builtin to include a "--verify" option so it could do the "git commit-graph verify --shallow" on the new layer before committing the new commit-graph-chain file and expiring old layers. That way, we would not need to delete and rewrite the whole thing when there is a problem writing the top layer.
Quoted 9 lines
Quoted 9 lines
Quoted 3 lines
Since the tasks are frequently running subcommands, returning 0 for success and non-zero for error matches the error codes returned by those subcommands.
As long as these will _never_ be called from other helper functions but from the
cmd_foo()top-level and their return values are only used directly as the top-level's return value, I do not mind too much.
Quoted 6 lines
Quoted 6 lines
But whenever I am writing such a code, I find myself not brave enough to make such a bold promise (I saw other people call the helpers I wrote in unintended ways and had to adjust the semantics of them to accomodate the new callers too many times), so I'd rather see the caller do "return !!helper_fn()" to allow
helper_fn()to be written more naturally (e.g. letting them return error(...)).
I will try to be consistent here with the behavior:
* 0 is success
* 1 is failure
Which is what I think you are implying by "return !!helper_fn()".
Thanks,
-Stolee
Re: [PATCH v2 05/18] maintenance: add commit-graph task
In reply to Derrick Stolee via GitGitGadgetQuoted 6 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +6
-0+static int run_write_commit_graph (void ) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ; + + argv_array_pushl(&cmd, "commit-graph", "write",
builtin/gc.c static struct maintenance_opts { · line 730
Quoted 10 lines
builtin/gc.c +10
-0+ "--split", "--reachable", NULL); + +if (opts.quiet) +argv_array_push (&cmd,"--no-progress" ); + + result =run_command_v_opt (cmd.argv,RUN_GIT_CMD ); +argv_array_clear (&cmd); + +return result; +}
This is a pretty minor nit, but since I happened to be looking at the merge of all of the recent argv_array callers today... :)
You can write this a bit more succinctly by reusing the argv_array provided by the child_process:
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
argv_array_pushl(&cmd.args, "commit-graph", "write",
"--split", "--reachable", NULL);
if (opts.quiet)
argv_array_push(&cmd.args, "--no-progress");
return run_command(&cmd);Then you don't have to worry about freeing the argv memory, because it's handled automatically.
Like I said, quite minor, but it looks like this pattern appears in a few places, so it might be worth tweaking. And it would still work with the "pushf" people recommended to avoid extra strbufs (I saw another one in fetch_remote(), too).
-Peff
PATCH v2 6/18 maintenance: add --task option
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
A user may want to only run certain maintenance tasks in a certain order. Add the --task=<task> option, which allows a user to specify an ordered list of tasks to run. These cannot be run multiple times, however.
Here is where our array of maintenance_task pointers becomes critical. We can sort the array of pointers based on the task order, but we do not want to move the struct data itself in order to preserve the hashmap references. We use the hashmap to match the --task=<task> arguments into the task struct data.
--- Documentation/git-maintenance.txt | 4 ++ builtin/gc.c | 64 ++++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 23 +++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-)
Documentation/git-maintenance.txt +4 -0
@@ -73,6 +73,10 @@ OPTIONS --quiet:: Do not report progress or other information over `stderr`. +--task=<task>:: + If this option is specified one or more times, then only run the + specified tasks in the specified order. + GIT --- Part of the linkgit:git[1] suite
builtin/gc.c +62 -2
100 lines not on this page · raw message
t/t7900-maintenance.sh +23 -0
@@ -21,4 +21,27 @@ test_expect_success 'run [--auto|--quiet]' ' grep ",\"gc\",\"--quiet\"" run-quiet.txt ' +test_expect_success 'run --task=<task>' ' + GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" git maintenance run --task=commit-graph && + GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" git maintenance run --task=gc && + GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" git maintenance run --task=commit-graph && + GIT_TRACE2_EVENT="$(pwd)/run-both.txt" git maintenance run --task=commit-graph --task=gc && + ! grep ",\"gc\"" run-commit-graph.txt && + grep ",\"gc\"" run-gc.txt && + grep ",\"gc\"" run-both.txt && + grep ",\"commit-graph\",\"write\"" run-commit-graph.txt && + ! grep ",\"commit-graph\",\"write\"" run-gc.txt && + grep ",\"commit-graph\",\"write\"" run-both.txt +' + +test_expect_success 'run --task=bogus' ' + test_must_fail git maintenance run --task=bogus 2>err && + test_i18ngrep "is not a valid task" err +' + +test_expect_success 'run --task duplicate' ' + test_must_fail git maintenance run --task=gc --task=gc 2>err && + test_i18ngrep "cannot be selected multiple times" err +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 06/18] maintenance: add --task option
Quoted 38 lines · Derrick Stolee via GitGitGadget
Documentation/git-maintenance.txt +4
-0@@ -73,6 +73,10 @@ OPTIONS --quiet:: Do not report progress or other information over `stderr`. +--task=<task>:: + If this option is specified one or more times, then only run the + specified tasks in the specified order. + GIT --- Part of the linkgit:git[1] suitebuiltin/gc.c +4
-1@@ -710,6 +710,7 @@ static const char * const builtin_maintenance_usage[] = {static struct maintenance_opts {int auto_flag;int quiet; +int tasks_selected; } opts;static int run_write_commit_graph (void )@@ -804,20 +805,38 @@ typedef int maintenance_task_fn(void);struct maintenance_task {const char *name;maintenance_task_fn *fn; -unsigned enabled:1 ; +int task_order; +unsigned enabled:1 , + selected:1 ; };static struct maintenance_task *tasks[MAX_NUM_TASKS ];
builtin/gc.c typedef int maintenance_task_fn(void); · line 823
Quoted 10 lines
builtin/gc.c +8
-0static int num_tasks; +static int compare_tasks_by_selection (const void *a_,const void *b_) +{ +const struct maintenance_task *a, *b; + a = (const struct maintenance_task *)a_; + b = (const struct maintenance_task *)b_; + +return b->task_order - a->task_order; +}
It forces the reader to know intimately that task_order *is* selection order in order to understand why this is "tasks by selection". Perhaps rename the field to match what it is (i.e. "selection_order")?
Quoted 5 lines
builtin/gc.c +0
-0static int maintenance_run (void ) {int i;int result =0 ;
builtin/gc.c typedef int maintenance_task_fn(void); · line 838
Quoted 10 lines
builtin/gc.c +7
-1+if (opts.tasks_selected) +QSORT (tasks, num_tasks, compare_tasks_by_selection); +for (i =0 ; !result && i < num_tasks; i++) { -if (!tasks[i]->enabled) +if (opts.tasks_selected && !tasks[i]->selected) +continue ; + +if (!opts.tasks_selected && !tasks[i]->enabled)continue ;
I am not sure about this. Even if the task <x> is disabled, if the user says --task=<x>, it is run anyway? Doesn't make an immediate sense to me.
As I am bad at deciphering de Morgan, I'd find it easier to read if the first were written more like so:
if (!(!opts.tasks_selected || tasks[i]->selected)) continue;
That is, "do not skip any when not limited, and do not skip the ones that are selected when limited". And that would easily extend to
if (!tasks[i]->enabled || !(!opts.tasks_selected || tasks[i]->selected)) continue;
Quoted 9 lines
builtin/gc.c +2
-0+ result = tasks[i]->fn (); }@@ -842,6 +861,44 @@ static void initialize_tasks(void) num_tasks++; } +static int task_option_parse(const struct option *opt,
builtin/gc.c static void initialize_tasks(void) · line 874
Quoted 10 lines
builtin/gc.c +10
-0+ const char *arg, int unset) +{ + int i; +struct maintenance_task *task =NULL ; + +BUG_ON_OPT_NEG (unset); + +if (!arg || !strlen (arg)) { +error (_ ("--task requires a value" )); +return 1 ;
There is no need to special case an empty string that was explicitly given as the value---it will be caught as "'' is not a valid task".
Quoted 26 lines
builtin/gc.c +26
-0+ } + + opts.tasks_selected++; + +for (i =0 ; i <MAX_NUM_TASKS ; i++) { +if (tasks[i] && !strcasecmp (tasks[i]->name, arg)) { + task = tasks[i]; +break ; + } + } + +if (!task) { +error (_ ("'%s' is not a valid task" ), arg); +return 1 ; + } + +if (task->selected) { +error (_ ("task '%s' cannot be selected multiple times" ), arg); +return 1 ; + } + + task->selected =1 ; + task->task_order = opts.tasks_selected; + +return 0 ; +}
Re: [PATCH v2 06/18] maintenance: add --task option
Quoted 11 lines · Junio C Hamano
builtin/gc.c typedef int maintenance_task_fn(void); · line 838
Quoted 7 lines
builtin/gc.c +4
-1for (i =0 ; !result && i < num_tasks; i++) { -if (!tasks[i]->enabled) +if (opts.tasks_selected && !tasks[i]->selected) +continue ; + +if (!opts.tasks_selected && !tasks[i]->enabled)continue ;I am not sure about this. Even if the task <x> is disabled, if the user says --task=<x>, it is run anyway? Doesn't make an immediate sense to me.
OK, after seeing the title of 12/18, it does make sense. Even if it is disabled by default, you could still choose to run, which makes sense.
Re: [PATCH v2 06/18] maintenance: add --task option
In reply to Junio C HamanoQuoted 14 lines · Junio C Hamano
builtin/gc.c typedef int maintenance_task_fn(void); · line 823
Quoted 8 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +8
-0+static int compare_tasks_by_selection (const void *a_,const void *b_) +{ +const struct maintenance_task *a, *b; + a = (const struct maintenance_task *)a_; + b = (const struct maintenance_task *)b_; + +return b->task_order - a->task_order; +}It forces the reader to know intimately that
task_order*is* selection order in order to understand why this is "tasks by selection". Perhaps rename the field to match what it is (i.e. "selection_order")?
Good idea. I made this fix locally.
Quoted 5 lines
Quoted 5 lines
static int
maintenance_run(void)
{builtin/gc.c +0
-0int i;int result =0 ;
Quoted 14 lines
builtin/gc.c typedef int maintenance_task_fn(void); · line 838
Quoted 10 lines
builtin/gc.c +7
-1+if (opts.tasks_selected) +QSORT (tasks, num_tasks, compare_tasks_by_selection); +for (i =0 ; !result && i < num_tasks; i++) { -if (!tasks[i]->enabled) +if (opts.tasks_selected && !tasks[i]->selected) +continue ; + +if (!opts.tasks_selected && !tasks[i]->enabled)continue ;I am not sure about this. Even if the task <x> is disabled, if the user says --task=<x>, it is run anyway? Doesn't make an immediate sense to me.
You already replied that you figured this out. However, I could make it easier by adding some foreshadowing in the commit message here.
Quoted 6 lines
As I am bad at deciphering de Morgan, I'd find it easier to read if the first were written more like so:
if (!(!opts.tasks_selected || tasks[i]->selected)) continue;
Quoted 6 lines
That is, "do not skip any when not limited, and do not skip the ones that are selected when limited". And that would easily extend to
if (!tasks[i]->enabled || !(!opts.tasks_selected || tasks[i]->selected)) continue;
This isn't quite right, due to the confusing nature of "enabled". The condition here will _never_ allow selecting a disabled task.
Perhaps it would be better to rename 'enabled' to 'run_by_default'? That would make it clear that it is choosing which tasks to run unless specified otherwise with --task=<task> here, the config option maintenance.<task>.enabled later, and the --auto conditions even later. Looking even farther down the line (into the next series) there will be similar checks for auto-conditionschecking time-based schedules.
Since this loop becomes more complicated in the future, I specifically wanted to group the "skip this task" conditions into their own if blocks:
1. If the user didn't specify --task=<task> explicitly and this task is disabled, then skip this task.
2. If the user _did_ specify --task=<task> explicitly and this task was not on the list, then skip this task.
3. If the user specified --auto and the auto condition fails, then skip this task.
4. (Later) If the user specified --scheduled and the time since the last run is too soon, then skip this task.
With this being the planned future, I'd prefer these be split out as separate if conditions instead of a giant combined if. And since that is the plan, then I won't work too hard to combine conditions 1 and 2 into a single condition.
Quoted 8 lines
Quoted 8 lines
builtin/gc.c +1
-0+ result = tasks[i]->fn (); }@@ -842,6 +861,44 @@ static void initialize_tasks(void) num_tasks++; }
Quoted 14 lines
builtin/gc.c static void initialize_tasks(void) · line 874
Quoted 11 lines
builtin/gc.c +11
-0+static int task_option_parse (const struct option *opt, +const char *arg,int unset) +{ +int i; +struct maintenance_task *task =NULL ; + +BUG_ON_OPT_NEG (unset); + +if (!arg || !strlen (arg)) { +error (_ ("--task requires a value" )); +return 1 ;There is no need to special case an empty string that was explicitly given as the value---it will be caught as "'' is not a valid task".
Sounds good. No need for this extra message.
Thanks,
-Stolee
Re: [PATCH v2 06/18] maintenance: add --task option
Quoted 16 lines · Derrick Stolee
Quoted 12 lines
builtin/gc.c typedef int maintenance_task_fn(void); · line 838
Quoted 7 lines
builtin/gc.c +4
-1for (i =0 ; !result && i < num_tasks; i++) { -if (!tasks[i]->enabled) +if (opts.tasks_selected && !tasks[i]->selected) +continue ; + +if (!opts.tasks_selected && !tasks[i]->enabled)continue ;I am not sure about this. Even if the task <x> is disabled, if the user says --task=<x>, it is run anyway? Doesn't make an immediate sense to me.
...Quoted 3 lines
if (!tasks[i]->enabled || !(!opts.tasks_selected || tasks[i]->selected)) continue;
Quoted 1 line
This isn't quite right, due to the confusing nature of "enabled".
Yes, in the message you are responding to, I was still assuming that --task=foo that defeat task.foo.enabled=no would be a bug. If we want to run disabled tasks by selection, of course the condition would need to change.
PATCH v2 7/18 maintenance: take a lock on the objects directory
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Performing maintenance on a Git repository involves writing data to the .git directory, which is not safe to do with multiple writers attempting the same operation. Ensure that only one 'git maintenance' process is running at a time by holding a file-based lock. Simply the presence of the .git/maintenance.lock file will prevent future maintenance. This lock is never committed, since it does not represent meaningful data. Instead, it is only a placeholder.
If the lock file already exists, then fail silently. This will become very important later when we implement the 'fetch' task, as this is our stop-gap from creating a recursive process loop between 'git fetch' and 'git maintenance run'.
--- builtin/gc.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
builtin/gc.c +20 -0
@@ -826,6 +826,25 @@ static int maintenance_run(void) {int i;int result =0 ; +struct lock_file lk; +struct repository *r = the_repository; +char *lock_path =xstrfmt ("%s/maintenance" , r->objects->odb->path); + +if (hold_lock_file_for_update (&lk, lock_path,LOCK_NO_DEREF ) <0 ) { +/* +* Another maintenance command is running. +* +* If --auto was provided, then it is likely due to a +* recursive process stack. Do not report an error in +* that case. +*/ +if (!opts.auto_flag && !opts.quiet) +error (_ ("lock file '%s' exists, skipping maintenance" ), + lock_path); +free (lock_path); +return 0 ; + } +free (lock_path);if (opts.tasks_selected)QSORT (tasks, num_tasks, compare_tasks_by_selection);
@@ -840,6 +859,7 @@ static int maintenance_run(void) result = tasks[i]->fn (); } +rollback_lock_file (&lk);return result; }
Signature · 1 line
gitgitgadget
PATCH v2 8/18 maintenance: add prefetch task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
When working with very large repositories, an incremental 'git fetch' command can download a large amount of data. If there are many other users pushing to a common repo, then this data can rival the initial pack-file size of a 'git clone' of a medium-size repo.
Users may want to keep the data on their local repos as close as possible to the data on the remote repos by fetching periodically in the background. This can break up a large daily fetch into several smaller hourly fetches.
The task is called "prefetch" because it is work done in advance of a foreground fetch to make that 'git fetch' command much faster.
However, if we simply ran 'git fetch <remote>' in the background, then the user running a foregroudn 'git fetch <remote>' would lose some important feedback when a new branch appears or an existing branch updates. This is especially true if a remote branch is force-updated and this isn't noticed by the user because it occurred in the background. Further, the functionality of 'git push --force-with-lease' becomes suspect.
When running 'git fetch <remote> <options>' in the background, use the following options for careful updating:
1. --no-tags prevents getting a new tag when a user wants to see the new tags appear in their foreground fetches.
2. --refmap= removes the configured refspec which usually updates refs/remotes/<remote>/* with the refs advertised by the remote.
3. By adding a new refspec "+refs/heads/*:refs/prefetch/<remote>/*" we can ensure that we actually load the new values somewhere in our refspace while not updating refs/heads or refs/remotes. By storing these refs here, the commit-graph job will update the commit-graph with the commits from these hidden refs.
4. --prune will delete the refs/prefetch/<remote> refs that no longer appear on the remote.
We've been using this step as a critical background job in Scalar
[1] (and VFS for Git). This solved a pain point that was showing up in user reports: fetching was a pain! Users do not like waiting to download the data that was created while they were away from their machines. After implementing background fetch, the foreground fetch commands sped up significantly because they mostly just update refs and download a small amount of new data. The effect is especially dramatic when paried with --no-show-forced-udpates (through fetch.showForcedUpdates=false).
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/FetchStep.cs
--- Documentation/git-maintenance.txt | 12 ++++++ builtin/gc.c | 64 ++++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 24 ++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-)
Documentation/git-maintenance.txt +12 -0
@@ -53,6 +53,18 @@ since it will not expire `.graph` files that were in the previous `commit-graph-chain` file. They will be deleted by a later run based on the expiration delay. +prefetch:: + The `fetch` task updates the object directory with the latest objects + from all registered remotes. For each remote, a `git fetch` command + is run. The refmap is custom to avoid updating local or remote + branches (those in `refs/heads` or `refs/remotes`). Instead, the + remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are + not updated. ++ +This means that foreground fetches are still required to update the +remote refs, but the users is notified when the branches and tags are +updated on the remote. + gc:: Cleanup unnecessary files and optimize the local repository. "GC" stands for "garbage collection," but this task performs many
builtin/gc.c +63 -1
88 lines not on this page · raw message
t/t7900-maintenance.sh +24 -0
@@ -44,4 +44,28 @@ test_expect_success 'run --task duplicate' ' test_i18ngrep "cannot be selected multiple times" err ' +test_expect_success 'run --task=prefetch with no remotes' ' + git maintenance run --task=prefetch 2>err && + test_must_be_empty err +' + +test_expect_success 'prefetch multiple remotes' ' + git clone . clone1 && + git clone . clone2 && + git remote add remote1 "file://$(pwd)/clone1" && + git remote add remote2 "file://$(pwd)/clone2" && + git -C clone1 switch -c one && + git -C clone2 switch -c two && + test_commit -C clone1 one && + test_commit -C clone2 two && + GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch && + grep ",\"fetch\",\"remote1\"" run-prefetch.txt && + grep ",\"fetch\",\"remote2\"" run-prefetch.txt && + test_path_is_missing .git/refs/remotes && + test_cmp clone1/.git/refs/heads/one .git/refs/prefetch/remote1/one && + test_cmp clone2/.git/refs/heads/two .git/refs/prefetch/remote2/two && + git log prefetch/remote1/one && + git log prefetch/remote2/two +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 08/18] maintenance: add prefetch task
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 57
Quoted 10 lines · Derrick Stolee via GitGitGadget
Documentation/git-maintenance.txt +2
-0@@ -53,6 +53,18 @@ since it will not expire `.graph` files that were in the previous `commit-graph-chain` file. They will be deleted by a later run based on the expiration delay. +prefetch:: + The `fetch` task updates the object directory with the latest objects
s/fetch/prefetch/ most likely.
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 59
Quoted 2 lines
Documentation/git-maintenance.txt +2
-0+ from all registered remotes. For each remote, a `git fetch` command + is run. The refmap is custom to avoid updating local or remote
s/remote/remote-tracking/ definitely. Do not forget the hyphen between the two words.
I think it made the above unnecessarily confusing that you ended a sentence after "is run". It gives a wrong impression that you'd be doing a "real fetch", which you need to dispel with a follow up description of the refmap.
For each remote, a git fetch command is run with a refspec to fetch their branches (those in their refs/heads) into our refs/prefetch/<remote>/ hierarchy and without auto following tags (the configured refspec in the repository is ignored).
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 65
Quoted 6 lines
Documentation/git-maintenance.txt +6
-0+ branches (those in `refs/heads` or `refs/remotes`). Instead, the + remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are + not updated. ++ +This means that foreground fetches are still required to update the +remote refs, but the users is notified when the branches and tags are
s/is notified/are notified/???
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 66
Quoted 1 line
Documentation/git-maintenance.txt +1
-0+updated on the remote.
Often, when one needs to say "X. This means Y.", X is a suboptimal way to explain what needs to be conveyed to the readers. But this is not such a case. Unlike the "This means" that is often an attempt to rephrasing a poor explanation given first, this gives an implication.
But let's not start with a negative impression (i.e. even with prefetch, I still have to do X? What's the point???), but let them feel why it is a good thing. Perhaps (continuing my previous rewrite):
This is done to avoid disrupting the remote-tracking branches---the end users expect them to stay unmoved unless they initiate a fetch. With prefetch task, however, the objects necessary to complete a later real fetch would already be obtained, so the real fetch would go faster. In the ideal case, it will just become an update to bunch of remote-tracking branches without any object transfer.
or something like that?
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 70
Quoted 4 lines
Documentation/git-maintenance.txt +4
-0+ argv_array_pushl(&cmd, "fetch", remote, "--prune", + "--no-tags", "--refmap=", NULL); + strbuf_addf(&refmap, "+refs/heads/*:refs/prefetch/%s/*", remote); + argv_array_push(&cmd, refmap.buf);
The command line looks somewhat fishy, but I think it is correct. At first glance it looks like a mistake to pass "--refmap=" and the refspec "+refs/heads/*:refs/prefetch/origin/*" as separate arguments, but I think that is exactly what you want here, i.e.
- defeat any refspec in the configuration with "--refmap=<empty>"
- give explicit refspec "+refs/heads/*:...", with "--no-tags" to decline auto-following, to tell what exactly are to be fetched and stored where.
The description in the log message about refmap needs to be clarified, though (I've already done so in the above suggested rewrite).
Quoted 8 lines
Documentation/git-maintenance.txt +8
-0+static int maintenance_task_prefetch(void) +{ + int result = 0; + struct string_list_item *item; + struct string_list remotes = STRING_LIST_INIT_DUP; + + if (for_each_remote(fill_each_remote, &remotes)) { + error(_("failed to fill remotes"));
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous
Quoted 10 lines
Documentation/git-maintenance.txt +10
-0+ result = 1; + goto cleanup; + } + + /* + * Do not modify the result based on the success of the 'fetch' + * operation, as a loss of network could cause 'fetch' to fail + * quickly. We do not want that to stop the rest of our + * background operations. + */
The loop that runs different tasks abort at the first failure, though. Perhaps that loop needs to be rethought as well?
Quoted 14 lines
Documentation/git-maintenance.txt +10
-0+ for (item = remotes.items; + item && item < remotes.items + remotes.nr; + item++) + fetch_remote(item->string); + +cleanup: + string_list_clear(&remotes, 0); + return result; +} + static int maintenance_task_gc(void) { int result;
Documentation/git-maintenance.txt static void initialize_tasks(void) · line 938
Quoted 10 lines
Documentation/git-maintenance.txt +4
-0@@ -871,6 +929,10 @@ static void initialize_tasks(void) for (i = 0; i < MAX_NUM_TASKS; i++) tasks[i] = xcalloc(1, sizeof(struct maintenance_task)); + tasks[num_tasks]->name = "prefetch"; + tasks[num_tasks]->fn = maintenance_task_prefetch; + num_tasks++; + tasks[num_tasks]->name = "gc"; tasks[num_tasks]->fn = maintenance_task_gc; tasks[num_tasks]->enabled = 1;
Two things.
- As I said upfront, I do not see the point of preparing the table with code.
- The reason why prefetch is placed in front is probably because you do not want to repack before you add more objects to the object store. But doesn't that imply that there is an inherent ordering that we, as those who are more expert on Git than the end users, prefer? Is it a wise decision to let the users affect the order of the tasks run by giving command line options in different order in the previous step?
Re: [PATCH v2 08/18] maintenance: add prefetch task
Quoted 62 lines · Junio C Hamano
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 57
Quoted 10 lines · Derrick Stolee via GitGitGadget
Documentation/git-maintenance.txt +2
-0@@ -53,6 +53,18 @@ since it will not expire `.graph` files that were in the previous `commit-graph-chain` file. They will be deleted by a later run based on the expiration delay. +prefetch:: + The `fetch` task updates the object directory with the latest objectss/fetch/prefetch/ most likely.
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 59
Quoted 2 lines
Documentation/git-maintenance.txt +2
-0+ from all registered remotes. For each remote, a `git fetch` command + is run. The refmap is custom to avoid updating local or remotes/remote/remote-tracking/ definitely. Do not forget the hyphen between the two words.
I think it made the above unnecessarily confusing that you ended a sentence after "is run". It gives a wrong impression that you'd be doing a "real fetch", which you need to dispel with a follow up description of the refmap.
For each remote, a
git fetchcommand is run with a refspec to fetch their branches (those in theirrefs/heads) into ourrefs/prefetch/<remote>/hierarchy and without auto following tags (the configured refspec in the repository is ignored).Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 65
Quoted 6 lines
Documentation/git-maintenance.txt +6
-0+ branches (those in `refs/heads` or `refs/remotes`). Instead, the + remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are + not updated. ++ +This means that foreground fetches are still required to update the +remote refs, but the users is notified when the branches and tags ares/is notified/are notified/???
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 66
Quoted 1 line
Documentation/git-maintenance.txt +1
-0+updated on the remote.Often, when one needs to say "X. This means Y.", X is a suboptimal way to explain what needs to be conveyed to the readers. But this is not such a case. Unlike the "This means" that is often an attempt to rephrasing a poor explanation given first, this gives an implication.
But let's not start with a negative impression (i.e. even with prefetch, I still have to do X? What's the point???), but let them feel why it is a good thing. Perhaps (continuing my previous rewrite):
This is done to avoid disrupting the remote-tracking branches---the end users expect them to stay unmoved unless they initiate a fetch. With prefetch task, however, the objects necessary to complete a later real fetch would already be obtained, so the real fetch would go faster. In the ideal case, it will just become an update to bunch of remote-tracking branches without any object transfer.
Quoted 1 line
or something like that?
I like this clarification and have adapted it with minimal edits.
Quoted 12 lines
Documentation/git-maintenance.txt since it will not expire `.graph` files that were in the previous · line 70
Quoted 4 lines
Documentation/git-maintenance.txt +4
-0+ argv_array_pushl(&cmd, "fetch", remote, "--prune", + "--no-tags", "--refmap=", NULL); + strbuf_addf(&refmap, "+refs/heads/*:refs/prefetch/%s/*", remote); + argv_array_push(&cmd, refmap.buf);The command line looks somewhat fishy, but I think it is correct. At first glance it looks like a mistake to pass "--refmap=" and the refspec "+refs/heads/*:refs/prefetch/origin/*" as separate arguments, but I think that is exactly what you want here, i.e.
- defeat any refspec in the configuration with "--refmap=<empty>"
Quoted 7 lines
- give explicit refspec "+refs/heads/*:...", with "--no-tags" to decline auto-following, to tell what exactly are to be fetched and stored where.
The description in the log message about refmap needs to be clarified, though (I've already done so in the above suggested rewrite).
I could have made your life easier by referring to b40a50264ac (fetch: document and test --refmap="", 2020-01-21) in my commit message. It includes this sentence in Documentation/fetch-options.txt:
Providing an empty <refspec> to the --refmap option causes Git to ignore the configured refspecs and rely entirely on the refspecs supplied as command-line arguments.
Quoted 13 lines
Quoted 13 lines
Documentation/git-maintenance.txt +13
-0+static int maintenance_task_prefetch(void) +{ + int result = 0; + struct string_list_item *item; + struct string_list remotes = STRING_LIST_INIT_DUP; + + if (for_each_remote(fill_each_remote, &remotes)) { + error(_("failed to fill remotes")); + result = 1; + goto cleanup; + } + + /*
Quoted 8 lines
Documentation/git-maintenance.txt
Quoted 5 lines
Documentation/git-maintenance.txt +5
-0+ * Do not modify the result based on the success of the 'fetch' + * operation, as a loss of network could cause 'fetch' to fail + * quickly. We do not want that to stop the rest of our + * background operations. + */The loop that runs different tasks abort at the first failure, though. Perhaps that loop needs to be rethought as well?
You're right. These maintenance tasks are intended to be independent of each other, so let's try all of them and report a failure after all have been given an opportunity to run. That makes this failure behavior unnecessary.
Quoted 30 lines
Quoted 14 lines
Documentation/git-maintenance.txt +10
-0+ for (item = remotes.items; + item && item < remotes.items + remotes.nr; + item++) + fetch_remote(item->string); + +cleanup: + string_list_clear(&remotes, 0); + return result; +} + static int maintenance_task_gc(void) { int result;Documentation/git-maintenance.txt static void initialize_tasks(void) · line 938
Quoted 10 lines
Documentation/git-maintenance.txt +4
-0@@ -871,6 +929,10 @@ static void initialize_tasks(void) for (i = 0; i < MAX_NUM_TASKS; i++) tasks[i] = xcalloc(1, sizeof(struct maintenance_task)); + tasks[num_tasks]->name = "prefetch"; + tasks[num_tasks]->fn = maintenance_task_prefetch; + num_tasks++; + tasks[num_tasks]->name = "gc"; tasks[num_tasks]->fn = maintenance_task_gc; tasks[num_tasks]->enabled = 1;Two things.
- As I said upfront, I do not see the point of preparing the table with code.
Quoted 7 lines
- The reason why prefetch is placed in front is probably because you do not want to repack before you add more objects to the object store. But doesn't that imply that there is an inherent ordering that we, as those who are more expert on Git than the end users, prefer? Is it a wise decision to let the users affect the order of the tasks run by giving command line options in different order in the previous step?
I don't anticipate users specifying --task=<task> very often, as it requires deep knowledge of the tasks. If a user _does_ use the option, then we should trust their order as they might have a good reason to choose that order.
Generally, my philosophy is to provide expert users with flexible choices while creating sensible defaults for non-expert users.
That said, if this sort is more of a problem than the value it provides, then I can drop the sort and the duplicate --task=<task> check.
Thanks,
-Stolee
Re: [PATCH v2 08/18] maintenance: add prefetch task
Quoted 7 lines · Derrick Stolee
I don't anticipate users specifying --task=<task> very often, as it requires deep knowledge of the tasks. If a user _does_ use the option, then we should trust their order as they might have a good reason to choose that order.
Generally, my philosophy is to provide expert users with flexible choices while creating sensible defaults for non-expert users.
Sounds sensible.
Re: [PATCH v2 08/18] maintenance: add prefetch task
In reply to Derrick Stolee via GitGitGadgetQuoted 23 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
When working with very large repositories, an incremental 'git fetch' command can download a large amount of data. If there are many other users pushing to a common repo, then this data can rival the initial pack-file size of a 'git clone' of a medium-size repo.
Users may want to keep the data on their local repos as close as possible to the data on the remote repos by fetching periodically in the background. This can break up a large daily fetch into several smaller hourly fetches.
The task is called "prefetch" because it is work done in advance of a foreground fetch to make that 'git fetch' command much faster.
However, if we simply ran 'git fetch <remote>' in the background, then the user running a foregroudn 'git fetch <remote>' would lose some important feedback when a new branch appears or an existing branch updates. This is especially true if a remote branch is force-updated and this isn't noticed by the user because it occurred in the background. Further, the functionality of 'git push --force-with-lease' becomes suspect.
Quoted 2 lines
When running 'git fetch <remote> <options>' in the background, use the following options for careful updating:
Does this job interfere with FETCH_HEAD?
From my quick test (by applying 01-08 on top of rc1, and messing with t7900), it looks like yes.
I (and some other people, probably) rely on FETCH_HEAD for our scripts. Hence, it would be nice to not touch FETCH_HEAD with prefetch job.
Thanks,
-Danh
Quoted 190 lines
1. --no-tags prevents getting a new tag when a user wants to see the new tags appear in their foreground fetches.
2. --refmap= removes the configured refspec which usually updates refs/remotes/<remote>/* with the refs advertised by the remote.
3. By adding a new refspec "+refs/heads/*:refs/prefetch/<remote>/*" we can ensure that we actually load the new values somewhere in our refspace while not updating refs/heads or refs/remotes. By storing these refs here, the commit-graph job will update the commit-graph with the commits from these hidden refs.
4. --prune will delete the refs/prefetch/<remote> refs that no longer appear on the remote.
We've been using this step as a critical background job in Scalar
[1] (and VFS for Git). This solved a pain point that was showing up in user reports: fetching was a pain! Users do not like waiting to download the data that was created while they were away from their machines. After implementing background fetch, the foreground fetch commands sped up significantly because they mostly just update refs and download a small amount of new data. The effect is especially dramatic when paried with --no-show-forced-udpates (through fetch.showForcedUpdates=false).[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/FetchStep.cs
Signed-off-by Derrick Stolee <[email protected]>--- Documentation/git-maintenance.txt | 12 ++++++ builtin/gc.c | 64 ++++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 24 ++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-)Documentation/git-maintenance.txt +12
-0@@ -53,6 +53,18 @@ since it will not expire `.graph` files that were in the previous `commit-graph-chain` file. They will be deleted by a later run based on the expiration delay. +prefetch:: + The `fetch` task updates the object directory with the latest objects + from all registered remotes. For each remote, a `git fetch` command + is run. The refmap is custom to avoid updating local or remote + branches (those in `refs/heads` or `refs/remotes`). Instead, the + remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are + not updated. ++ +This means that foreground fetches are still required to update the +remote refs, but the users is notified when the branches and tags are +updated on the remote. + gc:: Cleanup unnecessary files and optimize the local repository. "GC" stands for "garbage collection," but this task performs manybuiltin/gc.c +63
-1@@ -28,6 +28,7 @@#include "blob.h" #include "tree.h" #include "promisor-remote.h" +#include "remote.h" #define FAILED_RUN "failed to run %s"@@ -700,7 +701,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)return 0 ; } -#define MAX_NUM_TASKS 2 +#define MAX_NUM_TASKS 3static const char *const builtin_maintenance_usage[] = {N_ ("git maintenance run [<options>]" ),@@ -781,6 +782,63 @@ static int maintenance_task_commit_graph(void)return 1 ; } +static int fetch_remote (const char *remote) +{ +int result; +struct argv_array cmd =ARGV_ARRAY_INIT ; +struct strbuf refmap =STRBUF_INIT ; + +argv_array_pushl (&cmd,"fetch" , remote,"--prune" , +"--no-tags" ,"--refmap=" ,NULL ); + +strbuf_addf (&refmap,"+refs/heads/*:refs/prefetch/%s/*" , remote); +argv_array_push (&cmd, refmap.buf); + +if (opts.quiet) +argv_array_push (&cmd,"--quiet" ); + + result =run_command_v_opt (cmd.argv,RUN_GIT_CMD ); + +strbuf_release (&refmap); +return result; +} + +static int fill_each_remote (struct remote *remote,void *cbdata) +{ +struct string_list *remotes = (struct string_list *)cbdata; + +string_list_append (remotes, remote->name); +return 0 ; +} + +static int maintenance_task_prefetch (void ) +{ +int result =0 ; +struct string_list_item *item; +struct string_list remotes =STRING_LIST_INIT_DUP ; + +if (for_each_remote (fill_each_remote, &remotes)) { +error (_ ("failed to fill remotes" )); + result =1 ; + goto cleanup; + } + +/* +* Do not modify the result based on the success of the 'fetch' +* operation, as a loss of network could cause 'fetch' to fail +* quickly. We do not want that to stop the rest of our +* background operations. +*/ +for (item = remotes.items; + item && item < remotes.items + remotes.nr; + item++) +fetch_remote (item->string); + +cleanup: +string_list_clear (&remotes,0 ); +return result; +} +static int maintenance_task_gc (void ) {int result;@@ -871,6 +929,10 @@ static void initialize_tasks(void)for (i =0 ; i <MAX_NUM_TASKS ; i++) tasks[i] =xcalloc (1 ,sizeof (struct maintenance_task )); + tasks[num_tasks]->name ="prefetch" ; + tasks[num_tasks]->fn = maintenance_task_prefetch; + num_tasks++; + tasks[num_tasks]->name ="gc" ; tasks[num_tasks]->fn = maintenance_task_gc; tasks[num_tasks]->enabled =1 ;t/t7900-maintenance.sh +24
-0@@ -44,4 +44,28 @@ test_expect_success 'run --task duplicate' ' test_i18ngrep "cannot be selected multiple times" err ' +test_expect_success 'run --task=prefetch with no remotes' ' + git maintenance run --task=prefetch 2>err && + test_must_be_empty err +' + +test_expect_success 'prefetch multiple remotes' ' + git clone . clone1 && + git clone . clone2 && + git remote add remote1 "file://$(pwd)/clone1" && + git remote add remote2 "file://$(pwd)/clone2" && + git -C clone1 switch -c one && + git -C clone2 switch -c two && + test_commit -C clone1 one && + test_commit -C clone2 two && + GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch && + grep ",\"fetch\",\"remote1\"" run-prefetch.txt && + grep ",\"fetch\",\"remote2\"" run-prefetch.txt && + test_path_is_missing .git/refs/remotes && + test_cmp clone1/.git/refs/heads/one .git/refs/prefetch/remote1/one && + test_cmp clone2/.git/refs/heads/two .git/refs/prefetch/remote2/two && + git log prefetch/remote1/one && + git log prefetch/remote2/two +' + test_doneSignature · 1 line
gitgitgadget
Signature · 1 line
Danh
Re: [PATCH v2 08/18] maintenance: add prefetch task
Quoted 9 lines · Đoàn Trần Công Danh
Quoted 2 lines
When running 'git fetch <remote> <options>' in the background, use the following options for careful updating:
Does this job interfere with
FETCH_HEAD?
From my quick test (by applying 01-08 on top of rc1, and messing with t7900), it looks like yes.I (and some other people, probably) rely on
FETCH_HEADfor our scripts. Hence, it would be nice to not touchFETCH_HEADwith prefetch job.
Very good point. For that, Derrick may want to swallow the single patch from 'jc/no-update-fetch-head' topic into this series and pass the new command line option.
Re: [PATCH v2 08/18] maintenance: add prefetch task
Quoted 9 lines · Junio C Hamano
Quoted 7 lines · Đoàn Trần Công Danh
Quoted 2 lines
When running 'git fetch <remote> <options>' in the background, use the following options for careful updating:
Does this job interfere with
FETCH_HEAD?
From my quick test (by applying 01-08 on top of rc1, and messing with t7900), it looks like yes.
Quoted 6 lines
Quoted 2 lines
I (and some other people, probably) rely on
FETCH_HEADfor our scripts. Hence, it would be nice to not touchFETCH_HEADwith prefetch job.Very good point. For that, Derrick may want to swallow the single patch from 'jc/no-update-fetch-head' topic into this series and pass the new command line option.
Thanks for the pointer! I appreciate the attention to detail here.
I'll rebase onto jc/no-update-fetch-head for the next version, since that branch is based on v2.28.0-rc0, which is recent enough.
Thanks,
-Stolee
Re: [PATCH v2 08/18] maintenance: add prefetch task
Quoted 2 lines · Derrick Stolee
I'll rebase onto jc/no-update-fetch-head for the next version, since that branch is based on v2.28.0-rc0, which is recent enough.
I do not think it is wise to base a work on top of unfinished "you could do it this way, perhaps?" demonstration patch the original author does not have much inclination to finish, though.
When I am really bored, I may go back to the topic to finish it, but I wouldn't mind if you took ownership of it at all.
Thanks.
Re: [PATCH v2 08/18] maintenance: add prefetch task
Quoted 11 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee
I'll rebase onto jc/no-update-fetch-head for the next version, since that branch is based on v2.28.0-rc0, which is recent enough.
I do not think it is wise to base a work on top of unfinished "you could do it this way, perhaps?" demonstration patch the original author does not have much inclination to finish, though.
When I am really bored, I may go back to the topic to finish it, but I wouldn't mind if you took ownership of it at all.
Ah. I didn't understand the status of that branch. I'll pull it in to this topic.
Thanks,
-Stolee
PATCH v2 1/1 fetch: optionally allow disabling FETCH_HEAD update
Quoted 10 lines · Derrick Stolee
Quoted 9 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee
I'll rebase onto jc/no-update-fetch-head for the next version, since that branch is based on v2.28.0-rc0, which is recent enough.
I do not think it is wise to base a work on top of unfinished "you could do it this way, perhaps?" demonstration patch the original author does not have much inclination to finish, though.
Quoted 5 lines
Quoted 2 lines
When I am really bored, I may go back to the topic to finish it, but I wouldn't mind if you took ownership of it at all.
Ah. I didn't understand the status of that branch. I'll pull it in to this topic.
So here is with one of the two things that I found missing in the first iteration of the patch: documentation.
The other thing that I found iffy (and still missing from this version) was what should be done when "git pull" is explicitly given the "--no-write-fetch-head" option.
I think (but didn't check the recent code) that 'git pull' would pass only known-to-make-sense command line options to underlying 'git fetch', so it probably will barf with "unknown option", which is the best case. We might want to make it sure with a new test in 5521. On the other hand, if we get anything other than "no such option", we may want to think if we want to "fix" it or just leave it inside "if it hurts, don't do it" territory.
Thanks.
The patch without doc was Reviewed-by: Taylor Blau <[email protected]> but this round has not been.
-- >8 --
If you run fetch but record the result in remote-tracking branches, and either if you do nothing with the fetched refs (e.g. you are merely mirroring) or if you always work from the remote-tracking refs (e.g. you fetch and then merge origin/branchname separately), you can get away with having no FETCH_HEAD at all.
Teach "git fetch" a command line option "--[no-]write-fetch-head" and "fetch.writeFetchHEAD" configuration variable. Without either, the default is to write FETCH_HEAD, and the usual rule that the command line option defeats configured default applies.
Note that under "--dry-run" mode, FETCH_HEAD is never written; otherwise you'd see list of objects in the file that you do not actually have. Passing --fetch-write-head does not force git fetch to write the file.
Also note that this option is explicitly passed when "git pull" internally invokes "git fetch", so that those who configured their "git fetch" not to write FETCH_HEAD would not be able to break the cooperation between these two commands. "git pull" must see what "git fetch" got recorded in FETCH_HEAD to work correctly.
--- Documentation/config/fetch.txt | 7 ++++++ Documentation/fetch-options.txt | 10 +++++++++ builtin/fetch.c | 19 +++++++++++++--- builtin/pull.c | 3 ++- t/t5510-fetch.sh | 39 +++++++++++++++++++++++++++++++-- 5 files changed, 72 insertions(+), 6 deletions(-)
Documentation/config/fetch.txt +7 -0
@@ -91,3 +91,10 @@ fetch.writeCommitGraph:: merge and the write may take longer. Having an updated commit-graph file helps performance of many Git commands, including `git merge-base`, `git push -f`, and `git log --graph`. Defaults to false. + +fetch.writeFetchHEAD:: + Setting it to false tells `git fetch` not to write the list + of remote refs fetched in the `FETCH_HEAD` file directly + under `$GIT_DIR`. Can be countermanded from the command + line with the `--[no-]write-fetch-head` option. Defaults to + true.
Documentation/fetch-options.txt +10 -0
@@ -64,6 +64,16 @@ documented in linkgit:git-config[1]. --dry-run:: Show what would be done, without making any changes. +ifndef::git-pull[] +--[no-]write-fetch-head:: + Write the list of remote refs fetched in the `FETCH_HEAD` + file directly under `$GIT_DIR`. This is the default unless + the configuration variable `fetch.writeFetchHEAD` is set to + false. Passing `--no-write-fetch-head` from the command + line tells Git not to write the file. Under `--dry-run` + option, the file is never written. +endif::git-pull[] + -f:: --force:: When 'git fetch' is used with `<src>:<dst>` refspec it may
builtin/fetch.c +16 -3
@@ -56,6 +56,7 @@ static int prune_tags = -1; /* unspecified */#define PRUNE_TAGS_BY_DEFAULT 0/* do we prune tags by default? */ static int all, append, dry_run, force, keep, multiple, update_head_ok; +static int write_fetch_head =1 ;static int verbosity, deepen_relative, set_upstream;static int progress =-1 ;static int enable_auto_gc =1 ;
@@ -118,6 +119,10 @@ static int git_fetch_config(const char *k, const char *v, void *cb)return 0 ; } +if (!strcmp (k,"fetch.writefetchhead" )) { + write_fetch_head =git_config_bool (k, v); +return 0 ; + }return git_default_config (k, v, cb); }
@@ -162,6 +167,8 @@ static struct option builtin_fetch_options[] = { PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),OPT_BOOL (0 ,"dry-run" , &dry_run,N_ ("dry run" )), +OPT_BOOL (0 ,"write-fetch-head" , &write_fetch_head, +N_ ("write fetched references to the FETCH_HEAD file" )),OPT_BOOL ('k' ,"keep" , &keep,N_ ("keep downloaded pack" )),OPT_BOOL ('u' ,"update-head-ok" , &update_head_ok, N_("allow updating of HEAD ref")),
@@ -893,7 +900,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,const char *what, *kind;struct ref *rm;char *url; -const char *filename = dry_run ?"/dev/null" :git_path_fetch_head (the_repository); +const char *filename = (!write_fetch_head + ?"/dev/null" + :git_path_fetch_head (the_repository));int want_status;int summary_width =transport_summary_width (ref_map);
@@ -1327,7 +1336,7 @@ static int do_fetch(struct transport *transport, }/* if not appending, truncate FETCH_HEAD */ -if (!append && !dry_run) { +if (!append && write_fetch_head) { retcode =truncate_fetch_head ();if (retcode) goto cleanup;
@@ -1594,7 +1603,7 @@ static int fetch_multiple(struct string_list *list, int max_children)int i, result =0 ;struct argv_array argv =ARGV_ARRAY_INIT ; -if (!append && !dry_run) { +if (!append && write_fetch_head) {int errcode =truncate_fetch_head ();if (errcode)return errcode;
@@ -1795,6 +1804,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)if (depth || deepen_since || deepen_not.nr) deepen =1 ; +/* FETCH_HEAD never gets updated in --dry-run mode */ +if (dry_run) + write_fetch_head =0 ; +if (all) {if (argc ==1 )die (_ ("fetch --all does not take a repository argument" ));
builtin/pull.c +2 -1
@@ -527,7 +527,8 @@ static int run_fetch(const char *repo, const char **refspecs)struct argv_array args =ARGV_ARRAY_INIT ;int ret; -argv_array_pushl (&args,"fetch" ,"--update-head-ok" ,NULL ); +argv_array_pushl (&args,"fetch" ,"--update-head-ok" , +"--write-fetch-head" ,NULL );/* Shared options */ argv_push_verbosity (&args);
t/t5510-fetch.sh +37 -2
@@ -539,13 +539,48 @@ test_expect_success 'fetch into the current branch with --update-head-ok' ' ' -test_expect_success 'fetch --dry-run' ' - +test_expect_success 'fetch --dry-run does not touch FETCH_HEAD' ' rm -f .git/FETCH_HEAD && git fetch --dry-run . && ! test -f .git/FETCH_HEAD ' +test_expect_success '--no-write-fetch-head does not touch FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success '--write-fetch-head gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git fetch --dry-run --write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --dry-run . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --no-write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch --write-fetch-head . && + test -f .git/FETCH_HEAD +' + test_expect_success "should be able to fetch with duplicate refspecs" ' mkdir dups && (
Signature · 1 line
2.28.0
Re: [PATCH v2] fetch: optionally allow disabling FETCH_HEAD update
Quoted 46 lines · Junio C Hamano
Quoted 10 lines · Derrick Stolee
Quoted 9 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee
I'll rebase onto jc/no-update-fetch-head for the next version, since that branch is based on v2.28.0-rc0, which is recent enough.
I do not think it is wise to base a work on top of unfinished "you could do it this way, perhaps?" demonstration patch the original author does not have much inclination to finish, though.
Quoted 5 lines
Quoted 2 lines
When I am really bored, I may go back to the topic to finish it, but I wouldn't mind if you took ownership of it at all.
Ah. I didn't understand the status of that branch. I'll pull it in to this topic.
So here is with one of the two things that I found missing in the first iteration of the patch: documentation.
The other thing that I found iffy (and still missing from this version) was what should be done when "git pull" is explicitly given the "--no-write-fetch-head" option.
I think (but didn't check the recent code) that 'git pull' would pass only known-to-make-sense command line options to underlying 'git fetch', so it probably will barf with "unknown option", which is the best case. We might want to make it sure with a new test in 5521. On the other hand, if we get anything other than "no such option", we may want to think if we want to "fix" it or just leave it inside "if it hurts, don't do it" territory.
Thanks.
The patch without doc was Reviewed-by: Taylor Blau <[email protected]> but this round has not been.
-- >8 --
If you run fetch but record the result in remote-tracking branches, and either if you do nothing with the fetched refs (e.g. you are merely mirroring) or if you always work from the remote-tracking refs (e.g. you fetch and then merge origin/branchname separately), you can get away with having no
FETCH_HEADat all.
Quoted 8 lines
Teach "git fetch" a command line option "--[no-]write-fetch-head" and "fetch.writeFetchHEAD" configuration variable. Without either, the default is to write
FETCH_HEAD, and the usual rule that the command line option defeats configured default applies.Note that under "--dry-run" mode,
FETCH_HEADis never written; otherwise you'd see list of objects in the file that you do not actually have. Passing--fetch-write-head
Typo, it should be --write-fetch-head
Quoted 87 lines
does not force `git
fetch` to write the file.Also note that this option is explicitly passed when "git pull" internally invokes "git fetch", so that those who configured their "git fetch" not to write
FETCH_HEADwould not be able to break the cooperation between these two commands. "git pull" must see what "git fetch" got recorded inFETCH_HEADto work correctly.Signed-off-by Junio C Hamano <[email protected]>--- Documentation/config/fetch.txt | 7 ++++++ Documentation/fetch-options.txt | 10 +++++++++ builtin/fetch.c | 19 +++++++++++++--- builtin/pull.c | 3 ++- t/t5510-fetch.sh | 39 +++++++++++++++++++++++++++++++-- 5 files changed, 72 insertions(+), 6 deletions(-)Documentation/config/fetch.txt +7
-0@@ -91,3 +91,10 @@ fetch.writeCommitGraph:: merge and the write may take longer. Having an updated commit-graph file helps performance of many Git commands, including `git merge-base`, `git push -f`, and `git log --graph`. Defaults to false. + +fetch.writeFetchHEAD:: + Setting it to false tells `git fetch` not to write the list + of remote refs fetched in the `FETCH_HEAD` file directly + under `$GIT_DIR`. Can be countermanded from the command + line with the `--[no-]write-fetch-head` option. Defaults to + true.Documentation/fetch-options.txt +10
-0@@ -64,6 +64,16 @@ documented in linkgit:git-config[1]. --dry-run:: Show what would be done, without making any changes. +ifndef::git-pull[] +--[no-]write-fetch-head:: + Write the list of remote refs fetched in the `FETCH_HEAD` + file directly under `$GIT_DIR`. This is the default unless + the configuration variable `fetch.writeFetchHEAD` is set to + false. Passing `--no-write-fetch-head` from the command + line tells Git not to write the file. Under `--dry-run` + option, the file is never written. +endif::git-pull[] + -f:: --force:: When 'git fetch' is used with `<src>:<dst>` refspec it maybuiltin/fetch.c +7
-0@@ -56,6 +56,7 @@ static int prune_tags = -1; /* unspecified */#define PRUNE_TAGS_BY_DEFAULT 0/* do we prune tags by default? */ static int all, append, dry_run, force, keep, multiple, update_head_ok; +static int write_fetch_head =1 ;static int verbosity, deepen_relative, set_upstream;static int progress =-1 ;static int enable_auto_gc =1 ;@@ -118,6 +119,10 @@ static int git_fetch_config(const char *k, const char *v, void *cb)return 0 ; } +if (!strcmp (k,"fetch.writefetchhead" )) { + write_fetch_head =git_config_bool (k, v); +return 0 ; + }return git_default_config (k, v, cb); }@@ -162,6 +167,8 @@ static struct option builtin_fetch_options[] = { PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),OPT_BOOL (0 ,"dry-run" , &dry_run,N_ ("dry run" )), +OPT_BOOL (0 ,"write-fetch-head" , &write_fetch_head, +N_ ("write fetched references to the FETCH_HEAD file" )),OPT_BOOL ('k' ,"keep" , &keep,N_ ("keep downloaded pack" )),OPT_BOOL ('u' ,"update-head-ok" , &update_head_ok, N_("allow updating of HEAD ref")),
builtin/fetch.c static int store_updated_refs(const char *raw_url, const char *remote_name, · line 905
Quoted 7 lines
builtin/fetch.c +3
-1@@ -893,7 +900,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,const char *what, *kind;struct ref *rm;char *url; -const char *filename = dry_run ?"/dev/null" :git_path_fetch_head (the_repository); +const char *filename = (!write_fetch_head + ?"/dev/null" + :git_path_fetch_head (the_repository));
I was suspicious of this as we haven't cleared write_fetch_head in the --dry-run case yet but the test below seems to show that we still don't write FETCH_HEAD if --dry-run is given. That makes we wonder what the point of setting the filename based on the value of write_fetch_head is.
The rest looks good to me, though it might be worth having a test to check that pull does indeed reject --no-write-fetch-head
Best Wishes
Phillip
Quoted 102 lines
builtin/fetch.c +6
-2int want_status;int summary_width =transport_summary_width (ref_map);@@ -1327,7 +1336,7 @@ static int do_fetch(struct transport *transport, }/* if not appending, truncate FETCH_HEAD */ -if (!append && !dry_run) { +if (!append && write_fetch_head) { retcode =truncate_fetch_head ();if (retcode) goto cleanup;@@ -1594,7 +1603,7 @@ static int fetch_multiple(struct string_list *list, int max_children)int i, result =0 ;struct argv_array argv =ARGV_ARRAY_INIT ; -if (!append && !dry_run) { +if (!append && write_fetch_head) {int errcode =truncate_fetch_head ();if (errcode)return errcode;@@ -1795,6 +1804,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)if (depth || deepen_since || deepen_not.nr) deepen =1 ; +/* FETCH_HEAD never gets updated in --dry-run mode */ +if (dry_run) + write_fetch_head =0 ; +if (all) {if (argc ==1 )die (_ ("fetch --all does not take a repository argument" ));builtin/pull.c +2
-1@@ -527,7 +527,8 @@ static int run_fetch(const char *repo, const char **refspecs)struct argv_array args =ARGV_ARRAY_INIT ;int ret; -argv_array_pushl (&args,"fetch" ,"--update-head-ok" ,NULL ); +argv_array_pushl (&args,"fetch" ,"--update-head-ok" , +"--write-fetch-head" ,NULL );/* Shared options */ argv_push_verbosity (&args);t/t5510-fetch.sh +37
-2@@ -539,13 +539,48 @@ test_expect_success 'fetch into the current branch with --update-head-ok' ' ' -test_expect_success 'fetch --dry-run' ' - +test_expect_success 'fetch --dry-run does not touch FETCH_HEAD' ' rm -f .git/FETCH_HEAD && git fetch --dry-run . && ! test -f .git/FETCH_HEAD ' +test_expect_success '--no-write-fetch-head does not touch FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success '--write-fetch-head gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git fetch --dry-run --write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --dry-run . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --no-write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch --write-fetch-head . && + test -f .git/FETCH_HEAD +' + test_expect_success "should be able to fetch with duplicate refspecs" ' mkdir dups && (
Re: [PATCH v2] fetch: optionally allow disabling FETCH_HEAD update
Quoted 147 lines · Phillip Wood
Quoted 46 lines · Junio C Hamano
Quoted 10 lines · Derrick Stolee
Quoted 9 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee
I'll rebase onto jc/no-update-fetch-head for the next version, since that branch is based on v2.28.0-rc0, which is recent enough.
I do not think it is wise to base a work on top of unfinished "you could do it this way, perhaps?" demonstration patch the original author does not have much inclination to finish, though.
Quoted 5 lines
Quoted 2 lines
When I am really bored, I may go back to the topic to finish it, but I wouldn't mind if you took ownership of it at all.
Ah. I didn't understand the status of that branch. I'll pull it in to this topic.
So here is with one of the two things that I found missing in the first iteration of the patch: documentation.
The other thing that I found iffy (and still missing from this version) was what should be done when "git pull" is explicitly given the "--no-write-fetch-head" option.
I think (but didn't check the recent code) that 'git pull' would pass only known-to-make-sense command line options to underlying 'git fetch', so it probably will barf with "unknown option", which is the best case. We might want to make it sure with a new test in 5521. On the other hand, if we get anything other than "no such option", we may want to think if we want to "fix" it or just leave it inside "if it hurts, don't do it" territory.
Thanks.
The patch without doc was Reviewed-by: Taylor Blau <[email protected]> but this round has not been.
-- >8 --
If you run fetch but record the result in remote-tracking branches, and either if you do nothing with the fetched refs (e.g. you are merely mirroring) or if you always work from the remote-tracking refs (e.g. you fetch and then merge origin/branchname separately), you can get away with having no
FETCH_HEADat all.Quoted 8 lines
Teach "git fetch" a command line option "--[no-]write-fetch-head" and "fetch.writeFetchHEAD" configuration variable. Without either, the default is to write
FETCH_HEAD, and the usual rule that the command line option defeats configured default applies.Note that under "--dry-run" mode,
FETCH_HEADis never written; otherwise you'd see list of objects in the file that you do not actually have. Passing--fetch-write-headTypo, it should be
--write-fetch-headQuoted 89 lines
does not force `git
fetch` to write the file.Also note that this option is explicitly passed when "git pull" internally invokes "git fetch", so that those who configured their "git fetch" not to write
FETCH_HEADwould not be able to break the cooperation between these two commands. "git pull" must see what "git fetch" got recorded inFETCH_HEADto work correctly.Signed-off-by Junio C Hamano <[email protected]>---
Documentation/config/fetch.txt | 7 ++++++Documentation/fetch-options.txt| 10 +++++++++ builtin/fetch.c | 19 +++++++++++++--- builtin/pull.c | 3 ++-
t/t5510-fetch.sh | 39 +++++++++++++++++++++++++++++++--5 files changed, 72 insertions(+), 6 deletions(-)Documentation/config/fetch.txt +0
-0@@ -91,3 +91,10 @@ fetch.writeCommitGraph::merge and the write may take longer. Having an updated commit-graph file helps performance of many Git commands, including
git merge-base,git push -f, andgit log --graph. Defaults to false.Documentation/config/fetch.txt +7
-0+ +fetch.writeFetchHEAD:: + Setting it to false tells `git fetch` not to write the list + of remote refs fetched in the `FETCH_HEAD` file directly + under `$GIT_DIR`. Can be countermanded from the command + line with the `--[no-]write-fetch-head` option. Defaults to + true.Documentation/fetch-options.txt +0
-0@@ -64,6 +64,16 @@ documented in linkgit:git-config[1].--dry-run::
Show what would be done, without making any changes.Documentation/fetch-options.txt +10
-0+ifndef::git-pull[] +--[no-]write-fetch-head:: + Write the list of remote refs fetched in the `FETCH_HEAD` + file directly under `$GIT_DIR`. This is the default unless + the configuration variable `fetch.writeFetchHEAD` is set to + false. Passing `--no-write-fetch-head` from the command + line tells Git not to write the file. Under `--dry-run` + option, the file is never written. +endif::git-pull[] +-f::
--force::
When 'git fetch' is used with<src>:<dst>refspec it maybuiltin/fetch.c +7
-1@@ -56,6 +56,7 @@ static int prune_tags = -1; /* unspecified */ #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */ static int all, append, dry_run, force, keep, multiple, update_head_ok; +static int write_fetch_head =1 ; static int verbosity, deepen_relative, set_upstream; static int progress = -1; static int enable_auto_gc = 1;@@ -118,6 +119,10 @@ static int git_fetch_config(const char *k, const char *v, void *cb) return 0; } + if (!strcmp(k, "fetch.writefetchhead")) { + write_fetch_head = git_config_bool(k, v); + return 0; + } return git_default_config(k, v, cb); }@@ -162,6 +167,8 @@ static struct option builtin_fetch_options[] = { PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "dry-run", &dry_run, N_("dry run")), + OPT_BOOL(0, "write-fetch-head", &write_fetch_head, + N_("write fetched references to the FETCH_HEAD file")), OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")), OPT_BOOL('u', "update-head-ok", &update_head_ok, N_("allow updating of HEAD ref")),@@ -893,7 +900,9 @@ static int store_updated_refs(const char *raw_url,const char *remote_name, const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" :
Quoted 8 lines
builtin/fetch.c static int store_updated_refs(const char *raw_url, const char *remote_name, · line 905
Quoted 4 lines
builtin/fetch.c +3
-0git_path_fetch_head (the_repository); + const char *filename = (!write_fetch_head + ? "/dev/null" + : git_path_fetch_head(the_repository));I was suspicious of this as we haven't cleared
write_fetch_headin the
--dry-run case yet but the test below seems to show that we still don't
writeFETCH_HEADif --dry-run is given. That makes we wonder what the point of setting the filename based on the value ofwrite_fetch_headis.
Sorry ignore that. I misread the hunk header - we have in fact cleared write_fetch_head in the --dry-run case by the time we get here.
Quoted 103 lines
The rest looks good to me, though it might be worth having a test to check that pull does indeed reject --no-write-fetch-head
Best Wishes
Phillip
Quoted 97 lines
builtin/fetch.c +0
-0int want_status;int
summary_width=transport_summary_width(ref_map);builtin/fetch.c +6
-2@@ -1327,7 +1336,7 @@ static int do_fetch(struct transport *transport, } /* if not appending, truncate FETCH_HEAD */ - if (!append && !dry_run) { + if (!append && write_fetch_head) { retcode = truncate_fetch_head(); if (retcode) goto cleanup;@@ -1594,7 +1603,7 @@ static int fetch_multiple(struct string_list *list, int max_children) int i, result = 0; struct argv_array argv = ARGV_ARRAY_INIT; - if (!append && !dry_run) { + if (!append && write_fetch_head) { int errcode = truncate_fetch_head(); if (errcode) return errcode;@@ -1795,6 +1804,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth || deepen_since || deepen_not.nr) deepen = 1; + /* FETCH_HEAD never gets updated in --dry-run mode */ + if (dry_run) + write_fetch_head = 0; + if (all) { if (argc == 1) die(_("fetch --all does not take a repository argument"));builtin/pull.c +2
-1@@ -527,7 +527,8 @@ static int run_fetch(const char *repo, const char **refspecs) struct argv_array args = ARGV_ARRAY_INIT; int ret; - argv_array_pushl(&args, "fetch", "--update-head-ok", NULL); + argv_array_pushl(&args, "fetch", "--update-head-ok", + "--write-fetch-head", NULL); /* Shared options */ argv_push_verbosity(&args);t/t5510-fetch.sh +0
-0@@ -539,13 +539,48 @@ test_expect_success 'fetch into the currentbranch with --update-head-ok' '
't/t5510-fetch.sh +1
-2-test_expect_success 'fetch --dry-run' ' - +test_expect_success 'fetch --dry-run does not touch FETCH_HEAD' 'rm -f .git/FETCH_HEAD &&
git fetch --dry-run . &&
! test -f .git/FETCH_HEAD
't/t5510-fetch.sh +36
-0+test_expect_success '--no-write-fetch-head does not touch FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success '--write-fetch-head gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git fetch --dry-run --write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --dry-run . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --no-write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch --write-fetch-head . && + test -f .git/FETCH_HEAD +' +
test_expect_success"should be able to fetch with duplicate refspecs" ' mkdir dups &&
(
Re: [PATCH v2] fetch: optionally allow disabling FETCH_HEAD update
In reply to Junio C HamanoQuoted 25 lines · Junio C Hamano
Quoted 10 lines · Derrick Stolee
Quoted 9 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee
I'll rebase onto jc/no-update-fetch-head for the next version, since that branch is based on v2.28.0-rc0, which is recent enough.
I do not think it is wise to base a work on top of unfinished "you could do it this way, perhaps?" demonstration patch the original author does not have much inclination to finish, though.
Quoted 5 lines
Quoted 2 lines
When I am really bored, I may go back to the topic to finish it, but I wouldn't mind if you took ownership of it at all.
Ah. I didn't understand the status of that branch. I'll pull it in to this topic.
So here is with one of the two things that I found missing in the first iteration of the patch: documentation.
The other thing that I found iffy (and still missing from this version) was what should be done when "git pull" is explicitly given the "--no-write-fetch-head" option.
Quoted 7 lines
I think (but didn't check the recent code) that 'git pull' would pass only known-to-make-sense command line options to underlying 'git fetch', so it probably will barf with "unknown option", which is the best case. We might want to make it sure with a new test in 5521. On the other hand, if we get anything other than "no such option", we may want to think if we want to "fix" it or just leave it inside "if it hurts, don't do it" territory.
Here is the version I applied and updated. Please let me know what you think.
-->8--
From 3f60a0f0fd388447aa9c2b805b5646039df98d0b Mon Sep 17 00:00:00 2001
From: Junio C Hamano <[email protected]>
Date: Tue, 28 Jul 2020 09:37:59 -0700
Subject: [PATCH] fetch: optionally allow disabling FETCH_HEAD update
If you run fetch but record the result in remote-tracking branches, and either if you do nothing with the fetched refs (e.g. you are merely mirroring) or if you always work from the remote-tracking refs (e.g. you fetch and then merge origin/branchname separately), you can get away with having no FETCH_HEAD at all.
Teach "git fetch" a command line option "--[no-]write-fetch-head" and "fetch.writeFetchHEAD" configuration variable. Without either, the default is to write FETCH_HEAD, and the usual rule that the command line option defeats configured default applies.
Note that under "--dry-run" mode, FETCH_HEAD is never written; otherwise you'd see list of objects in the file that you do not actually have. Passing --write-fetch-head does not force git fetch to write the file.
Also note that this option is explicitly passed when "git pull" internally invokes "git fetch", so that those who configured their "git fetch" not to write FETCH_HEAD would not be able to break the cooperation between these two commands. "git pull" must see what "git fetch" got recorded in FETCH_HEAD to work correctly.
--- Documentation/config/fetch.txt | 7 ++++++ Documentation/fetch-options.txt | 10 +++++++++ builtin/fetch.c | 19 +++++++++++++--- builtin/pull.c | 3 ++- t/t5510-fetch.sh | 39 +++++++++++++++++++++++++++++++-- t/t5521-pull-options.sh | 16 ++++++++++++++ 6 files changed, 88 insertions(+), 6 deletions(-)
Documentation/config/fetch.txt +7 -0
@@ -91,3 +91,10 @@ fetch.writeCommitGraph:: merge and the write may take longer. Having an updated commit-graph file helps performance of many Git commands, including `git merge-base`, `git push -f`, and `git log --graph`. Defaults to false. + +fetch.writeFetchHEAD:: + Setting it to false tells `git fetch` not to write the list + of remote refs fetched in the `FETCH_HEAD` file directly + under `$GIT_DIR`. Can be countermanded from the command + line with the `--[no-]write-fetch-head` option. Defaults to + true.
Documentation/fetch-options.txt +10 -0
@@ -64,6 +64,16 @@ documented in linkgit:git-config[1]. --dry-run:: Show what would be done, without making any changes. +ifndef::git-pull[] +--[no-]write-fetch-head:: + Write the list of remote refs fetched in the `FETCH_HEAD` + file directly under `$GIT_DIR`. This is the default unless + the configuration variable `fetch.writeFetchHEAD` is set to + false. Passing `--no-write-fetch-head` from the command + line tells Git not to write the file. Under `--dry-run` + option, the file is never written. +endif::git-pull[] + -f:: --force:: When 'git fetch' is used with `<src>:<dst>` refspec it may
builtin/fetch.c +16 -3
@@ -56,6 +56,7 @@ static int prune_tags = -1; /* unspecified */#define PRUNE_TAGS_BY_DEFAULT 0/* do we prune tags by default? */ static int all, append, dry_run, force, keep, multiple, update_head_ok; +static int write_fetch_head =1 ;static int verbosity, deepen_relative, set_upstream;static int progress =-1 ;static int enable_auto_gc =1 ;
@@ -118,6 +119,10 @@ static int git_fetch_config(const char *k, const char *v, void *cb)return 0 ; } +if (!strcmp (k,"fetch.writefetchhead" )) { + write_fetch_head =git_config_bool (k, v); +return 0 ; + }return git_default_config (k, v, cb); }
@@ -162,6 +167,8 @@ static struct option builtin_fetch_options[] = { PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),OPT_BOOL (0 ,"dry-run" , &dry_run,N_ ("dry run" )), +OPT_BOOL (0 ,"write-fetch-head" , &write_fetch_head, +N_ ("write fetched references to the FETCH_HEAD file" )),OPT_BOOL ('k' ,"keep" , &keep,N_ ("keep downloaded pack" )),OPT_BOOL ('u' ,"update-head-ok" , &update_head_ok, N_("allow updating of HEAD ref")),
@@ -895,7 +902,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,const char *what, *kind;struct ref *rm;char *url; -const char *filename = dry_run ?"/dev/null" :git_path_fetch_head (the_repository); +const char *filename = (!write_fetch_head + ?"/dev/null" + :git_path_fetch_head (the_repository));int want_status;int summary_width =transport_summary_width (ref_map);
@@ -1329,7 +1338,7 @@ static int do_fetch(struct transport *transport, }/* if not appending, truncate FETCH_HEAD */ -if (!append && !dry_run) { +if (!append && write_fetch_head) { retcode =truncate_fetch_head ();if (retcode) goto cleanup;
@@ -1596,7 +1605,7 @@ static int fetch_multiple(struct string_list *list, int max_children)int i, result =0 ;struct argv_array argv =ARGV_ARRAY_INIT ; -if (!append && !dry_run) { +if (!append && write_fetch_head) {int errcode =truncate_fetch_head ();if (errcode)return errcode;
@@ -1797,6 +1806,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)if (depth || deepen_since || deepen_not.nr) deepen =1 ; +/* FETCH_HEAD never gets updated in --dry-run mode */ +if (dry_run) + write_fetch_head =0 ; +if (all) {if (argc ==1 )die (_ ("fetch --all does not take a repository argument" ));
builtin/pull.c +2 -1
@@ -527,7 +527,8 @@ static int run_fetch(const char *repo, const char **refspecs)struct argv_array args =ARGV_ARRAY_INIT ;int ret; -argv_array_pushl (&args,"fetch" ,"--update-head-ok" ,NULL ); +argv_array_pushl (&args,"fetch" ,"--update-head-ok" , +"--write-fetch-head" ,NULL );/* Shared options */ argv_push_verbosity (&args);
t/t5510-fetch.sh +37 -2
@@ -539,13 +539,48 @@ test_expect_success 'fetch into the current branch with --update-head-ok' ' ' -test_expect_success 'fetch --dry-run' ' - +test_expect_success 'fetch --dry-run does not touch FETCH_HEAD' ' rm -f .git/FETCH_HEAD && git fetch --dry-run . && ! test -f .git/FETCH_HEAD ' +test_expect_success '--no-write-fetch-head does not touch FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success '--write-fetch-head gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git fetch --dry-run --write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and FETCH_HEAD' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD gets defeated by --dry-run' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --dry-run . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --no-write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=yes fetch --no-write-fetch-head . && + ! test -f .git/FETCH_HEAD +' + +test_expect_success 'fetch.writeFetchHEAD and --write-fetch-head' ' + rm -f .git/FETCH_HEAD && + git -c fetch.writeFetchHEAD=no fetch --write-fetch-head . && + test -f .git/FETCH_HEAD +' + test_expect_success "should be able to fetch with duplicate refspecs" ' mkdir dups && (
t/t5521-pull-options.sh +16 -0
@@ -77,6 +77,7 @@ test_expect_success 'git pull -q -v --no-rebase' ' test_must_be_empty out && test -s err) ' + test_expect_success 'git pull --cleanup errors early on invalid argument' ' mkdir clonedcleanup && (cd clonedcleanup && git init &&
@@ -85,6 +86,21 @@ test_expect_success 'git pull --cleanup errors early on invalid argument' ' test -s err) ' +test_expect_success 'git pull --no-write-fetch-head fails' ' + mkdir clonedwfh && + (cd clonedwfh && git init && + test_must_fail git pull --no-write-fetch-head "../parent" >out 2>err && + test_must_be_empty out && + test_i18ngrep "no-write-fetch-head" err) +' + +test_expect_success 'git pull succeeds with fetch.writeFetchHEAD=false' ' + mkdir clonedwfhconfig && + (cd clonedwfhconfig && git init && + git config fetch.writeFetchHEAD false && + git pull "../parent" >out 2>err && + grep FETCH_HEAD err) +' test_expect_success 'git pull --force' ' mkdir clonedoldstyle &&
Signature · 1 line
2.27.0.366.g34746c1d11e.dirty
PATCH v2 9/18 maintenance: add loose-objects task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
One goal of background maintenance jobs is to allow a user to disable auto-gc (gc.auto=0) but keep their repository in a clean state. Without any cleanup, loose objects will clutter the object database and slow operations. In addition, the loose objects will take up extra space because they are not stored with deltas against similar objects.
Create a 'loose-objects' task for the 'git maintenance run' command. This helps clean up loose objects without disrupting concurrent Git commands using the following sequence of events:
1. Run 'git prune-packed' to delete any loose objects that exist in a pack-file. Concurrent commands will prefer the packed version of the object to the loose version. (Of course, there are exceptions for commands that specifically care about the location of an object. These are rare for a user to run on purpose, and we hope a user that has selected background maintenance will not be trying to do foreground maintenance.)
2. Run 'git pack-objects' on a batch of loose objects. These objects are grouped by scanning the loose object directories in lexicographic order until listing all loose objects -or- reaching 50,000 objects. This is more than enough if the loose objects are created only by a user doing normal development. We noticed users with _millions_ of loose objects because VFS for Git downloads blobs on-demand when a file read operation requires populating a virtual file. This has potential of happening in partial clones if someone runs 'git grep' or otherwise evades the batch-download feature for requesting promisor objects.
This step is based on a similar step in Scalar [1] and VFS for Git.
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/LooseObjectsStep.cs
--- Documentation/git-maintenance.txt | 11 ++++ builtin/gc.c | 106 +++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 35 ++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-)
Documentation/git-maintenance.txt +11 -0
@@ -73,6 +73,17 @@ gc:: It can also be disruptive in some situations, as it deletes stale data. +loose-objects:: + The `loose-objects` job cleans up loose objects and places them into + pack-files. In order to prevent race conditions with concurrent Git + commands, it follows a two-step process. First, it deletes any loose + objects that already exist in a pack-file; concurrent Git processes + will examine the pack-file for the object data instead of the loose + object. Second, it creates a new pack-file (starting with "loose-") + containing a batch of loose objects. The batch size is limited to 50 + thousand objects to prevent the job from taking too long on a + repository with many loose objects. + OPTIONS ------- --auto::
builtin/gc.c +105 -1
124 lines not on this page · raw message
t/t7900-maintenance.sh +35 -0
@@ -68,4 +68,39 @@ test_expect_success 'prefetch multiple remotes' ' git log prefetch/remote2/two ' +test_expect_success 'loose-objects task' ' + # Repack everything so we know the state of the object dir + git repack -adk && + + # Hack to stop maintenance from running during "git commit" + echo in use >.git/objects/maintenance.lock && + test_commit create-loose-object && + rm .git/objects/maintenance.lock && + + ls .git/objects >obj-dir-before && + test_file_not_empty obj-dir-before && + ls .git/objects/pack/*.pack >packs-before && + test_line_count = 1 packs-before && + + # The first run creates a pack-file + # but does not delete loose objects. + git maintenance run --task=loose-objects && + ls .git/objects >obj-dir-between && + test_cmp obj-dir-before obj-dir-between && + ls .git/objects/pack/*.pack >packs-between && + test_line_count = 2 packs-between && + + # The second run deletes loose objects + # but does not create a pack-file. + git maintenance run --task=loose-objects && + ls .git/objects >obj-dir-after && + cat >expect <<-\EOF && + info + pack + EOF + test_cmp expect obj-dir-after && + ls .git/objects/pack/*.pack >packs-after && + test_cmp packs-between packs-after +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 09/18] maintenance: add loose-objects task
Quoted 11 lines · Derrick Stolee via GitGitGadget
Create a 'loose-objects' task for the 'git maintenance run' command. This helps clean up loose objects without disrupting concurrent Git commands using the following sequence of events:
1. Run 'git prune-packed' to delete any loose objects that exist in a pack-file. Concurrent commands will prefer the packed version of the object to the loose version. (Of course, there are exceptions for commands that specifically care about the location of an object. These are rare for a user to run on purpose, and we hope a user that has selected background maintenance will not be trying to do foreground maintenance.)
OK. That would make sense.
Quoted 5 lines
2. Run 'git pack-objects' on a batch of loose objects. These objects are grouped by scanning the loose object directories in lexicographic order until listing all loose objects -or- reaching 50,000 objects. This is more than enough if the loose objects are created only by a user doing normal development.
I haven't seen this in action, but my gut feeling is that this would result in horrible locality and deltification in the resulting packfile. The order you feed the objects to pack-objects and the path hint you attach to each object matters quite a lot.
I do agree that it would be useful to have a task to deal with only loose objects without touching existing packfiles. I just am not sure if 2. is a worthwhile thing to do. A poorly constructed pack will also contaminate later packfiles made without "-f" option to "git repack".
Re: [PATCH v2 09/18] maintenance: add loose-objects task
Quoted 27 lines · Junio C Hamano
Quoted 11 lines · Derrick Stolee via GitGitGadget
Create a 'loose-objects' task for the 'git maintenance run' command. This helps clean up loose objects without disrupting concurrent Git commands using the following sequence of events:
1. Run 'git prune-packed' to delete any loose objects that exist in a pack-file. Concurrent commands will prefer the packed version of the object to the loose version. (Of course, there are exceptions for commands that specifically care about the location of an object. These are rare for a user to run on purpose, and we hope a user that has selected background maintenance will not be trying to do foreground maintenance.)
OK. That would make sense.
Quoted 5 lines
2. Run 'git pack-objects' on a batch of loose objects. These objects are grouped by scanning the loose object directories in lexicographic order until listing all loose objects -or- reaching 50,000 objects. This is more than enough if the loose objects are created only by a user doing normal development.
I haven't seen this in action, but my gut feeling is that this would result in horrible locality and deltification in the resulting packfile. The order you feed the objects to pack-objects and the path hint you attach to each object matters quite a lot.
Quoted 5 lines
I do agree that it would be useful to have a task to deal with only loose objects without touching existing packfiles. I just am not sure if 2. is a worthwhile thing to do. A poorly constructed pack will also contaminate later packfiles made without "-f" option to "git repack".
There are several factors going on here:
* In a partial clone, it is likely that we get loose objects only due to a command like "git log -p" that downloads blobs one-by-one. In such a case, this step coming in later and picking up those blobs _will_ find good deltas because they are present in the same batch.
* (I know this case isn't important to core Git, but please indulge me) In a VFS for Git repo, the loose objects correspond to blobs that were faulted in by a virtual filesystem read. In this case, the blobs are usually from a single commit in history, so good deltas between the blobs don't actually exist!
* My experience indicates that the packs created by the loose-objects task are rather small (when created daily). This means that they get selected by the incremental-repack task to repack into a new pack-file where deltas are recomputed with modest success. As mentioned in that task, we saw a significant compression factor using that step for users of the Windows OS repo, mostly due to recomputing tree deltas.
* Some amount of "extra" space is expected with this incremental repacking scheme. The most space-efficient thing to do is a full repack along with a tree walk that detects the paths used for each blob, allowing better hints for delta compression. However, that operation is very _time_ consuming. The trade-off here is something I should make more explicit. In my experience, disk space is cheap but CPU time is expensive. Most repositories could probably do a daily repack without being a disruption to the user. These steps enable maintenance for repositories where a full repack is too disruptive.
I hope this adds some context. I would love if someone who knows more about delta compression could challenge my assumptions. Sharing that expertise can help create better maintenance strategies. Junio's initial concern here is a good first step in that direction.
Thanks,
-Stolee
Re: [PATCH v2 09/18] maintenance: add loose-objects task
Quoted 11 lines · Derrick Stolee
* In a partial clone, it is likely that we get loose objects only due to a command like "git log -p" that downloads blobs one-by-one. In such a case, this step coming in later and picking up those blobs _will_ find good deltas because they are present in the same batch.
* (I know this case isn't important to core Git, but please indulge me) In a VFS for Git repo, the loose objects correspond to blobs that were faulted in by a virtual filesystem read. In this case, the blobs are usually from a single commit in history, so good deltas between the blobs don't actually exist!
Let me stop here by saying that I am now starting to worry about overfitting the repacking strategy to lazy clone repositories. I am perfectly fine with the plan to start with just one strategy overfit for partially cloned repositories, as long as we make sure that we can be extended it to suit other access/object acquisition patterns.
Re: [PATCH v2 09/18] maintenance: add loose-objects task
In reply to Derrick Stolee via GitGitGadgetDocumentation/git-maintenance.txt gc:: · line 82
Quoted 7 lines · Derrick Stolee via GitGitGadget
Documentation/git-maintenance.txt +7
-0+loose-objects:: + The `loose-objects` job cleans up loose objects and places them into + pack-files. In order to prevent race conditions with concurrent Git + commands, it follows a two-step process. First, it deletes any loose + objects that already exist in a pack-file; concurrent Git processes + will examine the pack-file for the object data instead of the loose + object. Second, it creates a new pack-file (starting with "loose-")
[jonathan tan + jonathan nieder] If you are going to document this, probably it should also be tested, so the documentation does not become stale. Or, just don't document it.
Quoted 5 lines
builtin/gc.c +5
-0+static int pack_loose (void ) +{ +struct repository *r = the_repository; +int result =0 ; +struct write_loose_object_data data;
builtin/gc.c static int maintenance_task_gc(void) · line 910
Quoted 10 lines
builtin/gc.c +10
-0+struct strbuf prefix =STRBUF_INIT ; +struct child_process *pack_proc; + +/* +* Do not start pack-objects process +* if there are no loose objects. +*/ +if (!for_each_loose_file_in_objdir (r->objects->odb->path, + loose_object_exists, +NULL ,NULL ,NULL ))
[emily] To me, this is unintuitive - but upon inspection, it's exiting the foreach early if any loose object is found, so this is cheaper than actually counting. Maybe a comment would help to understand? Or we could name the function differently, like "bail_if_loose()" or something?
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 77
Quoted 7 lines
t/t7900-maintenance.sh +7
-0+test_expect_success 'loose-objects task' ' + # Repack everything so we know the state of the object dir + git repack -adk && + + # Hack to stop maintenance from running during "git commit" + echo in use >.git/objects/maintenance.lock && + test_commit create-loose-object &&
[jonathan nieder] Does it make sense to use a different git command which is guaranteed to make a loose object? Is 'git commit' futureproof, if we decide commits should directly create packs in the future? 'git unpack-objects' is guaranteed to make a loose object, although it is clumsy because it needs a packfile to begin with...
[jonathan tan] But, using 'git commit' is easier to understand in this context. Maybe commenting to say that we assume 'git commit' makes 1 or more loose objects will be enough to futureproof - then we have a signal to whoever made a change to make this fail, and that person knows how to fix this test.
Re: [PATCH v2 09/18] maintenance: add loose-objects task
Quoted 12 lines · Emily Shaffer
Documentation/git-maintenance.txt gc:: · line 82
Quoted 7 lines · Derrick Stolee via GitGitGadget
Documentation/git-maintenance.txt +7
-0+loose-objects:: + The `loose-objects` job cleans up loose objects and places them into + pack-files. In order to prevent race conditions with concurrent Git + commands, it follows a two-step process. First, it deletes any loose + objects that already exist in a pack-file; concurrent Git processes + will examine the pack-file for the object data instead of the loose + object. Second, it creates a new pack-file (starting with "loose-")[jonathan tan + jonathan nieder] If you are going to document this, probably it should also be tested, so the documentation does not become stale. Or, just don't document it.
Adding a condition to the test is a good idea.
Quoted 12 lines
Quoted 12 lines
builtin/gc.c +12
-0+static int pack_loose (void ) +{ +struct repository *r = the_repository; +int result =0 ; +struct write_loose_object_data data; +struct strbuf prefix =STRBUF_INIT ; +struct child_process *pack_proc; + +/* +* Do not start pack-objects process +* if there are no loose objects. +*/
Quoted 8 lines
builtin/gc.c static int maintenance_task_gc(void) · line 910
Quoted 3 lines
builtin/gc.c +3
-0+if (!for_each_loose_file_in_objdir (r->objects->odb->path, + loose_object_exists, +NULL ,NULL ,NULL ))[emily] To me, this is unintuitive - but upon inspection, it's exiting the foreach early if any loose object is found, so this is cheaper than actually counting. Maybe a comment would help to understand? Or we could name the function differently, like "
bail_if_loose()" or something?
Sure. "bail_on_loose()" makes more sense to me.
Quoted 5 lines
Quoted 5 lines
t/t7900-maintenance.sh +5
-0+test_expect_success 'loose-objects task' ' + # Repack everything so we know the state of the object dir + git repack -adk && + + # Hack to stop maintenance from running during "git commit"
Quoted 8 lines
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 77
Quoted 2 lines
t/t7900-maintenance.sh +2
-0+ echo in use >.git/objects/maintenance.lock && + test_commit create-loose-object &&[jonathan nieder] Does it make sense to use a different git command which is guaranteed to make a loose object? Is 'git commit' futureproof, if we decide commits should directly create packs in the future? 'git unpack-objects' is guaranteed to make a loose object, although it is clumsy because it needs a packfile to begin with...
"unpack-objects" also has the problem that it won't write the loose object if it exists as a packed object, so it requires moving the pack-file out of the object directory first. (That's also required for testing that the loose objects get packed by the maintenance task, but I'd rather not need to remove the multi-pack-index, too.)
Quoted 5 lines
[jonathan tan] But, using 'git commit' is easier to understand in this context. Maybe commenting to say that we assume 'git commit' makes 1 or more loose objects will be enough to futureproof - then we have a signal to whoever made a change to make this fail, and that person knows how to fix this test.
I'll add the comment for now. Thanks.
-Stolee
PATCH v2 10/18 maintenance: add incremental-repack task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The previous change cleaned up loose objects using the 'loose-objects' that can be run safely in the background. Add a similar job that performs similar cleanups for pack-files.
One issue with running 'git repack' is that it is designed to repack all pack-files into a single pack-file. While this is the most space-efficient way to store object data, it is not time or memory efficient. This becomes extremely important if the repo is so large that a user struggles to store two copies of the pack on their disk.
Instead, perform an "incremental" repack by collecting a few small pack-files into a new pack-file. The multi-pack-index facilitates this process ever since 'git multi-pack-index expire' was added in 19575c7 (multi-pack-index: implement 'expire' subcommand, 2019-06-10) and 'git multi-pack-index repack' was added in ce1e4a1 (midx: implement midx_repack(), 2019-06-10).
The 'incremental-repack' task runs the following steps:
1. 'git multi-pack-index write' creates a multi-pack-index file if one did not exist, and otherwise will update the multi-pack-index with any new pack-files that appeared since the last write. This is particularly relevant with the background fetch job.
When the multi-pack-index sees two copies of the same object, it stores the offset data into the newer pack-file. This means that some old pack-files could become "unreferenced" which I will use to mean "a pack-file that is in the pack-file list of the multi-pack-index but none of the objects in the multi-pack-index reference a location inside that pack-file."
2. 'git multi-pack-index expire' deletes any unreferenced pack-files and updaes the multi-pack-index to drop those pack-files from the list. This is safe to do as concurrent Git processes will see the multi-pack-index and not open those packs when looking for object contents. (Similar to the 'loose-objects' job, there are some Git commands that open pack-files regardless of the multi-pack-index, but they are rarely used. Further, a user that self-selects to use background operations would likely refrain from using those commands.)
3. 'git multi-pack-index repack --bacth-size=<size>' collects a set of pack-files that are listed in the multi-pack-index and creates a new pack-file containing the objects whose offsets are listed by the multi-pack-index to be in those objects. The set of pack- files is selected greedily by sorting the pack-files by modified time and adding a pack-file to the set if its "expected size" is smaller than the batch size until the total expected size of the selected pack-files is at least the batch size. The "expected size" is calculated by taking the size of the pack-file divided by the number of objects in the pack-file and multiplied by the number of objects from the multi-pack-index with offset in that pack-file. The expected size approximats how much data from that pack-file will contribute to the resulting pack-file size. The intention is that the resulting pack-file will be close in size to the provided batch size.
The next run of the incremental-repack task will delete these repacked pack-files during the 'expire' step.
In this version, the batch size is set to "0" which ignores the size restrictions when selecting the pack-files. It instead selects all pack-files and repacks all packed objects into a single pack-file. This will be updated in the next change, but it requires doing some calculations that are better isolated to a separate change.
Each of the above steps update the multi-pack-index file. After each step, we verify the new multi-pack-index. If the new multi-pack-index is corrupt, then delete the multi-pack-index, rewrite it from scratch, and stop doing the later steps of the job. This is intended to be an extra-safe check without leaving a repo with many pack-files without a multi-pack-index.
These steps are based on a similar background maintenance step in Scalar (and VFS for Git) [1]. This was incredibly effective for users of the Windows OS repository. After using the same VFS for Git repository for over a year, some users had _thousands_ of pack-files that combined to up to 250 GB of data. We noticed a few users were running into the open file descriptor limits (due in part to a bug in the multi-pack-index fixed by af96fe3 (midx: add packs to packed_git linked list, 2019-04-29).
These pack-files were mostly small since they contained the commits and trees that were pushed to the origin in a given hour. The GVFS protocol includes a "prefetch" step that asks for pre-computed pack- files containing commits and trees by timestamp. These pack-files were grouped into "daily" pack-files once a day for up to 30 days. If a user did not request prefetch packs for over 30 days, then they would get the entire history of commits and trees in a new, large pack-file. This led to a large number of pack-files that had poor delta compression.
By running this pack-file maintenance step once per day, these repos with thousands of packs spanning 200+ GB dropped to dozens of pack- files spanning 30-50 GB. This was done all without removing objects from the system and using a constant batch size of two gigabytes. Once the work was done to reduce the pack-files to small sizes, the batch size of two gigabytes means that not every run triggers a repack operation, so the following run will not expire a pack-file. This has kept these repos in a "clean" state.
[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/PackfileMaintenanceStep.cs
--- Documentation/git-maintenance.txt | 15 ++++ builtin/gc.c | 121 +++++++++++++++++++++++++++++- midx.c | 2 +- midx.h | 1 + t/t7900-maintenance.sh | 37 +++++++++ 5 files changed, 174 insertions(+), 2 deletions(-)
Documentation/git-maintenance.txt +15 -0
@@ -84,6 +84,21 @@ loose-objects:: thousand objects to prevent the job from taking too long on a repository with many loose objects. +incremental-repack:: + The `incremental-repack` job repacks the object directory + using the `multi-pack-index` feature. In order to prevent race + conditions with concurrent Git commands, it follows a two-step + process. First, it deletes any pack-files included in the + `multi-pack-index` where none of the objects in the + `multi-pack-index` reference those pack-files; this only happens + if all objects in the pack-file are also stored in a newer + pack-file. Second, it selects a group of pack-files whose "expected + size" is below the batch size until the group has total expected + size at least the batch size; see the `--batch-size` option for + the `repack` subcommand in linkgit:git-multi-pack-index[1]. The + default batch-size is zero, which is a special case that attempts + to repack all pack-files into a single pack-file. + OPTIONS ------- --auto::
builtin/gc.c +120 -1
145 lines not on this page · raw message
midx.c +1 -1
@@ -36,7 +36,7 @@#define PACK_EXPIRED UINT_MAX -static char *get_midx_filename (const char *object_dir) +char *get_midx_filename (const char *object_dir) {return xstrfmt ("%s/pack/multi-pack-index" , object_dir); }
midx.h +1 -0
@@ -37,6 +37,7 @@ struct multi_pack_index {#define MIDX_PROGRESS (1 << 0) +char *get_midx_filename (const char *object_dir);struct multi_pack_index *load_multi_pack_index (const char *object_dir,int local);int prepare_midx_pack (struct repository *r,struct multi_pack_index *m,uint32_t pack_int_id);int bsearch_midx (const struct object_id *oid,struct multi_pack_index *m,uint32_t *result);
t/t7900-maintenance.sh +37 -0
@@ -103,4 +103,41 @@ test_expect_success 'loose-objects task' ' test_cmp packs-between packs-after ' +test_expect_success 'incremental-repack task' ' + packDir=.git/objects/pack && + for i in $(test_seq 1 5) + do + test_commit $i || return 1 + done && + + # Create three disjoint pack-files with size BIG, small, small. + echo HEAD~2 | git pack-objects --revs $packDir/test-1 && + test_tick && + git pack-objects --revs $packDir/test-2 <<-\EOF && + HEAD~1 + ^HEAD~2 + EOF + test_tick && + git pack-objects --revs $packDir/test-3 <<-\EOF && + HEAD + ^HEAD~1 + EOF + rm -f $packDir/pack-* && + rm -f $packDir/loose-* && + ls $packDir/*.pack >packs-before && + test_line_count = 3 packs-before && + + # the job repacks the two into a new pack, but does not + # delete the old ones. + git maintenance run --task=incremental-repack && + ls $packDir/*.pack >packs-between && + test_line_count = 4 packs-between && + + # the job deletes the two old packs, and does not write + # a new one because only one pack remains. + git maintenance run --task=incremental-repack && + ls .git/objects/pack/*.pack >packs-after && + test_line_count = 1 packs-after +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 10/18] maintenance: add incremental-repack task
Quoted 11 lines · Derrick Stolee via GitGitGadget
1. 'git multi-pack-index write' creates a multi-pack-index file if one did not exist, and otherwise will update the multi-pack-index with any new pack-files that appeared since the last write. This is particularly relevant with the background fetch job.
When the multi-pack-index sees two copies of the same object, it stores the offset data into the newer pack-file. This means that some old pack-files could become "unreferenced" which I will use to mean "a pack-file that is in the pack-file list of the multi-pack-index but none of the objects in the multi-pack-index reference a location inside that pack-file."
An obvious alternative is to favor the copy in the older pack, right? Is the expectation that over time, most of the objects that are relevant would reappear in newer packs, so that eventually by favoring the copies in the newer packs, we can retire and remove the old pack, keeping only the newer ones?
But would that assumption hold? The old packs hold objects that are necessary for the older parts of the history, so unless you are cauterizing away the old history, these objects in the older packs are likely to stay with us longer than those used by the newer parts of the history, some of which may not even have been pushed out yet and can be rebased away?
Quoted 9 lines
2. 'git multi-pack-index expire' deletes any unreferenced pack-files and updaes the multi-pack-index to drop those pack-files from the list. This is safe to do as concurrent Git processes will see the multi-pack-index and not open those packs when looking for object contents. (Similar to the 'loose-objects' job, there are some Git commands that open pack-files regardless of the multi-pack-index, but they are rarely used. Further, a user that self-selects to use background operations would likely refrain from using those commands.)
OK.
Quoted 4 lines
3. 'git multi-pack-index repack --bacth-size=<size>' collects a set of pack-files that are listed in the multi-pack-index and creates a new pack-file containing the objects whose offsets are listed by the multi-pack-index to be in those objects. The set of pack-
Quoted 8 lines
files is selected greedily by sorting the pack-files by modified time and adding a pack-file to the set if its "expected size" is smaller than the batch size until the total expected size of the selected pack-files is at least the batch size. The "expected size" is calculated by taking the size of the pack-file divided by the number of objects in the pack-file and multiplied by the number of objects from the multi-pack-index with offset in that pack-file. The expected size approximats how much data from that
approximates.
Quoted 19 lines
pack-file will contribute to the resulting pack-file size. The intention is that the resulting pack-file will be close in size to the provided batch size.
builtin/gc.c +15
-0+static int maintenance_task_incremental_repack (void ) +{ +if (multi_pack_index_write ()) { +error (_ ("failed to write multi-pack-index" )); +return 1 ; + } + +if (multi_pack_index_verify ()) { +warning (_ ("multi-pack-index verify failed after initial write" )); +return rewrite_multi_pack_index (); + } + +if (multi_pack_index_expire ()) { +error (_ ("multi-pack-index expire failed" )); +return 1 ;
builtin/gc.c static int maintenance_task_loose_objects(void) · line 1071
Quoted 10 lines
builtin/gc.c +10
-0+ } + +if (multi_pack_index_verify ()) { +warning (_ ("multi-pack-index verify failed after expire" )); +return rewrite_multi_pack_index (); + } +if (multi_pack_index_repack ()) { +error (_ ("multi-pack-index repack failed" )); +return 1 ; + }
Hmph, I wonder if these warning should come from each helper functions that are static to this function anyway.
It also makes it easier to reason about this function by eliminating the need for having a different pattern only for the verify helper. Instead, verify could call rewrite internally when it notices a breakage. I.e.
if (multi_pack_index_write())
return 1;
if (multi_pack_index_verify("after initial write"))
return 1;
if (multi_pack_index_exire())
return 1;
...Also, it feels odd, compared to our internal API convention, that positive non-zero is used as an error here.
Quoted 7 lines
builtin/gc.c +3
-0+return 0 ; +} +typedef int maintenance_task_fn (void );struct maintenance_task {
builtin/gc.c static void initialize_tasks(void) · line 1161
Quoted 10 lines
builtin/gc.c +4
-0@@ -1037,6 +1152,10 @@ static void initialize_tasks(void) tasks[num_tasks]->fn = maintenance_task_loose_objects; num_tasks++; + tasks[num_tasks]->name ="incremental-repack" ; + tasks[num_tasks]->fn = maintenance_task_incremental_repack; + num_tasks++; + tasks[num_tasks]->name ="gc" ; tasks[num_tasks]->fn = maintenance_task_gc; tasks[num_tasks]->enabled =1 ;
Exactly the same comment as 08/18 about natural/inherent ordering applies here as well.
Thanks.
Re: [PATCH v2 10/18] maintenance: add incremental-repack task
Quoted 20 lines · Junio C Hamano
Quoted 11 lines · Derrick Stolee via GitGitGadget
1. 'git multi-pack-index write' creates a multi-pack-index file if one did not exist, and otherwise will update the multi-pack-index with any new pack-files that appeared since the last write. This is particularly relevant with the background fetch job.
When the multi-pack-index sees two copies of the same object, it stores the offset data into the newer pack-file. This means that some old pack-files could become "unreferenced" which I will use to mean "a pack-file that is in the pack-file list of the multi-pack-index but none of the objects in the multi-pack-index reference a location inside that pack-file."
An obvious alternative is to favor the copy in the older pack, right? Is the expectation that over time, most of the objects that are relevant would reappear in newer packs, so that eventually by favoring the copies in the newer packs, we can retire and remove the old pack, keeping only the newer ones?
Quoted 6 lines
But would that assumption hold? The old packs hold objects that are necessary for the older parts of the history, so unless you are cauterizing away the old history, these objects in the older packs are likely to stay with us longer than those used by the newer parts of the history, some of which may not even have been pushed out yet and can be rebased away?
If we created a new pack-file containing an already-packed object, then shouldn't we assume that the new pack-file does a _better_ job of compressing that object? Or at least, doesn't make it worse?
For example, if we use 'git multi-pack-index repack --batch-size=0', then this creates a new pack-file containing every previously-packed object. This new pack-file should have better delta compression than the previous setup across multiple pack-files. We want this new pack-file to be used, not the old ones.
This "pick the newest pack" strategy is also what allows us to safely use the 'expire' option to drop old pack-files. If we always keep the old copies, then when we try to switch the new pack-files, we cannot delete the old packs safely because a concurrent Git process could be trying to reference it. One race is as follows:
* Process A opens the multi-pack-index referring to old pack P. It doesn't open the pack-file as it hasn't tried to parse objects yet.
* Process B is 'git multi-pack-index expire'. It sees that pack P can be dropped because all objects appear in newer pack-files. It deletes P.
* Process A tries to read from P, and this fails. A must reprepare its representation of the pack-files.
This is the less disruptive race since A can recover with a small cost to its performance. The worse race (on Windows) is this:
* Process A loads the multi-pack-index and tries to parse an object by loading "old" pack P.
* Process B tries to delete P. However, on Windows the handle to P by A prevents the deletion.
At this point, there could be two resolutions. The first is to have the 'expire' fail because we can't delete A. This means we might never delete A in a busy repository. The second is that the 'expire' command continues and drops A from the list in the multi-pack-index. However, now all Git processes add A to the packed_git list because it isn't referenced by the multi-pack-index.
In summary: the decision to pick the newer copies is a fundamental part of how the write->expire->repack loop was designed.
Quoted 65 lines
Quoted 9 lines
2. 'git multi-pack-index expire' deletes any unreferenced pack-files and updaes the multi-pack-index to drop those pack-files from the list. This is safe to do as concurrent Git processes will see the multi-pack-index and not open those packs when looking for object contents. (Similar to the 'loose-objects' job, there are some Git commands that open pack-files regardless of the multi-pack-index, but they are rarely used. Further, a user that self-selects to use background operations would likely refrain from using those commands.)
OK.
Quoted 4 lines
3. 'git multi-pack-index repack --bacth-size=<size>' collects a set of pack-files that are listed in the multi-pack-index and creates a new pack-file containing the objects whose offsets are listed by the multi-pack-index to be in those objects. The set of pack-
Quoted 8 lines
files is selected greedily by sorting the pack-files by modified time and adding a pack-file to the set if its "expected size" is smaller than the batch size until the total expected size of the selected pack-files is at least the batch size. The "expected size" is calculated by taking the size of the pack-file divided by the number of objects in the pack-file and multiplied by the number of objects from the multi-pack-index with offset in that pack-file. The expected size approximats how much data from that
approximates.
Quoted 19 lines
pack-file will contribute to the resulting pack-file size. The intention is that the resulting pack-file will be close in size to the provided batch size.
builtin/gc.c +15
-0+static int maintenance_task_incremental_repack (void ) +{ +if (multi_pack_index_write ()) { +error (_ ("failed to write multi-pack-index" )); +return 1 ; + } + +if (multi_pack_index_verify ()) { +warning (_ ("multi-pack-index verify failed after initial write" )); +return rewrite_multi_pack_index (); + } + +if (multi_pack_index_expire ()) { +error (_ ("multi-pack-index expire failed" )); +return 1 ;builtin/gc.c static int maintenance_task_loose_objects(void) · line 1071
Quoted 10 lines
builtin/gc.c +10
-0+ } + +if (multi_pack_index_verify ()) { +warning (_ ("multi-pack-index verify failed after expire" )); +return rewrite_multi_pack_index (); + } +if (multi_pack_index_repack ()) { +error (_ ("multi-pack-index repack failed" )); +return 1 ; + }Hmph, I wonder if these warning should come from each helper functions that are static to this function anyway.
It also makes it easier to reason about this function by eliminating the need for having a different pattern only for the verify helper. Instead, verify could call rewrite internally when it notices a breakage. I.e.
Quoted 7 lines
if (multi_pack_index_write())
return 1;
if (multi_pack_index_verify("after initial write"))
return 1;
if (multi_pack_index_exire())
return 1;
...This is a cleaner model. I'll work on that.
Quoted 2 lines
Also, it feels odd, compared to our internal API convention, that positive non-zero is used as an error here.
...
Quoted 2 lines
Exactly the same comment as 08/18 about natural/inherent ordering applies here as well.
I'll leave these to be resolved in the earlier messages.
Thanks,
-Stolee
Re: [PATCH v2 10/18] maintenance: add incremental-repack task
In reply to Derrick Stolee via GitGitGadgetQuoted 4 lines · Derrick Stolee via GitGitGadget
1. 'git multi-pack-index write' creates a multi-pack-index file if one did not exist, and otherwise will update the multi-pack-index with any new pack-files that appeared since the last write. This is particularly relevant with the background fetch job.
[emily shaffer] Will this use midx even if the user has disabled it in their config?
t/t7900-maintenance.sh test_expect_success 'loose-objects task' ' · line 104
Quoted 7 lines
t/t7900-maintenance.sh +0
-0@@ -103,4 +103,41 @@ test_expect_success 'loose-objects task' ' test_cmp packs-between packs-after '
[emily shaffer] Can we include a test to prove that this task is or is not run if core.multipackindex is set or unset? That behavior is hard to deduce from the code... we might want to be cautious.
Quoted 42 lines
t/t7900-maintenance.sh +37
-1+test_expect_success 'incremental-repack task' ' + packDir=.git/objects/pack && + for i in $(test_seq 1 5) + do + test_commit $i || return 1 + done && + + # Create three disjoint pack-files with size BIG, small, small. + echo HEAD~2 | git pack-objects --revs $packDir/test-1 && + test_tick && + git pack-objects --revs $packDir/test-2 <<-\EOF && + HEAD~1 + ^HEAD~2 + EOF + test_tick && + git pack-objects --revs $packDir/test-3 <<-\EOF && + HEAD + ^HEAD~1 + EOF + rm -f $packDir/pack-* && + rm -f $packDir/loose-* && + ls $packDir/*.pack >packs-before && + test_line_count = 3 packs-before && + + # the job repacks the two into a new pack, but does not + # delete the old ones. + git maintenance run --task=incremental-repack && + ls $packDir/*.pack >packs-between && + test_line_count = 4 packs-between && + + # the job deletes the two old packs, and does not write + # a new one because only one pack remains. + git maintenance run --task=incremental-repack && + ls .git/objects/pack/*.pack >packs-after && + test_line_count = 1 packs-after +' + test_done --gitgitgadget
PATCH v2 11/18 maintenance: auto-size incremental-repack batch
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
When repacking during the 'incremental-repack' task, we use the --batch-size option in 'git multi-pack-index repack'. The initial setting used --batch-size=0 to repack everything into a single pack-file. This is not sustaintable for a large repository. The amount of work required is also likely to use too many system resources for a background job.
Update the 'incremental-repack' task by dynamically computing a --batch-size option based on the current pack-file structure.
The dynamic default size is computed with this idea in mind for a client repository that was cloned from a very large remote: there is likely one "big" pack-file that was created at clone time. Thus, do not try repacking it as it is likely packed efficiently by the server.
Instead, we select the second-largest pack-file, and create a batch size that is one larger than that pack-file. If there are three or more pack-files, then this guarantees that at least two will be combined into a new pack-file.
Of course, this means that the second-largest pack-file size is likely to grow over time and may eventually surpass the initially-cloned pack-file. Recall that the pack-file batch is selected in a greedy
manner: the packs are considered from oldest to newest and are selected if they have size smaller than the batch size until the total selected size is larger than the batch size. Thus, that oldest "clone" pack will be first to repack after the new data creates a pack larger than that.
We also want to place some limits on how large these pack-files become, in order to bound the amount of time spent repacking. A maximum batch-size of two gigabytes means that large repositories will never be packed into a single pack-file using this job, but also that repack is rather expensive. This is a trade-off that is valuable to have if the maintenance is being run automatically or in the background. Users who truly want to optimize for space and performance (and are willing to pay the upfront cost of a full repack) can use the 'gc' task to do so.
--- builtin/gc.c | 48 +++++++++++++++++++++++++++++++++++++++++- t/t7900-maintenance.sh | 5 +++-- 2 files changed, 50 insertions(+), 3 deletions(-)
builtin/gc.c +47 -1
@@ -1021,19 +1021,65 @@ static int multi_pack_index_expire(void)return result; } +#define TWO_GIGABYTES (2147483647) +#define UNSET_BATCH_SIZE ((unsigned long)-1) + +static off_t get_auto_pack_size (void ) +{ +/* +* The "auto" value is special: we optimize for +* one large pack-file (i.e. from a clone) and +* expect the rest to be small and they can be +* repacked quickly. +* +* The strategy we select here is to select a +* size that is one more than the second largest +* pack-file. This ensures that we will repack +* at least two packs if there are three or more +* packs. +*/ +off_t max_size =0 ; +off_t second_largest_size =0 ; +off_t result_size; +struct packed_git *p; +struct repository *r = the_repository; + +reprepare_packed_git (r); +for (p =get_all_packs (r); p; p = p->next) { +if (p->pack_size > max_size) { + second_largest_size = max_size; + max_size = p->pack_size; + }else if (p->pack_size > second_largest_size) + second_largest_size = p->pack_size; + } + + result_size = second_largest_size +1 ; + +/* But limit ourselves to a batch size of 2g */ +if (result_size >TWO_GIGABYTES ) + result_size =TWO_GIGABYTES ; + +return result_size; +} +static int multi_pack_index_repack (void ) {int result;struct argv_array cmd =ARGV_ARRAY_INIT ; +struct strbuf batch_arg =STRBUF_INIT ; +argv_array_pushl (&cmd,"multi-pack-index" ,"repack" ,NULL );if (opts.quiet)argv_array_push (&cmd,"--no-progress" ); -argv_array_push (&cmd,"--batch-size=0" ); +strbuf_addf (&batch_arg,"--batch-size=%" PRIuMAX, + (uintmax_t )get_auto_pack_size ()); +argv_array_push (&cmd, batch_arg.buf);close_object_store (the_repository->objects); result =run_command_v_opt (cmd.argv,RUN_GIT_CMD ); +strbuf_release (&batch_arg);if (result &&multi_pack_index_verify ()) {warning (_ ("multi-pack-index verify failed after repack" ));
t/t7900-maintenance.sh +3 -2
@@ -134,10 +134,11 @@ test_expect_success 'incremental-repack task' ' test_line_count = 4 packs-between && # the job deletes the two old packs, and does not write - # a new one because only one pack remains. + # a new one because the batch size is not high enough to + # pack the largest pack-file. git maintenance run --task=incremental-repack && ls .git/objects/pack/*.pack >packs-after && - test_line_count = 1 packs-after + test_line_count = 2 packs-after ' test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 6 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
When repacking during the 'incremental-repack' task, we use the --batch-size option in 'git multi-pack-index repack'. The initial setting used --batch-size=0 to repack everything into a single pack-file. This is not sustaintable for a large repository. The amount of work required is
sustainable.
Quoted 4 lines
also likely to use too many system resources for a background job.
Update the 'incremental-repack' task by dynamically computing a --batch-size option based on the current pack-file structure.
OK.
Quoted 18 lines
The dynamic default size is computed with this idea in mind for a client repository that was cloned from a very large remote: there is likely one "big" pack-file that was created at clone time. Thus, do not try repacking it as it is likely packed efficiently by the server.
Instead, we select the second-largest pack-file, and create a batch size that is one larger than that pack-file. If there are three or more pack-files, then this guarantees that at least two will be combined into a new pack-file.
Of course, this means that the second-largest pack-file size is likely to grow over time and may eventually surpass the initially-cloned pack-file. Recall that the pack-file batch is selected in a greedy
manner: the packs are considered from oldest to newest and are selected if they have size smaller than the batch size until the total selected size is larger than the batch size. Thus, that oldest "clone" pack will be first to repack after the new data creates a pack larger than that.
Quoted 8 lines
We also want to place some limits on how large these pack-files become, in order to bound the amount of time spent repacking. A maximum batch-size of two gigabytes means that large repositories will never be packed into a single pack-file using this job, but also that repack is rather expensive. This is a trade-off that is valuable to have if the maintenance is being run automatically or in the background. Users who truly want to optimize for space and performance (and are willing to pay the upfront cost of a full repack) can use the 'gc' task to do so.
It might be too late to ask this now, but how does the quality of the resulting combined pack ensured, wrt locality and deltification?
Quoted 23 lines
builtin/gc.c +23
-0+#define TWO_GIGABYTES (2147483647) +#define UNSET_BATCH_SIZE ((unsigned long)-1) + +static off_t get_auto_pack_size (void ) +{ +/* +* The "auto" value is special: we optimize for +* one large pack-file (i.e. from a clone) and +* expect the rest to be small and they can be +* repacked quickly. +* +* The strategy we select here is to select a +* size that is one more than the second largest +* pack-file. This ensures that we will repack +* at least two packs if there are three or more +* packs. +*/ +off_t max_size =0 ; +off_t second_largest_size =0 ; +off_t result_size; +struct packed_git *p; +struct repository *r = the_repository; +
builtin/gc.c static int multi_pack_index_expire(void) · line 1056
Quoted 10 lines
builtin/gc.c +10
-0+reprepare_packed_git (r); +for (p =get_all_packs (r); p; p = p->next) { +if (p->pack_size > max_size) { + second_largest_size = max_size; + max_size = p->pack_size; + }else if (p->pack_size > second_largest_size) + second_largest_size = p->pack_size; + } + + result_size = second_largest_size +1 ;
We won't worry about this addition wrapping around; I guess we cannot do anything intelligent when it happens.
builtin/gc.c static int multi_pack_index_expire(void) · line 1060
Quoted 3 lines
builtin/gc.c +3
-0+/* But limit ourselves to a batch size of 2g */ +if (result_size >TWO_GIGABYTES ) + result_size =TWO_GIGABYTES ;
Well, when it happens, we'd cap to 2G, which must be a reasonable fallback value, so it would be OK.
Quoted 12 lines
builtin/gc.c +5
-0+return result_size; +} +static int multi_pack_index_repack (void ) {int result;struct argv_array cmd =ARGV_ARRAY_INIT ; +struct strbuf batch_arg =STRBUF_INIT ; +argv_array_pushl (&cmd,"multi-pack-index" ,"repack" ,NULL );if (opts.quiet)
builtin/gc.c static int multi_pack_index_expire(void) · line 1082
Quoted 10 lines
builtin/gc.c +4
-1argv_array_push (&cmd,"--no-progress" ); -argv_array_push (&cmd,"--batch-size=0" ); +strbuf_addf (&batch_arg,"--batch-size=%" PRIuMAX, + (uintmax_t )get_auto_pack_size ()); +argv_array_push (&cmd, batch_arg.buf);close_object_store (the_repository->objects); result =run_command_v_opt (cmd.argv,RUN_GIT_CMD ); +strbuf_release (&batch_arg);
I think I saw a suggestion to use xstrfmt() with free() instead of the sequence of strbuf_init(), strbuf_addf(), and strbuf_release() in a similar but different context. Perhaps we should follow suit here, too?
Thanks. That's it for today from me.
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 8 lines · Junio C Hamano
Quoted 7 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +5
-1+struct strbuf batch_arg =STRBUF_INIT ; + -argv_array_push (&cmd,"--batch-size=0" ); +strbuf_addf (&batch_arg,"--batch-size=%" PRIuMAX, + (uintmax_t )get_auto_pack_size ()); +argv_array_push (&cmd, batch_arg.buf);
Quoted 6 lines
Quoted 1 line
+
strbuf_release(&batch_arg);I think I saw a suggestion to use
xstrfmt()withfree()instead of the sequence ofstrbuf_init(),strbuf_addf(), andstrbuf_release()in a similar but different context. Perhaps we should follow suit here, too?
Perhaps I'm missing something obvious, but wouldn't argv_array_pushf() be even simpler?
argv_array_pushf(&cmd, "--batch-size=%"PRIuMAX,
(uintmax_t)get_auto_pack_size());Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 16 lines · Eric Sunshine
Quoted 8 lines · Junio C Hamano
Quoted 7 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +5
-1+struct strbuf batch_arg =STRBUF_INIT ; + -argv_array_push (&cmd,"--batch-size=0" ); +strbuf_addf (&batch_arg,"--batch-size=%" PRIuMAX, + (uintmax_t )get_auto_pack_size ()); +argv_array_push (&cmd, batch_arg.buf);Quoted 6 lines
Quoted 1 line
+
strbuf_release(&batch_arg);I think I saw a suggestion to use
xstrfmt()withfree()instead of the sequence ofstrbuf_init(),strbuf_addf(), andstrbuf_release()in a similar but different context. Perhaps we should follow suit here, too?
Quoted 5 lines
Perhaps I'm missing something obvious, but wouldn't
argv_array_pushf()be even simpler?argv_array_pushf(&cmd, "--batch-size=%"PRIuMAX, (uintmax_t)get_auto_pack_size());
No, it was me who was missing an obvious and better alternative.
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 18 lines · Junio C Hamano
Quoted 16 lines · Eric Sunshine
Quoted 14 lines · Junio C Hamano
builtin/gc.c static int multi_pack_index_expire(void) · line 1082
Quoted 8 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +6
-1+struct strbuf batch_arg =STRBUF_INIT ; + -argv_array_push (&cmd,"--batch-size=0" ); +strbuf_addf (&batch_arg,"--batch-size=%" PRIuMAX, + (uintmax_t )get_auto_pack_size ()); +argv_array_push (&cmd, batch_arg.buf); +strbuf_release (&batch_arg);I think I saw a suggestion to use
xstrfmt()withfree()instead of the sequence ofstrbuf_init(),strbuf_addf(), andstrbuf_release()in a similar but different context. Perhaps we should follow suit here, too?
Quoted 7 lines
Quoted 5 lines
Perhaps I'm missing something obvious, but wouldn't
argv_array_pushf()be even simpler?argv_array_pushf(&cmd, "--batch-size=%"PRIuMAX, (uintmax_t)get_auto_pack_size());No, it was me who was missing an obvious and better alternative.
Today I learned about arv_array_push. Thanks!
-Stolee
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
In reply to Junio C HamanoQuoted 2 lines · Junio C Hamano
It might be too late to ask this now, but how does the quality of the resulting combined pack ensured, wrt locality and deltification?
There are two questions here, really.
The first is: given the set of objects to pack, are we packing them as efficiently as possible?
Since e11d86de139 (midx: teach "git multi-pack-index repack" honor "git repack" configurations, 2020-05-10), the 'repack' subcommand honors the configured recommendations for deltas. This includes:
(requires updating the arguments to pack-objects)
* repack.useDeltaBaseOffset
* repack.useDeltaIslands
(automatically respected by pack-objects)
* repack.packKeptsObjects
* pack.threads
* pack.depth
* pack.window
* pack.windowMemory
* pack.deltaCacheSize
* pack.deltaCacheLimit
All of these config settings allow the user to specify how hard to try for delta compression. If they know something about their data or their tolerance for extra CPU time during pack-objects, then they can get better deltas by changing these values.
The second question is "how well do the deltas compress when only packing incrementally versus packing the entire repo?"
One important way to consider these things is how the pack- files are created. If we expect most pack-files coming from 'git fetch' calls, then there are some interesting patterns that arise.
I started measuring by creating a local clone of the Linux kernel repo starting at v5.0 and then fetching an increment of ten commits from the first-parent history of later tags. Each fetch created a pack-file of ~300 MB relative to the base pack-file of ~1.6 GB. Collecting ten of these in a row leads to almost 2 GB of "fetched" packs.
However, keep in mind that we didn't fetch 2 GB of data "across the wire" but instead expanded the thin pack into a full pack by copying the base objects. After running the incremental-repack step, that ~2 GB of data re-compresses back down to one pack-file of size ~300 MB.
_Why_ did 10 pack-files all around 300 MB get repacked at once? It's because there were duplicate objects across those pack-files! Recall that the multi-pack-index repack computes batch sizes by computing an "estimated pack size" by counting how many objects in that pack-file are referenced by the multi-pack-index, then computing
expected size = actual size * num objects
/ num referenced objects
In this case, the "base" objects that are copied between the fetches already exist in these smaller pack-files. Thus, when the batch-size is ~300 MB it still repacks all 10 "small" packs into a new pack that is still ~300 MB.
Now, this is still a little wasteful. That second pack has a significant "extra space" cost. However, it came at a bonus of writing much less data.
Perhaps the Linux kernel repository is just too small to care about this version of maintenance? In such a case, I can work to introduce a 'full-repack' task that is more aggressive with repacking all pack-files. This could use the multi-pack-index repack with --batch-size=0 to still benefit from the repack/expire trick for safe concurrency.
Other ideas are to try repacking in other ways, such as by object type, to maximize easy wins. For example, perhaps we repack all of the commits and trees every time, but leave the blobs to be repacked when we are ready to spend time on removing deltas?
I think the incremental-repack has value, and perhaps it is isolated to super-huge repositories. That can be controlled by limiting its use to those when an expert user configures Git to use it.
I remain open to recommendations from others with more experience with delta compression to recommend alternatives.
tl,dr: the incremental-repack isn't the most space-efficient thing we can do, and that's by design.
Thanks,
-Stolee
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 2 lines · Derrick Stolee
tl,dr: the incremental-repack isn't the most space-efficient thing we can do, and that's by design.
I am not very surprised by the fact that many packfiles that were obtained by thin pack transfer have many duplicate objects (due to having to include the delta bases), and it is natural to expect that deduplication would save many bytes. It's not all that interesting.
I am more interested in making sure that we can assure that in the combined single pack, (1) objects are ordered for good locality of access and (2) objects are getting good delta compression.
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
In reply to Derrick Stolee via GitGitGadgetbuiltin/gc.c static int multi_pack_index_expire(void) · line 1024
Quoted 9 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +1
-0@@ -1021,19 +1021,65 @@ static int multi_pack_index_expire(void)return result; } +#define TWO_GIGABYTES (2147483647)
[jonathan tan] This would be easier to understand if it was expressed with bitshift, e.g. 1 << 31
builtin/gc.c static int multi_pack_index_expire(void) · line 1025
Quoted 1 line
builtin/gc.c +1
-0+#define UNSET_BATCH_SIZE ((unsigned long)-1)
[jonathan tan] This looks like it's never used... and vulnerable to cross-platform size changes because it's referring to an implicitly sized int, and could behave differently if it was put into a larger size, e.g. you wouldn't get 0xFFFF.. if you assigned this into a long long.
builtin/gc.c static int multi_pack_index_expire(void) · line 1056
Quoted 9 lines
builtin/gc.c +9
-0+for (p =get_all_packs (r); p; p = p->next) { +if (p->pack_size > max_size) { + second_largest_size = max_size; + max_size = p->pack_size; + }else if (p->pack_size > second_largest_size) + second_largest_size = p->pack_size; + } + + result_size = second_largest_size +1 ;
[jonathan tan] What happens when there's only one packfile, and when there are two packfiles? Can we write tests to illustrate the behavior? The edge case here (result_size=1) is hard to understand by reading the code.
builtin/gc.c static int multi_pack_index_expire(void) · line 1036
Quoted 2 lines
builtin/gc.c +2
-0+ +/* But limit ourselves to a batch size of 2g */
[emily shaffer] nit: can you please capitalize G, GB, whatever :)
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 9 lines · Emily Shaffer
Quoted 8 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +0
-0@@ -1021,19 +1021,65 @@ static int multi_pack_index_expire(void)return result; }
Quoted 4 lines
builtin/gc.c static int multi_pack_index_expire(void) · line 1024
Quoted 1 line
builtin/gc.c +1
-0+#define TWO_GIGABYTES (2147483647)[jonathan tan] This would be easier to understand if it was expressed with bitshift, e.g. 1 << 31
This is actually ((1 << 31) - 1) because "unsigned long" is 32-bits on Windows. But it's better to not use magic numbers and instead use operations like this.
Quoted 6 lines
builtin/gc.c static int multi_pack_index_expire(void) · line 1025
Quoted 1 line
builtin/gc.c +1
-0+#define UNSET_BATCH_SIZE ((unsigned long)-1)[jonathan tan] This looks like it's never used... and vulnerable to cross-platform size changes because it's referring to an implicitly sized int, and could behave differently if it was put into a larger size, e.g. you wouldn't get
0xFFFF.. if you assigned this into a long long.
Thanks for catching this cruft.
Quoted 14 lines
builtin/gc.c static int multi_pack_index_expire(void) · line 1056
Quoted 9 lines
builtin/gc.c +9
-0+for (p =get_all_packs (r); p; p = p->next) { +if (p->pack_size > max_size) { + second_largest_size = max_size; + max_size = p->pack_size; + }else if (p->pack_size > second_largest_size) + second_largest_size = p->pack_size; + } + + result_size = second_largest_size +1 ;[jonathan tan] What happens when there's only one packfile, and when there are two packfiles? Can we write tests to illustrate the behavior? The edge case here (result_size=1) is hard to understand by reading the code.
Quoted 3 lines
builtin/gc.c static int multi_pack_index_expire(void) · line 1036
Quoted 2 lines
builtin/gc.c +2
-0+ +/* But limit ourselves to a batch size of 2g */ [emily shaffer] nit: can you please capitalize G, GB, whatever :)
I suppose I could (get_unit_factor() performs case-insensitive matches on the size suffixes), except that would be inconsistent with the following error message in parse-options.c:
return error(_("%s expects a non-negative integer value"
" with an optional k/m/g suffix"),
or these options in Documentation/git-pack-objects.txt:
--window-memory=<n>::
This option provides an additional limit on top of --window; the window size will dynamically scale down so as to not take up more than '<n>' bytes in memory. This is useful in repositories with a mix of large and small objects to not run out of memory with a large window, but still be able to take advantage of the large window for the smaller objects. The size can be suffixed with "k", "m", or "g". --window-memory=0 makes memory usage unlimited. The default is taken from the pack.windowMemory configuration variable.
--max-pack-size=<n>::
In unusual scenarios, you may not be able to create files larger than a certain size on your filesystem, and this option can be used to tell the command to split the output packfile into multiple independent packfiles, each not larger than the given size. The size can be suffixed with
"k", "m", or "g". The minimum size allowed is limited to 1 MiB. This option
prevents the creation of a bitmap index.
The default is unlimited, unless the config variable pack.packSizeLimit is set.
Perhaps that's not really a good reason, but upper vs lower case seems to be arbitrary. Any tie breaker seems valid.
Thanks,
-Stolee
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 10 lines · Derrick Stolee
Quoted 9 lines · Emily Shaffer
Quoted 8 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +0
-0@@ -1021,19 +1021,65 @@ static int multi_pack_index_expire(void)return result; }
Quoted 8 lines
Quoted 4 lines
builtin/gc.c static int multi_pack_index_expire(void) · line 1024
Quoted 1 line
builtin/gc.c +1
-0+#define TWO_GIGABYTES (2147483647)[jonathan tan] This would be easier to understand if it was expressed with bitshift, e.g. 1 << 31
This is actually ((1 << 31) - 1) because "unsigned long" is 32-bits on Windows. But it's better to not use magic numbers and instead use operations like this.
Nevermind. This breaks the build on 32-bit machines (even adding a few "L" characters). I'll replace my magic decimal number with a magic hexadecimal number.
Thanks,
-Stolee
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 7 lines · Derrick Stolee
Quoted 3 lines
This is actually ((1 << 31) - 1) because "unsigned long" is 32-bits on Windows. But it's better to not use magic numbers and instead use operations like this.
Nevermind. This breaks the build on 32-bit machines (even adding a few "L" characters). I'll replace my magic decimal number with a magic hexadecimal number.
You would need something along these lines:
#define WHATEVER ((long)((1UL << 31) - 1))
i.e., make the constant 1 be "unsigned long" so that the shift is well defined, then subtract 1, then convert back to signed long.
I say "something along" because there are several more ways to write it. I'm not really a big fan of most of them, and 0x7FFFFFFF (or in lowercase) is my own preference here too.
Chris
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
In reply to Derrick StoleeQuoted 16 lines · Derrick Stolee
Quoted 15 lines · Derrick Stolee
Quoted 13 lines · Emily Shaffer
builtin/gc.c static int multi_pack_index_expire(void) · line 1024
Quoted 9 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +1
-0@@ -1021,19 +1021,65 @@ static int multi_pack_index_expire(void)return result; } +#define TWO_GIGABYTES (2147483647)[jonathan tan] This would be easier to understand if it was expressed with bitshift, e.g. 1 << 31
Quoted 7 lines
Quoted 3 lines
This is actually ((1 << 31) - 1) because "unsigned long" is 32-bits on Windows. But it's better to not use magic numbers and instead use operations like this.
Nevermind. This breaks the build on 32-bit machines (even adding a few "L" characters). I'll replace my magic decimal number with a magic hexadecimal number.
Would it be better if we use this C99 feature instead:
#define TWO_GIGABYTES INT32_MAX
But it seems like it's available everywhere, except (maybe) Windows. (judging from UINT32_MAX's usage)
Signature · 1 line
Danh
Re: [PATCH v2 11/18] maintenance: auto-size incremental-repack batch
Quoted 25 lines · Đoàn Trần Công Danh
Quoted 16 lines · Derrick Stolee
Quoted 15 lines · Derrick Stolee
Quoted 13 lines · Emily Shaffer
builtin/gc.c static int multi_pack_index_expire(void) · line 1024
Quoted 9 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +1
-0@@ -1021,19 +1021,65 @@ static int multi_pack_index_expire(void)return result; } +#define TWO_GIGABYTES (2147483647)[jonathan tan] This would be easier to understand if it was expressed with bitshift, e.g. 1 << 31
Quoted 7 lines
Quoted 3 lines
This is actually ((1 << 31) - 1) because "unsigned long" is 32-bits on Windows. But it's better to not use magic numbers and instead use operations like this.
Nevermind. This breaks the build on 32-bit machines (even adding a few "L" characters). I'll replace my magic decimal number with a magic hexadecimal number.
Quoted 6 lines
Would it be better if we use this C99 feature instead:
#define TWO_GIGABYTES INT32_MAXBut it seems like it's available everywhere, except (maybe) Windows. (judging from UINT32_MAX's usage)
Thanks for the recommendation. This appears to work in all of the CI builds, including Windows. I'll use that constant in my next version. Perhaps we will discover a platform that doesn't have it by including it.
-Stolee
PATCH v2 12/18 maintenance: create maintenance.<task>.enabled config
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Currently, a normal run of "git maintenance run" will only run the 'gc' task, as it is the only one enabled. This is mostly for backwards- compatible reasons since "git maintenance run --auto" commands replaced previous "git gc --auto" commands after some Git processes. Users could manually run specific maintenance tasks by calling "git maintenance run --task=<task>" directly.
Allow users to customize which steps are run automatically using config. The 'maintenance.<task>.enabled' option then can turn on these other tasks (or turn off the 'gc' task).
--- Documentation/config.txt | 2 ++ Documentation/config/maintenance.txt | 4 ++++ Documentation/git-maintenance.txt | 6 +++++- builtin/gc.c | 13 +++++++++++++ t/t7900-maintenance.sh | 12 ++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 Documentation/config/maintenance.txt
Documentation/config.txt +2 -0
@@ -396,6 +396,8 @@ include::config/mailinfo.txt[] include::config/mailmap.txt[] +include::config/maintenance.txt[] + include::config/man.txt[] include::config/merge.txt[]
Documentation/config/maintenance.txt new file +4 -0
@@ -0,0 +1,4 @@ +maintenance.<task>.enabled:: + This boolean config option controls whether the maintenance task + with name `<task>` is run when no `--task` option is specified. + By default, only `maintenance.gc.enabled` is true.
Documentation/git-maintenance.txt +5 -1
@@ -30,7 +30,11 @@ SUBCOMMANDS ----------- run:: - Run one or more maintenance tasks. + Run one or more maintenance tasks. If one or more `--task` options + are specified, then those tasks are run in that order. Otherwise, + the tasks are determined by which `maintenance.<task>.enabled` + config options are true. By default, only `maintenance.gc.enabled` + is true. TASKS -----
builtin/gc.c +13 -0
@@ -1185,6 +1185,7 @@ static int maintenance_run(void)static void initialize_tasks (void ) {int i; +struct strbuf config_name =STRBUF_INIT ; num_tasks =0 ;for (i =0 ; i <MAX_NUM_TASKS ; i++)
@@ -1210,6 +1211,18 @@ static void initialize_tasks(void) tasks[num_tasks]->name ="commit-graph" ; tasks[num_tasks]->fn = maintenance_task_commit_graph; num_tasks++; + +for (i =0 ; i < num_tasks; i++) { +int config_value; + +strbuf_setlen (&config_name,0 ); +strbuf_addf (&config_name,"maintenance.%s.enabled" , tasks[i]->name); + +if (!git_config_get_bool (config_name.buf, &config_value)) + tasks[i]->enabled = config_value; + } + +strbuf_release (&config_name); } static int task_option_parse(const struct option *opt,
t/t7900-maintenance.sh +12 -0
@@ -21,6 +21,18 @@ test_expect_success 'run [--auto|--quiet]' ' grep ",\"gc\",\"--quiet\"" run-quiet.txt ' +test_expect_success 'maintenance.<task>.enabled' ' + git config maintenance.gc.enabled false && + git config maintenance.commit-graph.enabled true && + git config maintenance.loose-objects.enabled true && + GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run && + ! grep ",\"fetch\"" run-config.txt && + ! grep ",\"gc\"" run-config.txt && + ! grep ",\"multi-pack-index\"" run-config.txt && + grep ",\"commit-graph\"" run-config.txt && + grep ",\"prune-packed\"" run-config.txt +' + test_expect_success 'run --task=<task>' ' GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" git maintenance run --task=commit-graph && GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" git maintenance run --task=gc &&
Signature · 1 line
gitgitgadget
PATCH v2 13/18 maintenance: use pointers to check --auto
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The 'git maintenance run' command has an '--auto' option. This is used by other Git commands such as 'git commit' or 'git fetch' to check if maintenance should be run after adding data to the repository.
Previously, this --auto option was only used to add the argument to the 'git gc' command as part of the 'gc' task. We will be expanding the other tasks to perform a check to see if they should do work as part of the --auto flag, when they are enabled by config.
First, update the 'gc' task to perform the auto check inside the maintenance process. This prevents running an extra 'git gc --auto' command when not needed. It also shows a model for other tasks.
Second, use the 'auto_condition' function pointer as a signal for whether we enable the maintenance task under '--auto'. For instance, we do not want to enable the 'fetch' task in '--auto' mode, so that function pointer will remain NULL.
Now that we are not automatically calling 'git gc', a test in t5514-fetch-multiple.sh must be changed to watch for 'git maintenance' instead.
We continue to pass the '--auto' option to the 'git gc' command when necessary, because of the gc.autoDetach config option changes behavior. Likely, we will want to absorb the daemonizing behavior implied by gc.autoDetach as a maintenance.autoDetach config option.
--- builtin/gc.c | 15 +++++++++++++++ t/t5514-fetch-multiple.sh | 2 +- t/t7900-maintenance.sh | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-)
builtin/gc.c +15 -0
@@ -1121,9 +1121,17 @@ static int maintenance_task_incremental_repack(void)typedef int maintenance_task_fn (void ); +/* +* An auto condition function returns 1 if the task should run +* and 0 if the task should NOT run. See needs_to_gc() for an +* example. +*/ +typedef int maintenance_auto_fn (void ); +struct maintenance_task {const char *name;maintenance_task_fn *fn; +maintenance_auto_fn *auto_condition;int task_order;unsigned enabled:1 , selected:1 ;
@@ -1175,6 +1183,11 @@ static int maintenance_run(void)if (!opts.tasks_selected && !tasks[i]->enabled)continue ; +if (opts.auto_flag && + (!tasks[i]->auto_condition || + !tasks[i]->auto_condition ())) +continue ; + result = tasks[i]->fn (); }
@@ -1205,6 +1218,7 @@ static void initialize_tasks(void) tasks[num_tasks]->name ="gc" ; tasks[num_tasks]->fn = maintenance_task_gc; + tasks[num_tasks]->auto_condition = need_to_gc; tasks[num_tasks]->enabled =1 ; num_tasks++;
@@ -1283,6 +1297,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix) builtin_maintenance_options); opts.quiet = !isatty (2 ); +gc_config ();initialize_tasks (); argc = parse_options(argc, argv, prefix,
t/t5514-fetch-multiple.sh +1 -1
@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' ' GIT_TRACE=1 git fetch --multiple one two 2>trace && git branch -r > output && test_cmp ../expect output && - grep "built-in: git gc" trace >gc && + grep "built-in: git maintenance" trace >gc && test_line_count = 1 gc ) '
t/t7900-maintenance.sh +1 -1
@@ -17,7 +17,7 @@ test_expect_success 'run [--auto|--quiet]' ' GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto && GIT_TRACE2_EVENT="$(pwd)/run-quiet.txt" git maintenance run --quiet && grep ",\"gc\"]" run-no-auto.txt && - grep ",\"gc\",\"--auto\"" run-auto.txt && + ! grep ",\"gc\",\"--auto\"" run-auto.txt && grep ",\"gc\",\"--quiet\"" run-quiet.txt '
Signature · 1 line
gitgitgadget
PATCH v2 14/18 maintenance: add auto condition for commit-graph task
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Instead of writing a new commit-graph in every 'git maintenance run --auto' process (when maintenance.commit-graph.enalbed is configured to be true), only write when there are "enough" commits not in a commit-graph file.
This count is controlled by the maintenance.commit-graph.auto config option.
To compute the count, use a depth-first search starting at each ref, and leaving markers using the PARENT1 flag. If this count reaches the limit, then terminate early and start the task. Otherwise, this operation will peel every ref and parse the commit it points to. If these are all in the commit-graph, then this is typically a very fast operation. Users with many refs might feel a slow-down, and hence could consider updating their limit to be very small. A negative value will force the step to run every time.
--- Documentation/config/maintenance.txt | 10 ++++ builtin/gc.c | 76 ++++++++++++++++++++++++++++ object.h | 1 + 3 files changed, 87 insertions(+)
Documentation/config/maintenance.txt +10 -0
@@ -2,3 +2,13 @@ maintenance.<task>.enabled:: This boolean config option controls whether the maintenance task with name `<task>` is run when no `--task` option is specified. By default, only `maintenance.gc.enabled` is true. + +maintenance.commit-graph.auto:: + This integer config option controls how often the `commit-graph` task + should be run as part of `git maintenance run --auto`. If zero, then + the `commit-graph` task will not run with the `--auto` option. A + negative value will force the task to run every time. Otherwise, a + positive value implies the command should run when the number of + reachable commits that are not in the commit-graph file is at least + the value of `maintenance.commit-graph.auto`. The default value is + 100.
builtin/gc.c +76 -0
94 lines not on this page · raw message
object.h +1 -0
@@ -73,6 +73,7 @@ struct object_array {* list-objects-filter.c: 21 * builtin/fsck.c: 0--3 * builtin/index-pack.c: 2021 +* builtin/maintenance.c: 16 * builtin/pack-objects.c: 20 * builtin/reflog.c: 10--12 * builtin/show-branch.c: 0-------------------------------------------26
Signature · 1 line
gitgitgadget
PATCH v2 15/18 maintenance: create auto condition for loose-objects
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The loose-objects task deletes loose objects that already exist in a pack-file, then place the remaining loose objects into a new pack-file. If this step runs all the time, then we risk creating pack-files with very few objects with every 'git commit' process. To prevent overwhelming the packs directory with small pack-files, place a minimum number of objects to justify the task.
The 'maintenance.loose-objects.auto' config option specifies a minimum number of loose objects to justify the task to run under the '--auto' option. This defaults to 100 loose objects. Setting the value to zero will prevent the step from running under '--auto' while a negative value will force it to run every time.
--- Documentation/config/maintenance.txt | 9 +++++++++ builtin/gc.c | 30 ++++++++++++++++++++++++++++ t/t7900-maintenance.sh | 25 +++++++++++++++++++++++ 3 files changed, 64 insertions(+)
Documentation/config/maintenance.txt +9 -0
@@ -12,3 +12,12 @@ maintenance.commit-graph.auto:: reachable commits that are not in the commit-graph file is at least the value of `maintenance.commit-graph.auto`. The default value is 100. + +maintenance.loose-objects.auto:: + This integer config option controls how often the `loose-objects` task + should be run as part of `git maintenance run --auto`. If zero, then + the `loose-objects` task will not run with the `--auto` option. A + negative value will force the task to run every time. Otherwise, a + positive value implies the command should run when the number of + loose objects is at least the value of `maintenance.loose-objects.auto`. + The default value is 100.
builtin/gc.c +30 -0
@@ -951,6 +951,35 @@ struct write_loose_object_data {int batch_size; }; +static int loose_object_auto_limit =100 ; + +static int loose_object_count (const struct object_id *oid, +const char *path, +void *data) +{ +int *count = (int *)data; +if (++(*count) >= loose_object_auto_limit) +return 1 ; +return 0 ; +} + +static int loose_object_auto_condition (void ) +{ +int count =0 ; + +git_config_get_int ("maintenance.loose-objects.auto" , + &loose_object_auto_limit); + +if (!loose_object_auto_limit) +return 0 ; +if (loose_object_auto_limit <0 ) +return 1 ; + +return for_each_loose_file_in_objdir (the_repository->objects->odb->path, + loose_object_count, +NULL ,NULL , &count); +} +static int loose_object_exists (const struct object_id *oid,const char *path,void *data)
@@ -1285,6 +1314,7 @@ static void initialize_tasks(void) tasks[num_tasks]->name ="loose-objects" ; tasks[num_tasks]->fn = maintenance_task_loose_objects; + tasks[num_tasks]->auto_condition = loose_object_auto_condition; num_tasks++; tasks[num_tasks]->name ="incremental-repack" ;
t/t7900-maintenance.sh +25 -0
@@ -115,6 +115,31 @@ test_expect_success 'loose-objects task' ' test_cmp packs-between packs-after ' +test_expect_success 'maintenance.loose-objects.auto' ' + git repack -adk && + GIT_TRACE2_EVENT="$(pwd)/trace-lo1.txt" \ + git -c maintenance.loose-objects.auto=1 maintenance \ + run --auto --task=loose-objects && + ! grep "\"prune-packed\"" trace-lo1.txt && + for i in 1 2 + do + printf data-A-$i | git hash-object -t blob --stdin -w && + GIT_TRACE2_EVENT="$(pwd)/trace-loA-$i" \ + git -c maintenance.loose-objects.auto=2 \ + maintenance run --auto --task=loose-objects && + ! grep "\"prune-packed\"" trace-loA-$i && + printf data-B-$i | git hash-object -t blob --stdin -w && + GIT_TRACE2_EVENT="$(pwd)/trace-loB-$i" \ + git -c maintenance.loose-objects.auto=2 \ + maintenance run --auto --task=loose-objects && + grep "\"prune-packed\"" trace-loB-$i && + GIT_TRACE2_EVENT="$(pwd)/trace-loC-$i" \ + git -c maintenance.loose-objects.auto=2 \ + maintenance run --auto --task=loose-objects && + grep "\"prune-packed\"" trace-loC-$i || return 1 + done +' + test_expect_success 'incremental-repack task' ' packDir=.git/objects/pack && for i in $(test_seq 1 5)
Signature · 1 line
gitgitgadget
PATCH v2 16/18 maintenance: add incremental-repack auto condition
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The incremental-repack task updates the multi-pack-index by deleting pack- files that have been replaced with new packs, then repacking a batch of small pack-files into a larger pack-file. This incremental repack is faster than rewriting all object data, but is slower than some other maintenance activities.
The 'maintenance.incremental-repack.auto' config option specifies how many pack-files should exist outside of the multi-pack-index before running the step. These pack-files could be created by 'git fetch' commands or by the loose-objects task. The default value is 10.
Setting the option to zero disables the task with the '--auto' option, and a negative value makes the task run every time.
--- Documentation/config/maintenance.txt | 9 ++++++++ builtin/gc.c | 31 ++++++++++++++++++++++++++++ t/t7900-maintenance.sh | 30 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+)
Documentation/config/maintenance.txt +9 -0
@@ -21,3 +21,12 @@ maintenance.loose-objects.auto:: positive value implies the command should run when the number of loose objects is at least the value of `maintenance.loose-objects.auto`. The default value is 100. + +maintenance.incremental-repack.auto:: + This integer config option controls how often the `incremental-repack` + task should be run as part of `git maintenance run --auto`. If zero, + then the `incremental-repack` task will not run with the `--auto` + option. A negative value will force the task to run every time. + Otherwise, a positive value implies the command should run when the + number of pack-files not in the multi-pack-index is at least the value + of `maintenance.incremental-repack.auto`. The default value is 10.
builtin/gc.c +31 -0
@@ -31,6 +31,7 @@#include "remote.h" #include "midx.h" #include "refs.h" +#include "object-store.h" #define FAILED_RUN "failed to run %s"
@@ -1063,6 +1064,35 @@ static int maintenance_task_loose_objects(void)return prune_packed () ||pack_loose (); } +static int incremental_repack_auto_condition (void ) +{ +struct packed_git *p; +int enabled; +int incremental_repack_auto_limit =10 ; +int count =0 ; + +if (git_config_get_bool ("core.multiPackIndex" , &enabled) || + !enabled) +return 0 ; + +git_config_get_int ("maintenance.incremental-repack.auto" , + &incremental_repack_auto_limit); + +if (!incremental_repack_auto_limit) +return 0 ; +if (incremental_repack_auto_limit <0 ) +return 1 ; + +for (p =get_packed_git (the_repository); + count < incremental_repack_auto_limit && p; + p = p->next) { +if (!p->multi_pack_index) + count++; + } + +return count >= incremental_repack_auto_limit; +} +static int multi_pack_index_write (void ) {int result;
@@ -1319,6 +1349,7 @@ static void initialize_tasks(void) tasks[num_tasks]->name ="incremental-repack" ; tasks[num_tasks]->fn = maintenance_task_incremental_repack; + tasks[num_tasks]->auto_condition = incremental_repack_auto_condition; num_tasks++; tasks[num_tasks]->name ="gc" ;
t/t7900-maintenance.sh +30 -0
@@ -178,4 +178,34 @@ test_expect_success 'incremental-repack task' ' test_line_count = 2 packs-after ' +test_expect_success 'maintenance.incremental-repack.auto' ' + git repack -adk && + git config core.multiPackIndex true && + git multi-pack-index write && + GIT_TRACE2_EVENT=1 git -c maintenance.incremental-repack.auto=1 \ + maintenance run --auto --task=incremental-repack >out && + ! grep "\"multi-pack-index\"" out && + for i in 1 2 + do + test_commit A-$i && + git pack-objects --revs .git/objects/pack/pack <<-\EOF && + HEAD + ^HEAD~1 + EOF + GIT_TRACE2_EVENT=$(pwd)/trace-A-$i git \ + -c maintenance.incremental-repack.auto=2 \ + maintenance run --auto --task=incremental-repack && + ! grep "\"multi-pack-index\"" trace-A-$i && + test_commit B-$i && + git pack-objects --revs .git/objects/pack/pack <<-\EOF && + HEAD + ^HEAD~1 + EOF + GIT_TRACE2_EVENT=$(pwd)/trace-B-$i git \ + -c maintenance.incremental-repack.auto=2 \ + maintenance run --auto --task=incremental-repack >out && + grep "\"multi-pack-index\"" trace-B-$i >/dev/null || return 1 + done +' + test_done
Signature · 1 line
gitgitgadget
PATCH v2 17/18 midx: use start_delayed_progress()
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Now that the multi-pack-index may be written as part of auto maintenance at the end of a command, reduce the progress output when the operations are quick. Use start_delayed_progress() instead of start_progress().
Update t5319-multi-pack-index.sh to use GIT_PROGRESS_DELAY=0 now that the progress indicators are conditional.
--- midx.c | 10 +++++----- t/t5319-multi-pack-index.sh | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-)
midx.c +5 -5
@@ -837,7 +837,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * packs.pack_paths_checked =0 ;if (flags &MIDX_PROGRESS ) - packs.progress =start_progress (_ ("Adding packfiles to multi-pack-index" ),0 ); + packs.progress =start_delayed_progress (_ ("Adding packfiles to multi-pack-index" ),0 );else packs.progress =NULL ;
@@ -974,7 +974,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * }if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Writing chunks to multi-pack-index" ), + progress =start_delayed_progress (_ ("Writing chunks to multi-pack-index" ), num_chunks);for (i =0 ; i < num_chunks; i++) {if (written != chunk_offsets[i])
@@ -1109,7 +1109,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flagreturn 0 ;if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Looking for referenced packfiles" ), + progress =start_delayed_progress (_ ("Looking for referenced packfiles" ), m->num_packs);for (i =0 ; i < m->num_packs; i++) {if (prepare_midx_pack (r, m, i))
@@ -1230,7 +1230,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla count =xcalloc (m->num_packs,sizeof (uint32_t ));if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Counting referenced objects" ), + progress =start_delayed_progress (_ ("Counting referenced objects" ), m->num_objects);for (i =0 ; i < m->num_objects; i++) {int pack_int_id =nth_midxed_pack_int_id (m, i);
@@ -1240,7 +1240,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flastop_progress (&progress);if (flags &MIDX_PROGRESS ) - progress =start_progress (_ ("Finding and deleting unreferenced packfiles" ), + progress =start_delayed_progress (_ ("Finding and deleting unreferenced packfiles" ), m->num_packs);for (i =0 ; i < m->num_packs; i++) {char *pack_name;
t/t5319-multi-pack-index.sh +7 -7
@@ -172,12 +172,12 @@ test_expect_success 'write progress off for redirected stderr' ' ' test_expect_success 'write force progress on for stderr' ' - git multi-pack-index --object-dir=$objdir --progress write 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress write 2>err && test_file_not_empty err ' test_expect_success 'write with the --no-progress option' ' - git multi-pack-index --object-dir=$objdir --no-progress write 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress write 2>err && test_line_count = 0 err '
@@ -334,17 +334,17 @@ test_expect_success 'git-fsck incorrect offset' ' ' test_expect_success 'repack progress off for redirected stderr' ' - git multi-pack-index --object-dir=$objdir repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack 2>err && test_line_count = 0 err ' test_expect_success 'repack force progress on for stderr' ' - git multi-pack-index --object-dir=$objdir --progress repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress repack 2>err && test_file_not_empty err ' test_expect_success 'repack with the --no-progress option' ' - git multi-pack-index --object-dir=$objdir --no-progress repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress repack 2>err && test_line_count = 0 err '
@@ -488,7 +488,7 @@ test_expect_success 'expire progress off for redirected stderr' ' test_expect_success 'expire force progress on for stderr' ' ( cd dup && - git multi-pack-index --progress expire 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --progress expire 2>err && test_file_not_empty err ) '
@@ -496,7 +496,7 @@ test_expect_success 'expire force progress on for stderr' ' test_expect_success 'expire with the --no-progress option' ' ( cd dup && - git multi-pack-index --no-progress expire 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --no-progress expire 2>err && test_line_count = 0 err ) '
Signature · 1 line
gitgitgadget
PATCH v2 18/18 maintenance: add trace2 regions for task execution
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
--- builtin/gc.c | 2 ++ 1 file changed, 2 insertions(+)
builtin/gc.c +2 -0
@@ -1322,7 +1322,9 @@ static int maintenance_run(void) !tasks[i]->auto_condition()))continue ; +trace2_region_enter ("maintenance" , tasks[i]->name, r); result = tasks[i]->fn (); +trace2_region_leave ("maintenance" , tasks[i]->name, r); }rollback_lock_file (&lk);
Signature · 1 line
gitgitgadget
Quoted 28 lines · Derrick Stolee via GitGitGadget
UPDATES in V2
=============I'm sending this between v2.28.0-rc2 adn v2.28.0 as the release things have become a bit quiet.
* The biggest disruption to the range-diff is that I removed the premature use of struct repository *r and instead continue to rely on
the_repository. This means several patches were dropped that did prep work inbuiltin/gc.c.* I dropped the task hashmap and opted for a linear scan. This task list will always be too small to justify the extra complication of the hashmap.
* struct
maintenance_optsis properly static now.* Some tasks are renamed: fetch -> prefetch, pack-files -> incremental-repack.
* With the rename, the prefetch task uses refs/prefetch/ instead of refs/hidden/.
Quoted 5 lines
* A trace2 region around the task executions are added.
Thanks, -Stolee
FYI: We covered this series in review club (me, Jonathan Tan, Jonathan Nieder) so I'll send some mails with comments from all three of us.
We noticed reviewing that it looks like it's time to focus on the details because the big picture of this series looks good. Since we were able to review each patch independently, the approach you took to separate the commits seems effective.
One concern I (Emily) had was about the use of midx without necessarily checking the config, where users can opt out of using it. What are the consequences of running maintenance that creates a multipack index without the config turned on for the rest of Git?
- Emily