Maintenance: adapt custom refspecs
Subsystems (2)
cover Maintenance: adapt custom refspecs
Tom Saeger rightly pointed out [1] that the prefetch task ignores custom refspecs. This can lead to downloading more data than requested, and it doesn't even help the future foreground fetches that use that custom refspec.
[1]
https://lore.kernel.org/git/20210401184914.qmr7jhjbhp2mt3h6@dhcp-10-154-148-175.vpn.oracle.com/
This series fixes this problem by carefully replacing the start of each refspec's destination with "refs/prefetch/". If the destination already starts with "refs/", then that is replaced. Otherwise "refs/prefetch/" is just prepended.
In order to accomplish this safely, a new refspec_item_format() method is created and tested.
Patch 1 is just a preparation patch that makes the code simpler (and in hindsight it should have been written this way from the start).
Patch 2 is a simplification of test_subcommand that removes the need for escaping glob characters. Thanks, Eric Sunshine, for the tip of why my tests were failing on FreeBSD.
Patches 3-4 add refspec_item_format().
Patch 5 finally modifies the logic in the prefetch task to translate these refspecs.
Thanks, -Stolee
Derrick Stolee (5):
maintenance: simplify prefetch logic
test-lib: use exact match for test_subcommand
refspec: output a refspec item
test-tool: test refspec input/output
maintenance: allow custom refspecs during prefetch
Documentation/git-maintenance.txt | 3 +- Makefile | 1 + builtin/gc.c | 63 +++++++++++++++++++------------ refspec.c | 25 ++++++++++++ refspec.h | 5 +++ t/helper/test-refspec.c | 39 +++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t5511-refspec.sh | 41 ++++++++++++++++++++ t/t7900-maintenance.sh | 43 ++++++++++++++++++--- t/test-lib-functions.sh | 4 +- 11 files changed, 192 insertions(+), 34 deletions(-) create mode 100644 t/helper/test-refspec.c
base-commit: 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-924%2Fderrickstolee%2Fmaintenance%2Frefspec-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-924/derrickstolee/maintenance/refspec-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/924
Signature · 1 line
gitgitgadget
PATCH 1/5 maintenance: simplify prefetch logic
From: Derrick Stolee <[email protected]>
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also becuase it sets up an upcoming change to use the remote's refspec. This data is accessible from the 'struct remote' data that is now accessible in fetch_remote().
--- builtin/gc.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-)
builtin/gc.c +8 -25
@@ -873,55 +873,38 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)return 0 ; } -static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" ,"--refmap=" ,NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote); +strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name);return !!run_command (&child); } -static int append_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 (struct maintenance_run_opts *opts) { -int result =0 ; -struct string_list_item *item; -struct string_list remotes =STRING_LIST_INIT_DUP ; -git_config_set_multivar_gently ("log.excludedecoration" ,"refs/prefetch/" ,"refs/prefetch/" ,CONFIG_FLAGS_FIXED_VALUE |CONFIG_FLAGS_MULTI_REPLACE ); -if (for_each_remote (append_remote, &remotes)) { -error (_ ("failed to fill remotes" )); - result =1 ; - goto cleanup; +if (for_each_remote (fetch_remote, opts)) { +error (_ ("failed to prefetch remotes" )); +return 1 ; } -for_each_string_list_item (item, &remotes) - result |=fetch_remote (item->string, opts); - -cleanup: -string_list_clear (&remotes,0 ); -return result; +return 0 ; }static int maintenance_task_gc (struct maintenance_run_opts *opts)
Signature · 1 line
gitgitgadget
Re: [PATCH 1/5] maintenance: simplify prefetch logic
Quoted 6 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also becuase it sets up an upcoming change to use the remote's refspec. This
*NIT* because
Quoted 79 lines
data is accessible from the 'struct remote' data that is now accessible in
fetch_remote().Signed-off-by Derrick Stolee <[email protected]>--- builtin/gc.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-)builtin/gc.c +8
-25@@ -873,55 +873,38 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)return 0 ; } -static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" ,"--refmap=" ,NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote); +strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name);return !!run_command (&child); } -static int append_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 (struct maintenance_run_opts *opts) { -int result =0 ; -struct string_list_item *item; -struct string_list remotes =STRING_LIST_INIT_DUP ; -git_config_set_multivar_gently ("log.excludedecoration" ,"refs/prefetch/" ,"refs/prefetch/" ,CONFIG_FLAGS_FIXED_VALUE |CONFIG_FLAGS_MULTI_REPLACE ); -if (for_each_remote (append_remote, &remotes)) { -error (_ ("failed to fill remotes" )); - result =1 ; - goto cleanup; +if (for_each_remote (fetch_remote, opts)) { +error (_ ("failed to prefetch remotes" )); +return 1 ; } -for_each_string_list_item (item, &remotes) - result |=fetch_remote (item->string, opts); - -cleanup: -string_list_clear (&remotes,0 ); -return result; +return 0 ; }static int maintenance_task_gc (struct maintenance_run_opts *opts)Signature · 1 line
gitgitgadget
PATCH 2/5 test-lib: use exact match for test_subcommand
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The use of 'grep' inside test_subcommand uses general patterns, leading to sometimes needing escape characters to avoid incorrect matches. Further, some platforms interpret different glob characters differently.
Use 'grep -F' to use an exact match. This requires removing escape characters from existing callers. Luckily, this is only one test that expects refspecs as part of the subcommand.
--- t/t7900-maintenance.sh | 4 ++-- t/test-lib-functions.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-)
t/t7900-maintenance.sh +2 -2
@@ -142,8 +142,8 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt && test_path_is_missing .git/refs/remotes && git log prefetch/remote1/one && git log prefetch/remote2/two &&
t/test-lib-functions.sh +2 -2
@@ -1652,9 +1652,9 @@ test_subcommand () { if test -n "$negate" then - ! grep "\[$expr\]" + ! grep -F "[$expr]" else - grep "\[$expr\]" + grep -F "[$expr]" fi }
Signature · 1 line
gitgitgadget
Re: [PATCH 2/5] test-lib: use exact match for test_subcommand
Quoted 3 lines · Derrick Stolee via GitGitGadget
The use of 'grep' inside
test_subcommanduses general patterns, leading to sometimes needing escape characters to avoid incorrect matches. Further, some platforms interpret different glob characters differently.
These are regular expression metacharacters, not glob characters. A more general way to say this might be:
Furthermore, it can be difficult to know which characters need escaping since the actual regular expression language implemented by various greps differs between platforms; for instance, some may employ pure BRE, whereas others a mix of BRE & ERE.
Sidestep this difficulty by using grep -F...
Quoted 6 lines
Use 'grep -F' to use an exact match. This requires removing escape characters from existing callers. Luckily, this is only one test that expects refspecs as part of the subcommand.
Reported-by Eric Sunshine <[email protected]>Signed-off-by Derrick Stolee <[email protected]>
The Reported-by: feels a bit unusual in this context. Perhaps
Helped-by: would be more appropriate.
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 143
Quoted 6 lines
t/t7900-maintenance.sh +2
-2@@ -142,8 +142,8 @@ test_expect_success 'prefetch multiple remotes' ' - test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt &&
To be really robust and avoid accidental glob expansion (as unlikely as it is), you should quote any arguments which contain glob metacharacters such as "*" rather than supplying them bare like this.
Re: [PATCH 2/5] test-lib: use exact match for test_subcommand
Quoted 26 lines · Eric Sunshine
Quoted 3 lines · Derrick Stolee via GitGitGadget
The use of 'grep' inside
test_subcommanduses general patterns, leading to sometimes needing escape characters to avoid incorrect matches. Further, some platforms interpret different glob characters differently.These are regular expression metacharacters, not glob characters. A more general way to say this might be:
Furthermore, it can be difficult to know which characters need escaping since the actual regular expression language implemented by various
greps differs between platforms; for instance, some may employ pure BRE, whereas others a mix of BRE & ERE.Sidestep this difficulty by using
grep -F...Quoted 6 lines
Use 'grep -F' to use an exact match. This requires removing escape characters from existing callers. Luckily, this is only one test that expects refspecs as part of the subcommand.
Reported-by Eric Sunshine <[email protected]>Signed-off-by Derrick Stolee <[email protected]>The Reported-by: feels a bit unusual in this context. Perhaps
Helped-by: would be more appropriate.
Quoted 10 lines
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 143
Quoted 6 lines
t/t7900-maintenance.sh +2
-2@@ -142,8 +142,8 @@ test_expect_success 'prefetch multiple remotes' ' - test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt &&To be really robust and avoid accidental glob expansion (as unlikely as it is), you should quote any arguments which contain glob metacharacters such as "*" rather than supplying them bare like this.
Yup, just enclose the whole refspec inside dq-pair, like
test_subcommand git fetch remote2 $fetchargs \ "+refs/heads/*:refs/prefetch/remote2/*" <run-prefetch.txt &&
would be the easiest to read.
Thanks.
PATCH 3/5 refspec: output a refspec item
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Add a new method, refspec_item_format(), that takes a 'struct refspec_item' pointer as input and returns a string for how that refspec item should be written to Git's config or a subcommand, such as 'git fetch'.
There are several subtleties regarding special-case refspecs that can occur and are represented in t5511-refspec.sh. These cases will be explored in new tests in the following change. It requires adding a new test helper in order to test this format directly, so that is saved for a separate change to keep this one focused on the logic of the format method.
A future change will consume this method when translating refspecs in the 'prefetch' task of the 'git maintenance' builtin.
--- refspec.c | 25 +++++++++++++++++++++++++ refspec.h | 5 +++++ 2 files changed, 30 insertions(+)
refspec.c +25 -0
@@ -180,6 +180,31 @@ void refspec_item_clear(struct refspec_item *item) item->exact_sha1 =0 ; } +const char *refspec_item_format (const struct refspec_item *rsi) +{ +static struct strbuf buf =STRBUF_INIT ; + +strbuf_reset (&buf); + +if (rsi->matching) +return ":" ; + +if (rsi->negative) +strbuf_addch (&buf,'^' ); +else if (rsi->force) +strbuf_addch (&buf,'+' ); + +if (rsi->src) +strbuf_addstr (&buf, rsi->src); + +if (rsi->dst) { +strbuf_addch (&buf,':' ); +strbuf_addstr (&buf, rsi->dst); + } + +return buf.buf; +} +void refspec_init (struct refspec *rs,int fetch) {memset (rs,0 ,sizeof (*rs));
refspec.h +5 -0
@@ -56,6 +56,11 @@ int refspec_item_init(struct refspec_item *item, const char *refspec,void refspec_item_init_or_die (struct refspec_item *item,const char *refspec,int fetch);void refspec_item_clear (struct refspec_item *item); +/* +* Output a given refspec item to a string. +*/ +const char *refspec_item_format (const struct refspec_item *rsi); +void refspec_init (struct refspec *rs,int fetch);void refspec_append (struct refspec *rs,const char *refspec); __attribute__((format (printf,2,3)))
Signature · 1 line
gitgitgadget
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 28 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
Add a new method,
refspec_item_format(), that takes a 'structrefspec_item' pointer as input and returns a string for how that refspec item should be written to Git's config or a subcommand, such as 'git fetch'.There are several subtleties regarding special-case refspecs that can occur and are represented in
t5511-refspec.sh. These cases will be explored in new tests in the following change. It requires adding a new test helper in order to test this format directly, so that is saved for a separate change to keep this one focused on the logic of the format method.A future change will consume this method when translating refspecs in the 'prefetch' task of the 'git maintenance' builtin.
Signed-off-by Derrick Stolee <[email protected]>--- refspec.c | 25 +++++++++++++++++++++++++ refspec.h | 5 +++++ 2 files changed, 30 insertions(+)refspec.c +0
-0
refspec.c void refspec_item_clear(struct refspec_item *item) · line 187
Quoted 8 lines
refspec.c +5
-0@@ -180,6 +180,31 @@ void refspec_item_clear(struct refspec_item *item) item->exact_sha1 =0 ; } +const char *refspec_item_format (const struct refspec_item *rsi) +{ +static struct strbuf buf =STRBUF_INIT ; + +strbuf_reset (&buf);
is this even needed?
Quoted 8 lines
refspec.c +8
-0+ +if (rsi->matching) +return ":" ; + +if (rsi->negative) +strbuf_addch (&buf,'^' ); +else if (rsi->force) +strbuf_addch (&buf,'+' );
refspec.c void refspec_item_clear(struct refspec_item *item) · line 205
Quoted 10 lines
refspec.c +10
-0+ +if (rsi->src) +strbuf_addstr (&buf, rsi->src); + +if (rsi->dst) { +strbuf_addch (&buf,':' ); +strbuf_addstr (&buf, rsi->dst); + } + +return buf.buf;
should this be strbuf_detach?
Quoted 24 lines
refspec.c +2
-0+} +void refspec_init (struct refspec *rs,int fetch) {memset (rs,0 ,sizeof (*rs));refspec.h +5
-0@@ -56,6 +56,11 @@ int refspec_item_init(struct refspec_item *item, const char *refspec,void refspec_item_init_or_die (struct refspec_item *item,const char *refspec,int fetch);void refspec_item_clear (struct refspec_item *item); +/* +* Output a given refspec item to a string. +*/ +const char *refspec_item_format (const struct refspec_item *rsi); +void refspec_init (struct refspec *rs,int fetch);void refspec_append (struct refspec *rs,const char *refspec); __attribute__((format (printf,2,3)))Signature · 1 line
gitgitgadget
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 8 lines · Tom Saeger
refspec.c void refspec_item_clear(struct refspec_item *item) · line 187
Quoted 5 lines · Derrick Stolee via GitGitGadget
refspec.c +5
-0+const char *refspec_item_format (const struct refspec_item *rsi) +{ +static struct strbuf buf =STRBUF_INIT ; + +strbuf_reset (&buf);is this even needed?
This is needed due to the static strbuf declaration (which is easy to overlook).
Quoted 11 lines
Quoted 11 lines
refspec.c +11
-0+if (rsi->matching) +return ":" ; + +if (rsi->negative) +strbuf_addch (&buf,'^' ); +else if (rsi->force) +strbuf_addch (&buf,'+' ); + +if (rsi->src) +strbuf_addstr (&buf, rsi->src); +
Quoted 8 lines
refspec.c void refspec_item_clear(struct refspec_item *item) · line 205
Quoted 6 lines
refspec.c +6
-0+if (rsi->dst) { +strbuf_addch (&buf,':' ); +strbuf_addstr (&buf, rsi->dst); + } + +return buf.buf;should this be
strbuf_detach?
In normal circumstances, yes, however, with the static strbuf, this is correct.
However, a more significant question, perhaps, is why this is using a static strbuf in the first place? Does this need to be optimized because it is on a hot path? If not, then the only obvious reason why static was chosen was that sometimes the function returns a string literal and sometimes a constructed string. However, that's minor, and it would feel cleaner to avoid the static strbuf altogether by using strbuf_detach() for the constructed case and xstrdup() for the string literal case, and making it the caller's responsibility to free the result. (The comment in the header file would need to be updated to say as much.)
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 36 lines · Eric Sunshine
Quoted 8 lines · Tom Saeger
refspec.c void refspec_item_clear(struct refspec_item *item) · line 187
Quoted 5 lines · Derrick Stolee via GitGitGadget
refspec.c +5
-0+const char *refspec_item_format (const struct refspec_item *rsi) +{ +static struct strbuf buf =STRBUF_INIT ; + +strbuf_reset (&buf);is this even needed?
This is needed due to the
staticstrbuf declaration (which is easy to overlook).Quoted 11 lines
Quoted 11 lines
refspec.c +11
-0+if (rsi->matching) +return ":" ; + +if (rsi->negative) +strbuf_addch (&buf,'^' ); +else if (rsi->force) +strbuf_addch (&buf,'+' ); + +if (rsi->src) +strbuf_addstr (&buf, rsi->src); +Quoted 8 lines
refspec.c void refspec_item_clear(struct refspec_item *item) · line 205
Quoted 6 lines
refspec.c +6
-0+if (rsi->dst) { +strbuf_addch (&buf,':' ); +strbuf_addstr (&buf, rsi->dst); + } + +return buf.buf;should this be
strbuf_detach?In normal circumstances, yes, however, with the
staticstrbuf, this is correct.
Quoted 10 lines
However, a more significant question, perhaps, is why this is using a
staticstrbuf in the first place? Does this need to be optimized because it is on a hot path? If not, then the only obvious reason whystaticwas chosen was that sometimes the function returns a string literal and sometimes a constructed string. However, that's minor, and it would feel cleaner to avoid thestaticstrbuf altogether by usingstrbuf_detach()for the constructed case andxstrdup()for the string literal case, and making it the caller's responsibility to free the result. (The comment in the header file would need to be updated to say as much.)
Very good suggestion. That would also make this codepath thread-safe (I do not offhand know how important that is, though).
Thanks.
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 38 lines · Junio C Hamano
Quoted 36 lines · Eric Sunshine
Quoted 8 lines · Tom Saeger
refspec.c void refspec_item_clear(struct refspec_item *item) · line 187
Quoted 5 lines · Derrick Stolee via GitGitGadget
refspec.c +5
-0+const char *refspec_item_format (const struct refspec_item *rsi) +{ +static struct strbuf buf =STRBUF_INIT ; + +strbuf_reset (&buf);is this even needed?
This is needed due to the
staticstrbuf declaration (which is easy to overlook).Quoted 11 lines
Quoted 11 lines
refspec.c +11
-0+if (rsi->matching) +return ":" ; + +if (rsi->negative) +strbuf_addch (&buf,'^' ); +else if (rsi->force) +strbuf_addch (&buf,'+' ); + +if (rsi->src) +strbuf_addstr (&buf, rsi->src); +Quoted 8 lines
refspec.c void refspec_item_clear(struct refspec_item *item) · line 205
Quoted 6 lines
refspec.c +6
-0+if (rsi->dst) { +strbuf_addch (&buf,':' ); +strbuf_addstr (&buf, rsi->dst); + } + +return buf.buf;should this be
strbuf_detach?In normal circumstances, yes, however, with the
staticstrbuf, this is correct.
Quoted 10 lines
Quoted 10 lines
However, a more significant question, perhaps, is why this is using a
staticstrbuf in the first place? Does this need to be optimized because it is on a hot path? If not, then the only obvious reason whystaticwas chosen was that sometimes the function returns a string literal and sometimes a constructed string. However, that's minor, and it would feel cleaner to avoid thestaticstrbuf altogether by usingstrbuf_detach()for the constructed case andxstrdup()for the string literal case, and making it the caller's responsibility to free the result. (The comment in the header file would need to be updated to say as much.)
Yes, we could get around the return of ":" very easily.
Quoted 2 lines
Very good suggestion. That would also make this codepath thread-safe (I do not offhand know how important that is, though).
I was not intending to make this re-entrant/thread safe. The intention was to make it easy to consume the formatted string into output such as a printf without needing to store a temporary 'char *' and free() it afterwards. This ensures that the only lost memory over the life of the process is at most one buffer. At minimum, these are things that could be part of the message to justify this design.
So, I'm torn. This seems like a case where there is value in having the return buffer be "owned" by this method, and the expected consumers will use the buffer before calling it again. I'm not sure how important it is to do this the other way.
Would it be sufficient to justify this choice in the commit message and comment about it in the method declaration? Or is it worth adding this templating around every caller:
char *buf = refspec_item_format(rsi); ... <use 'buf'> ... free(buf);
I don't need much convincing to do this, but I hadn't properly described my opinion before. Just a small nudge would convince me to do it this way.
Thanks,
-Stolee
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 6 lines · Derrick Stolee
I was not intending to make this re-entrant/thread safe. The intention was to make it easy to consume the formatted string into output such as a printf without needing to store a temporary 'char *' and
free()it afterwards. This ensures that the only lost memory over the life of the process is at most one buffer. At minimum, these are things that could be part of the message to justify this design.
This has the failing that it won't work if someone calls it twice in the same printf() or calls it again before even consuming the first returned value, so this fails:
printf("foo: %s\nbar: %s\n",
refspec_item_format(...),
refspec_item_format(...));as does this:
const char *a = refspec_item_format(...);
const char *b = refspec_item_format(...);Historically this project would "work around" that problem by using rotating static buffers in the function, but we've mostly been moving away from that for several reasons (can't predict how many buffers will be needed, re-entrancy, etc.).
Quoted 4 lines
So, I'm torn. This seems like a case where there is value in having the return buffer be "owned" by this method, and the expected consumers will use the buffer before calling it again. I'm not sure how important it is to do this the other way.
If history is any indication, we'd probably end up moving away from such an API eventually anyhow.
Quoted 9 lines
Would it be sufficient to justify this choice in the commit message and comment about it in the method declaration? Or is it worth adding this templating around every caller:
char *buf = refspec_item_format(rsi); ... <use 'buf'> ... free(buf);
An alternative would be to have the caller pass in a strbuf to be populated by the function. It doesn't reduce the boilerplate needed by the caller (still need to create and release the strbuf), but may avoid some memory allocations. But if this isn't a critical path and won't likely ever be, then passing in strbuf may be overkill.
Quoted 3 lines
I don't need much convincing to do this, but I hadn't properly described my opinion before. Just a small nudge would convince me to do it this way.
For the reasons described above and earlier in the thread, avoiding the static buffer seems the best course of action.
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 50 lines · Eric Sunshine
Quoted 6 lines · Derrick Stolee
I was not intending to make this re-entrant/thread safe. The intention was to make it easy to consume the formatted string into output such as a printf without needing to store a temporary 'char *' and
free()it afterwards. This ensures that the only lost memory over the life of the process is at most one buffer. At minimum, these are things that could be part of the message to justify this design.This has the failing that it won't work if someone calls it twice in the same
printf()or calls it again before even consuming the first returned value, so this fails:printf("foo: %s\nbar: %s\n", refspec_item_format(...), refspec_item_format(...));as does this:
const char *a = refspec_item_format(...); const char *b = refspec_item_format(...);Historically this project would "work around" that problem by using rotating static buffers in the function, but we've mostly been moving away from that for several reasons (can't predict how many buffers will be needed, re-entrancy, etc.).
Quoted 4 lines
So, I'm torn. This seems like a case where there is value in having the return buffer be "owned" by this method, and the expected consumers will use the buffer before calling it again. I'm not sure how important it is to do this the other way.
If history is any indication, we'd probably end up moving away from such an API eventually anyhow.
Quoted 9 lines
Would it be sufficient to justify this choice in the commit message and comment about it in the method declaration? Or is it worth adding this templating around every caller:
char *buf = refspec_item_format(rsi); ... <use 'buf'> ... free(buf);An alternative would be to have the caller pass in a strbuf to be populated by the function. It doesn't reduce the boilerplate needed by the caller (still need to create and release the strbuf), but may avoid some memory allocations. But if this isn't a critical path and won't likely ever be, then passing in strbuf may be overkill.
Quoted 6 lines
Quoted 3 lines
I don't need much convincing to do this, but I hadn't properly described my opinion before. Just a small nudge would convince me to do it this way.
For the reasons described above and earlier in the thread, avoiding the static buffer seems the best course of action.
OK, convinced. I'll return a string that must be freed in my next version. Thanks!
-Stolee
Re: [PATCH 3/5] refspec: output a refspec item
In reply to Derrick Stolee via GitGitGadgetQuoted 44 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
Add a new method,
refspec_item_format(), that takes a 'structrefspec_item' pointer as input and returns a string for how that refspec item should be written to Git's config or a subcommand, such as 'git fetch'.There are several subtleties regarding special-case refspecs that can occur and are represented in
t5511-refspec.sh. These cases will be explored in new tests in the following change. It requires adding a new test helper in order to test this format directly, so that is saved for a separate change to keep this one focused on the logic of the format method.A future change will consume this method when translating refspecs in the 'prefetch' task of the 'git maintenance' builtin.
Signed-off-by Derrick Stolee <[email protected]>--- refspec.c | 25 +++++++++++++++++++++++++ refspec.h | 5 +++++ 2 files changed, 30 insertions(+)refspec.c +13
-0@@ -180,6 +180,31 @@ void refspec_item_clear(struct refspec_item *item) item->exact_sha1 =0 ; } +const char *refspec_item_format (const struct refspec_item *rsi) +{ +static struct strbuf buf =STRBUF_INIT ; + +strbuf_reset (&buf); + +if (rsi->matching) +return ":" ; + +if (rsi->negative) +strbuf_addch (&buf,'^' ); +else if (rsi->force) +strbuf_addch (&buf,'+' );
refspec.c void refspec_item_clear(struct refspec_item *item) · line 205
Quoted 10 lines
refspec.c +10
-0+ +if (rsi->src) +strbuf_addstr (&buf, rsi->src); + +if (rsi->dst) { +strbuf_addch (&buf,':' ); +strbuf_addstr (&buf, rsi->dst); + } + +return buf.buf;
There's a downthread discussion about the strbuf usage here so that's covered.
But I'm still confused about the need for this function and the following two patches. If we apply this on top of your series:
diff --git a/t/helper/test-refspec.c b/t/helper/test-refspec.c
index 08cf441a0a0..9e099e43ebf 100644
--- a/t/helper/test-refspec.c
+++ b/t/helper/test-refspec.c
@@ -31,7 +31,7 @@ int cmd__refspec(int argc, const char **argv)
continue;
}
- printf("%s\n", refspec_item_format(&rsi));
+ puts(line.buf);
refspec_item_clear(&rsi);
}The only failing test is:
+ diff -u expect output
--- expect 2021-04-07 08:12:05.577598038 +0000
+++ output 2021-04-07 08:12:05.577598038 +0000
@@ -11,5 +11,5 @@
refs/heads*/for-linus:refs/remotes/mine/*
2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed
HEAD
-HEAD
+@
:Other than that applying this on top makes everything pass:
diff --git a/builtin/gc.c b/builtin/gc.c
index 92cb8b4e0bf..ea5572d15c5 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -889,35 +889,21 @@ static int fetch_remote(struct remote *remote, void *cbdata)
strvec_push(&child.args, "--quiet");
for (i = 0; i < remote->fetch.nr; i++) {
- struct refspec_item replace;
struct refspec_item *rsi = &remote->fetch.items[i];
- struct strbuf new_dst = STRBUF_INIT;
- size_t ignore_len = 0;
+ struct strbuf new_spec = STRBUF_INIT;
+ char *pos;
if (rsi->negative) {
strvec_push(&child.args, remote->fetch.raw[i]);
continue;
}
- refspec_item_init(&replace, remote->fetch.raw[i], 1);
-
- /*
- * If a refspec dst starts with "refs/" at the start,
- * then we will replace "refs/" with "refs/prefetch/".
- * Otherwise, we will prepend the dst string with
- * "refs/prefetch/".
- */
- if (!strncmp(replace.dst, "refs/", 5))
- ignore_len = 5;
-
- strbuf_addstr(&new_dst, "refs/prefetch/");
- strbuf_addstr(&new_dst, replace.dst + ignore_len);
- free(replace.dst);
- replace.dst = strbuf_detach(&new_dst, NULL);
-
- strvec_push(&child.args, refspec_item_format(&replace));
-
- refspec_item_clear(&replace);
+ strbuf_addstr(&new_spec, remote->fetch.raw[i]);
+ if ((pos = strrchr(new_spec.buf, ':')) != NULL)
+ strbuf_splice(&new_spec, pos - new_spec.buf + 1, sizeof("refs/") - 1,
+ "refs/prefetch/", sizeof("refs/prefetch/") - 1);
+ strvec_push(&child.args, new_spec.buf);
+ strbuf_release(&new_spec);
}
return !!run_command(&child);So the purpose of this new API is that we don't want to make the assumption that strrchr(buf, ':') is a safe way to find the delimiter in the refspec, or is there some case where we grok "HEAD" but not "@" that's buggy, but not tested for in this series?
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 6 lines · Ævar Arnfjörð Bjarmason
refspec.c void refspec_item_clear(struct refspec_item *item) · line 205
Quoted 1 line · Derrick Stolee via GitGitGadget
refspec.c +1
-0+return buf.buf;There's a downthread discussion about the strbuf usage here so that's covered.
And it's fixed in v2.
Quoted 18 lines
But I'm still confused about the need for this function and the following two patches. If we apply this on top of your series:
diff --git a/t/helper/test-refspec.c b/t/helper/test-refspec.c index 08cf441a0a0..9e099e43ebf 100644 --- a/t/helper/test-refspec.c +++ b/t/helper/test-refspec.c @@ -31,7 +31,7 @@ int cmd__refspec(int argc, const char **argv) continue; } - printf("%s\n", refspec_item_format(&rsi)); + puts(line.buf); refspec_item_clear(&rsi); }The only failing test is:
Quoted 10 lines
+ diff -u expect output
--- expect 2021-04-07 08:12:05.577598038 +0000
+++ output 2021-04-07 08:12:05.577598038 +0000
@@ -11,5 +11,5 @@
refs/heads*/for-linus:refs/remotes/mine/*
2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed
HEAD
-HEAD
+@
:It should be obvious that taking refspecs as input, parsing them, then reformatting them for output should be almost equivalent to printing the input line.
The point is to exercise the logic that actually formats the refspec for output. The test-tool clearly does this.
The logic for converting a 'struct refspec_item' to a string is non-trivial and worth testing. I don't understand why you are concerned that the black-box of the test-tool could be done more easily to "trick" the test script.
Quoted 4 lines
So the purpose of this new API is that we don't want to make the assumption that
strrchr(buf, ':')is a safe way to find the delimiter in the refspec, or is there some case where we grok "HEAD" but not "@" that's buggy, but not tested for in this series?
The purpose is to allow us to modify a 'struct refspec_item' andproduce a refspec string instead of munging a refspec string directly.
Thanks,
-Stolee
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 43 lines · Derrick Stolee
Quoted 6 lines · Ævar Arnfjörð Bjarmason
refspec.c void refspec_item_clear(struct refspec_item *item) · line 205
Quoted 1 line · Derrick Stolee via GitGitGadget
refspec.c +1
-0+return buf.buf;There's a downthread discussion about the strbuf usage here so that's covered.
And it's fixed in v2.
Quoted 18 lines
But I'm still confused about the need for this function and the following two patches. If we apply this on top of your series:
diff --git a/t/helper/test-refspec.c b/t/helper/test-refspec.c index 08cf441a0a0..9e099e43ebf 100644 --- a/t/helper/test-refspec.c +++ b/t/helper/test-refspec.c @@ -31,7 +31,7 @@ int cmd__refspec(int argc, const char **argv) continue; } - printf("%s\n", refspec_item_format(&rsi)); + puts(line.buf); refspec_item_clear(&rsi); }The only failing test is:
Quoted 10 lines
+ diff -u expect output --- expect 2021-04-07 08:12:05.577598038 +0000 +++ output 2021-04-07 08:12:05.577598038 +0000 @@ -11,5 +11,5 @@ refs/heads*/for-linus:refs/remotes/mine/* 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed HEAD -HEAD +@ :It should be obvious that taking refspecs as input, parsing them, then reformatting them for output should be almost equivalent to printing the input line.
Quoted 7 lines
The point is to exercise the logic that actually formats the refspec for output. The test-tool clearly does this.
The logic for converting a 'struct
refspec_item' to a string is non-trivial and worth testing. I don't understand why you are concerned that the black-box of the test-tool could be done more easily to "trick" the test script.
Yes, but why do we need to convert it to a struct refspec_item in the first place?
Maybe I'm just overly comfortable with string munging but I think the smaller patch-on-top to use strbuf_splice() is simpler than adding a new API just for this use-case.
But I'm still wondering if that @ v.s. HEAD case is something this series actually needs in its end goal (but then has a missing test?), or if it was just a "let's test the guts of the refspec.c while we're at it".
Quoted 7 lines
Quoted 4 lines
So the purpose of this new API is that we don't want to make the assumption that
strrchr(buf, ':')is a safe way to find the delimiter in the refspec, or is there some case where we grok "HEAD" but not "@" that's buggy, but not tested for in this series?The purpose is to allow us to modify a 'struct
refspec_item' andproduce a refspec string instead of munging a refspec string directly.
But aren't we doing that all over the place, e.g. the grep results for "refspec_appendf". Even for things purely constructed on the C API level we pass a const char* now.
I'm not saying it wouldn't be nice to have the refspec.c API changed to have a clear delimitation between its const char* handling, and C-level uses which could construct and pass a "struct refspec_item" instead.
But is it *needed* here in a way that I've missed, or is this just a partial testing/refactoring of that API while we're at it?
[Guessing ahead here because of our TZ difference]:
It seems to me that if this is such a partial refactoring it's a strange way to go about it.
We're left with freeing/munging the "struct refspec" src/dst in-place and re-constructing a string that has "+" etc., but we already had that data in parse_refspec() just before we'd call refspec_item_format(). That function could then just spew out a pre-formatted string we'd squirreled away in "struct refspec_item".
If the lengthy paragraph you have at the end of 4/5 holds true, then such an internal representation doesn't need to have the "refs/" prefix stores as a const char* (in cases where it's not just "*" or whatever), no?. We'd then be able to more easily init/copy/munge the refspec for formatting.
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 5 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Derrick Stolee
The purpose is to allow us to modify a 'struct
refspec_item' andproduce a refspec string instead of munging a refspec string directly.
Ouch. I thought the goal was to take
[remote "origin"]
fetch = $src:$dst
let the code that is used in the actual fetching to parse it into the in-core "refspec_item", and then transform the refspec_item by
- discarding it if the item does not result in storing in the real fetch
- tweaking $dst side so that it won't touch anywhere outside refs/prefetch/ to avoid disturbing end-user's notion of what the latest state of the remote ref is.
so that the "parsed" refspec_item is passed to the fetch machinery without ever having to be converted back to textual form.
Why do we even need to "andproduce a refspec string"?
Re: [PATCH 3/5] refspec: output a refspec item
Quoted 19 lines · Junio C Hamano
Quoted 5 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Derrick Stolee
The purpose is to allow us to modify a 'struct
refspec_item' andproduce a refspec string instead of munging a refspec string directly.Ouch. I thought the goal was to take
[remote "origin"]
fetch = $src:$dstlet the code that is used in the actual fetching to parse it into the in-core "
refspec_item", and then transform therefspec_itemby- discarding it if the item does not result in storing in the real fetch
Quoted 8 lines
- tweaking $dst side so that it won't touch anywhere outside refs/prefetch/ to avoid disturbing end-user's notion of what the latest state of the remote ref is.
so that the "parsed"
refspec_itemis passed to the fetch machinery without ever having to be converted back to textual form.Why do we even need to "andproduce a refspec string"?
We're shelling out to "git fetch", but we first munge the refspec on in "git gc".
But yes, it seems even more straightforward to do away with passing the refspec at all to "git fetch", and instead pass some (maybe internal only, and documented as such) "--refspec-dst-prefix=refs/prefetch/" option to "git fetch".
I.e. get_ref_map() over there is already doing a version of this loop over "remote->fetch.nr".
So instead of "git gc" doing the loop, then passing all the refspecs on the command-line, it could tell "git fetch" to do the same munging when it does the same iteration.
Doing the munging in builtin/gc.c's fetch_remote() just seems like a relic from when we didn't care what decision builtin/fetch.c made about refspecs, we always wanted our custom one.
Re: [PATCH 3/5] refspec: output a refspec item
Ævar Arnfjörð Bjarmason <[email protected]> writes:
Quoted 11 lines · Ævar Arnfjörð Bjarmason
But yes, it seems even more straightforward to do away with passing the refspec at all to "git fetch", and instead pass some (maybe internal only, and documented as such) "--refspec-dst-prefix=refs/prefetch/" option to "git fetch".
I.e.
get_ref_map()over there is already doing a version of this loop over "remote->fetch.nr".So instead of "git gc" doing the loop, then passing all the refspecs on the command-line, it could tell "git fetch" to do the same munging when it does the same iteration.
That direction makes quite a lot of sense to me.
Quoted 3 lines
Doing the munging in builtin/gc.c's
fetch_remote()just seems like a relic from when we didn't care what decisionbuiltin/fetch.cmade about refspecs, we always wanted our custom one.
PATCH 4/5 test-tool: test refspec input/output
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Add a new test-helper, 'test-tool refspec', that currently reads stdin line-by-line and translates the refspecs using the parsing logic of refspec_item_init() and writes them to output.
Create a test in t5511-refspec.sh that uses this helper to test several known special cases. This includes all of the special properties of the 'struct refspec_item', including:
* force: The refspec starts with '+'.
* pattern: Each side of the refspec has a glob character ('*')
* matching: The refspec is simply the string ":".
* exact_sha1: The 'src' string is a 40-character hex string.
* negative: The refspec starts with '^' and 'dst' is NULL.
While the exact_sha1 property doesn't require special logic in refspec_item_format, it is still tested here for completeness.
There is also the special-case refspec "@" which translates to "HEAD".
Note that if a refspec does not start with "refs/", then that is not incorporated as part of the 'struct refspec_item'. This behavior is confirmed by these tests. These refspecs still work in the wild because the refs layer interprets them appropriately as branches, prepending "refs/" or "refs/heads/" as necessary. I spent some time attempting to insert these prefixes explicitly in parse_refspec(), but these are several subtleties I was unable to overcome. If such a change were to be made, then this new test in t5511-refspec.sh will need to be updated with new output. For example, the input lines ending with "translated" are designed to demonstrate these subtleties.
--- Makefile | 1 + t/helper/test-refspec.c | 39 +++++++++++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t5511-refspec.sh | 41 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 t/helper/test-refspec.c
Makefile +1 -0
@@ -734,6 +734,7 @@ TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o TEST_BUILTINS_OBJS += test-read-graph.o TEST_BUILTINS_OBJS += test-read-midx.o +TEST_BUILTINS_OBJS += test-refspec.o TEST_BUILTINS_OBJS += test-ref-store.o TEST_BUILTINS_OBJS += test-regex.o TEST_BUILTINS_OBJS += test-repository.o
t/helper/test-refspec.c new file +39 -0
@@ -0,0 +1,39 @@ +#include "cache.h" +#include "parse-options.h" +#include "refspec.h" +#include "strbuf.h" +#include "test-tool.h" + +static const char *const refspec_usage[] = { +N_ ("test-tool refspec [--fetch]" ), +NULL +}; + +int cmd__refspec (int argc,const char **argv) +{ +struct strbuf line =STRBUF_INIT ; +int fetch =0 ; + +struct option refspec_options [] = { +OPT_BOOL (0 ,"fetch" , &fetch, +N_ ("enable the 'fetch' option for parsing refpecs" )), +OPT_END () + }; + + argc =parse_options (argc, argv,NULL , refspec_options, + refspec_usage,0 ); + +while (strbuf_getline (&line, stdin) !=EOF ) { +struct refspec_item rsi; + +if (!refspec_item_init (&rsi, line.buf, fetch)) { +printf ("failed to parse %s\n" , line.buf); +continue ; + } + +printf ("%s\n" ,refspec_item_format (&rsi)); +refspec_item_clear (&rsi); + } + +return 0 ; +}
t/helper/test-tool.c +1 -0
@@ -55,6 +55,7 @@ static struct test_cmd cmds[] = { { "read-cache", cmd__read_cache }, { "read-graph", cmd__read_graph }, { "read-midx", cmd__read_midx }, + { "refspec", cmd__refspec }, { "ref-store", cmd__ref_store }, { "regex", cmd__regex }, { "repository", cmd__repository },
t/helper/test-tool.h +1 -0
@@ -44,6 +44,7 @@ int cmd__reach(int argc, const char **argv);int cmd__read_cache (int argc,const char **argv);int cmd__read_graph (int argc,const char **argv);int cmd__read_midx (int argc,const char **argv); +int cmd__refspec (int argc,const char **argv);int cmd__ref_store (int argc,const char **argv);int cmd__regex (int argc,const char **argv);int cmd__repository (int argc,const char **argv);
t/t5511-refspec.sh +41 -0
45 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH 4/5] test-tool: test refspec input/output
Quoted 8 lines · Derrick Stolee via GitGitGadget
Add a new test-helper, 'test-tool refspec', that currently reads stdin line-by-line and translates the refspecs using the parsing logic of
refspec_item_init()and writes them to output.[...]Signed-off-by Derrick Stolee <[email protected]>---
t/helper/test-refspec.c line 6
Quoted 6 lines
t/helper/test-refspec.c +6
-0@@ -0,0 +1,39 @@ +int cmd__refspec (int argc,const char **argv) +{ +struct strbuf line =STRBUF_INIT ; + [...] +return 0 ; +}
Leaking strbuf line here. Yes, I realize that the function is returning and test-tool exiting immediately after this, so not a big deal, but it's easy to do this correctly by releasing the strbuf, thus setting good precedence for people who might use this as a template for new test-tool functions they add in the future.
Quoted 10 lines
t/t5511-refspec.sh +8
-0@@ -93,4 +93,45 @@ test_refspec fetch "refs/heads/${good}" +test_expect_success 'test input/output round trip' ' + cat >input <<-\EOF && + +refs/heads/*:refs/remotes/origin/* + refs/heads/*:refs/remotes/origin/* + refs/heads/main:refs/remotes/frotz/xyzzy + :refs/remotes/frotz/deleteme + ^refs/heads/secrets + refs/heads/secret:refs/heads/translated
t/t5511-refspec.sh test_refspec fetch "refs/heads/${good}" · line 110
Quoted 10 lines
t/t5511-refspec.sh +10
-0+ refs/heads/secret:heads/translated + refs/heads/secret:remotes/translated + secret:translated + refs/heads/*:remotes/xxy/* + refs/heads*/for-linus:refs/remotes/mine/* + 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed + HEAD + @ + : + EOF
Over-indented heredoc body. It is customary[1] in this codebase for the body and EOF to have the same indentation as the command which starts the heredoc.
Quoted 7 lines
t/t5511-refspec.sh +7
-0+ cat >expect <<-\EOF && + +refs/heads/*:refs/remotes/origin/* + refs/heads/*:refs/remotes/origin/* + refs/heads/main:refs/remotes/frotz/xyzzy + :refs/remotes/frotz/deleteme + ^refs/heads/secrets + refs/heads/secret:refs/heads/translated
t/t5511-refspec.sh test_refspec fetch "refs/heads/${good}" · line 130
Quoted 10 lines
t/t5511-refspec.sh +10
-0+ refs/heads/secret:heads/translated + refs/heads/secret:remotes/translated + secret:translated + refs/heads/*:remotes/xxy/* + refs/heads*/for-linus:refs/remotes/mine/* + 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed + HEAD + HEAD + : + EOF
Ditto.
t/t5511-refspec.sh test_refspec fetch "refs/heads/${good}" · line 135
Quoted 5 lines
t/t5511-refspec.sh +5
-0+ test-tool refspec <input >output && + test_cmp expect output && + test-tool refspec --fetch <input >output && + test_cmp expect output +'
[1]: https://lore.kernel.org/git/CAPig+cSBVG0AdyqXH2mZp6Ohrcb8_ec1Mm_vGbQM4zWT_7yYxQ@mail.gmail.com/
Re: [PATCH 4/5] test-tool: test refspec input/output
Quoted 39 lines · Eric Sunshine
Quoted 8 lines · Derrick Stolee via GitGitGadget
Add a new test-helper, 'test-tool refspec', that currently reads stdin line-by-line and translates the refspecs using the parsing logic of
refspec_item_init()and writes them to output.[...]Signed-off-by Derrick Stolee <[email protected]>---
t/helper/test-refspec.c line 6
Quoted 6 lines
t/helper/test-refspec.c +6
-0@@ -0,0 +1,39 @@ +int cmd__refspec (int argc,const char **argv) +{ +struct strbuf line =STRBUF_INIT ; + [...] +return 0 ; +}Leaking
strbuf linehere. Yes, I realize that the function is returning and test-tool exiting immediately after this, so not a big deal, but it's easy to do this correctly by releasing the strbuf, thus setting good precedence for people who might use this as a template for new test-tool functions they add in the future.Quoted 16 lines
t/t5511-refspec.sh +14
-0@@ -93,4 +93,45 @@ test_refspec fetch "refs/heads/${good}" +test_expect_success 'test input/output round trip' ' + cat >input <<-\EOF && + +refs/heads/*:refs/remotes/origin/* + refs/heads/*:refs/remotes/origin/* + refs/heads/main:refs/remotes/frotz/xyzzy + :refs/remotes/frotz/deleteme + ^refs/heads/secrets + refs/heads/secret:refs/heads/translated + refs/heads/secret:heads/translated + refs/heads/secret:remotes/translated + secret:translated + refs/heads/*:remotes/xxy/* + refs/heads*/for-linus:refs/remotes/mine/* + 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed
Quoted 8 lines
t/t5511-refspec.sh test_refspec fetch "refs/heads/${good}" · line 113
Quoted 4 lines
t/t5511-refspec.sh +4
-0+ HEAD + @ + : + EOFOver-indented heredoc body. It is customary[1] in this codebase for the body and EOF to have the same indentation as the command which starts the heredoc.
Good catches. Thanks!
-Stolee
Re: [PATCH 4/5] test-tool: test refspec input/output
In reply to Derrick Stolee via GitGitGadgetQuoted 5 lines · Derrick Stolee via GitGitGadget
t/helper/test-refspec.c new file +5
-0+static const char *const refspec_usage[] = { +N_ ("test-tool refspec [--fetch]" ), +NULL +}; +
t/helper/test-refspec.c line 21
Quoted 10 lines
t/helper/test-refspec.c new file +10
-0+int cmd__refspec (int argc,const char **argv) +{ +struct strbuf line =STRBUF_INIT ; +int fetch =0 ; + +struct option refspec_options [] = { +OPT_BOOL (0 ,"fetch" , &fetch, +N_ ("enable the 'fetch' option for parsing refpecs" )), +OPT_END () + };
I don't think we should waste translator time by marking these (or anything else in t/helper) with N_()).
I see you probably copied this from elsewhere & we should probably fix the existing ones, but no reason to add new ones...
PATCH 5/5 maintenance: allow custom refspecs during prefetch
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The prefetch task previously used the default refspec source plus a custom refspec destination to avoid colliding with remote refs:
+refs/heads/*:refs/prefetch/<remote>/*
However, some users customize their refspec to reduce how much data they download from specific remotes. This can involve restrictive patterns for fetching or negative patterns to avoid downloading some refs.
Modify fetch_remote() to iterate over the remote's refspec list and translate that into the appropriate prefetch scenario. Specifically, re-parse the raw form of the refspec into a new 'struct refspec' and modify the 'dst' member to replace a leading "refs/" substring with "refs/prefetch/", or prepend "refs/prefetch/" to 'dst' otherwise. Negative refspecs do not have a 'dst' so they can be transferred to the 'git fetch' command unmodified.
This prefix change provides the benefit of keeping whatever collisions may exist in the custom refspecs, if that is a desirable outcome.
This changes the names of the refs that would be fetched by the default refspec. Instead of "refs/prefetch/<remote>/<branch>" they will now go to "refs/prefetch/remotes/<remote>/<branch>". While this is a change, it is not a seriously breaking one: these refs are intended to be hidden and not used.
Update the documentation to be more generic about the destination refs. Do not mention custom refpecs explicitly, as that does not need to be highlighted in this documentation. The important part of placing refs in refs/prefetch remains.
--- Documentation/git-maintenance.txt | 3 +-- builtin/gc.c | 34 +++++++++++++++++++++++- t/t7900-maintenance.sh | 43 ++++++++++++++++++++++++++----- 3 files changed, 71 insertions(+), 9 deletions(-)
Documentation/git-maintenance.txt +1 -2
@@ -94,8 +94,7 @@ prefetch:: 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. + refs are stored in `refs/prefetch/`. Also, tags are not updated. + This is done to avoid disrupting the remote-tracking branches. The end users expect these refs to stay unmoved unless they initiate a fetch. With prefetch
builtin/gc.c +33 -1
@@ -32,6 +32,7 @@#include "remote.h" #include "object-store.h" #include "exec-cmd.h" +#include "refspec.h" #define FAILED_RUN "failed to run %s"
@@ -877,6 +878,7 @@ static int fetch_remote(struct remote *remote, void *cbdata) {struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +int i; child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",
@@ -886,7 +888,37 @@ static int fetch_remote(struct remote *remote, void *cbdata)if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); +for (i =0 ; i < remote->fetch.nr; i++) { +struct refspec_item replace; +struct refspec_item *rsi = &remote->fetch.items[i]; +struct strbuf new_dst =STRBUF_INIT ; +size_t ignore_len =0 ; + +if (rsi->negative) { +strvec_push (&child.args, remote->fetch.raw[i]); +continue ; + } + +refspec_item_init (&replace, remote->fetch.raw[i],1 ); + +/* +* If a refspec dst starts with "refs/" at the start, +* then we will replace "refs/" with "refs/prefetch/". +* Otherwise, we will prepend the dst string with +* "refs/prefetch/". +*/ +if (!strncmp (replace.dst,"refs/" ,5 )) + ignore_len =5 ; + +strbuf_addstr (&new_dst,"refs/prefetch/" ); +strbuf_addstr (&new_dst, replace.dst + ignore_len); +free (replace.dst); + replace.dst =strbuf_detach (&new_dst,NULL ); + +strvec_push (&child.args,refspec_item_format (&replace)); + +refspec_item_clear (&replace); + }return !!run_command (&child); }
t/t7900-maintenance.sh +37 -6
57 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
Quoted 98 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The prefetch task previously used the default refspec source plus a custom refspec destination to avoid colliding with remote refs:
+refs/heads/*:refs/prefetch/<remote>/*However, some users customize their refspec to reduce how much data they download from specific remotes. This can involve restrictive patterns for fetching or negative patterns to avoid downloading some refs.
Modify
fetch_remote()to iterate over the remote's refspec list and translate that into the appropriate prefetch scenario. Specifically, re-parse the raw form of the refspec into a new 'struct refspec' and modify the 'dst' member to replace a leading "refs/" substring with "refs/prefetch/", or prepend "refs/prefetch/" to 'dst' otherwise. Negative refspecs do not have a 'dst' so they can be transferred to the 'git fetch' command unmodified.This prefix change provides the benefit of keeping whatever collisions may exist in the custom refspecs, if that is a desirable outcome.
This changes the names of the refs that would be fetched by the default refspec. Instead of "refs/prefetch/<remote>/<branch>" they will now go to "refs/prefetch/remotes/<remote>/<branch>". While this is a change, it is not a seriously breaking one: these refs are intended to be hidden and not used.
Update the documentation to be more generic about the destination refs. Do not mention custom refpecs explicitly, as that does not need to be highlighted in this documentation. The important part of placing refs in refs/prefetch remains.
Reported-by Tom Saeger <[email protected]>Signed-off-by Derrick Stolee <[email protected]>--- Documentation/git-maintenance.txt | 3 +-- builtin/gc.c | 34 +++++++++++++++++++++++- t/t7900-maintenance.sh | 43 ++++++++++++++++++++++++++----- 3 files changed, 71 insertions(+), 9 deletions(-)Documentation/git-maintenance.txt +1
-2@@ -94,8 +94,7 @@ prefetch:: 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. + refs are stored in `refs/prefetch/`. Also, tags are not updated. + This is done to avoid disrupting the remote-tracking branches. The end users expect these refs to stay unmoved unless they initiate a fetch. With prefetchbuiltin/gc.c +20
-1@@ -32,6 +32,7 @@#include "remote.h" #include "object-store.h" #include "exec-cmd.h" +#include "refspec.h" #define FAILED_RUN "failed to run %s"@@ -877,6 +878,7 @@ static int fetch_remote(struct remote *remote, void *cbdata) {struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +int i; child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",@@ -886,7 +888,37 @@ static int fetch_remote(struct remote *remote, void *cbdata)if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); +for (i =0 ; i < remote->fetch.nr; i++) { +struct refspec_item replace; +struct refspec_item *rsi = &remote->fetch.items[i]; +struct strbuf new_dst =STRBUF_INIT ; +size_t ignore_len =0 ; + +if (rsi->negative) { +strvec_push (&child.args, remote->fetch.raw[i]); +continue ; + } + +refspec_item_init (&replace, remote->fetch.raw[i],1 ); + +/* +* If a refspec dst starts with "refs/" at the start, +* then we will replace "refs/" with "refs/prefetch/". +* Otherwise, we will prepend the dst string with +* "refs/prefetch/".
builtin/gc.c static int fetch_remote(struct remote *remote, void *cbdata) · line 918
Quoted 10 lines
builtin/gc.c +10
-0+*/ +if (!strncmp (replace.dst,"refs/" ,5 )) + ignore_len =5 ; + +strbuf_addstr (&new_dst,"refs/prefetch/" ); +strbuf_addstr (&new_dst, replace.dst + ignore_len); +free (replace.dst); + replace.dst =strbuf_detach (&new_dst,NULL ); + +strvec_push (&child.args,refspec_item_format (&replace));
see comment on 3/5, think refspec_item_format is leaking here. this code looks fine though.
Quoted 70 lines
builtin/gc.c +3
-0+ +refspec_item_clear (&replace); + }return !!run_command (&child); }t/t7900-maintenance.sh +37
-6@@ -142,20 +142,51 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote1/* <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote2/* <run-prefetch.txt && test_path_is_missing .git/refs/remotes && - git log prefetch/remote1/one && - git log prefetch/remote2/two && + git log prefetch/remotes/remote1/one && + git log prefetch/remotes/remote2/two && git fetch --all && - test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one && - test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two && + test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one && + test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two && test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && ! grep "prefetch" log ' +test_expect_success 'prefetch custom refspecs' ' + git -C clone1 branch -f special/fetched HEAD && + git -C clone1 branch -f special/secret/not-fetched HEAD && + + # create multiple refspecs for remote1 + git config --add remote.remote1.fetch +refs/heads/special/fetched:refs/heads/fetched && + git config --add remote.remote1.fetch ^refs/heads/special/secret/not-fetched && + + GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null && + + fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && + + # skips second refspec because it is not a pattern type + rs1="+refs/heads/*:refs/prefetch/remotes/remote1/*" && + rs2="+refs/heads/special/fetched:refs/prefetch/heads/fetched" && + rs3="^refs/heads/special/secret/not-fetched" && + + test_subcommand git fetch remote1 $fetchargs $rs1 $rs2 $rs3 <prefetch-refspec.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote2/* <prefetch-refspec.txt && + + # first refspec is overridden by second + test_must_fail git rev-parse refs/prefetch/special/fetched && + git rev-parse refs/prefetch/heads/fetched && + + # possible incorrect places for the non-fetched ref + test_must_fail git rev-parse refs/prefetch/remotes/remote1/secret/not-fetched && + test_must_fail git rev-parse refs/prefetch/remotes/remote1/not-fetched && + test_must_fail git rev-parse refs/heads/secret/not-fetched && + test_must_fail git rev-parse refs/heads/not-fetched +' + test_expect_success 'prefetch and existing log.excludeDecoration values' ' git config --unset-all log.excludeDecoration && git config log.excludeDecoration refs/remotes/remote1/ &&Signature · 1 line
gitgitgadget
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
Quoted 6 lines · Tom Saeger
builtin/gc.c static int fetch_remote(struct remote *remote, void *cbdata) · line 918
Quoted 2 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
builtin/gc.c +1
-0+strvec_push (&child.args,refspec_item_format (&replace));see comment on 3/5, think
refspec_item_formatis leaking here. this code looks fine though.
I will respond to the comments on patch 3, but this is the reason a static strbuf is used: we can print like this without needing to store the buffer in a variable and free() it here. Seemed like an easier-to-use API for a non-critical area of code. I'll continue the discussion over on that patch thread.
Thanks,
-Stolee
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
In reply to Derrick Stolee via GitGitGadgetQuoted 28 lines · Derrick Stolee via GitGitGadget
[...]
t/t7900-maintenance.sh +6
-6@@ -142,20 +142,51 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote1/* <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote2/* <run-prefetch.txt && test_path_is_missing .git/refs/remotes && - git log prefetch/remote1/one && - git log prefetch/remote2/two && + git log prefetch/remotes/remote1/one && + git log prefetch/remotes/remote2/two && git fetch --all && - test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one && - test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two && + test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one && + test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two && test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && ! grep "prefetch" log '
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 167
Quoted 10 lines
t/t7900-maintenance.sh +9
-0+test_expect_success 'prefetch custom refspecs' ' + git -C clone1 branch -f special/fetched HEAD && + git -C clone1 branch -f special/secret/not-fetched HEAD && + + # create multiple refspecs for remote1 + git config --add remote.remote1.fetch +refs/heads/special/fetched:refs/heads/fetched && + git config --add remote.remote1.fetch ^refs/heads/special/secret/not-fetched && + + GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null &&
I see this is following some established convention in the file, but is there really not a way to make this pass without directing stderr to /dev/null? It makes ad-hoc debugging when reviewing harder.
I tried just removing it, but then (in an earlier test case) the "test_subcommand" fails because it can't find the line we're looking for, so us piping stderr to /dev/null impacts our trace2 output?
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
Quoted 31 lines · Ævar Arnfjörð Bjarmason
Quoted 29 lines · Derrick Stolee via GitGitGadget
[...]
t/t7900-maintenance.sh +6
-6@@ -142,20 +142,51 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote1/* <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote2/* <run-prefetch.txt && test_path_is_missing .git/refs/remotes && - git log prefetch/remote1/one && - git log prefetch/remote2/two && + git log prefetch/remotes/remote1/one && + git log prefetch/remotes/remote2/two && git fetch --all && - test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one && - test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two && + test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one && + test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two && test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && ! grep "prefetch" log '
Quoted 13 lines
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 167
Quoted 9 lines
t/t7900-maintenance.sh +9
-0+test_expect_success 'prefetch custom refspecs' ' + git -C clone1 branch -f special/fetched HEAD && + git -C clone1 branch -f special/secret/not-fetched HEAD && + + # create multiple refspecs for remote1 + git config --add remote.remote1.fetch +refs/heads/special/fetched:refs/heads/fetched && + git config --add remote.remote1.fetch ^refs/heads/special/secret/not-fetched && + + GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null &&I see this is following some established convention in the file, but is there really not a way to make this pass without directing stderr to
/dev/null? It makes ad-hoc debugging when reviewing harder.
As I later found out this is copy/pasted to get around the fact that --quiet is dependent on isatty(), so without this the result would be different under --verbose and non-verbose testing.
So that dates back to 3ddaad0e060 (maintenance: add --quiet option, 2020-09-17), but I see other quiet=isatty(2) in related code. I wish we could isolate that particular behavior so removing the 2>/dev/null when debugging the tests doesn't cause you to run into this, maybe an explicit --quiet or --no-quiet option for all but one test that's checking that isatty() behavior?
Quoted 3 lines
I tried just removing it, but then (in an earlier test case) the "
test_subcommand" fails because it can't find the line we're looking for, so us piping stderr to/dev/nullimpacts our trace2 output?
I hadn't seen seen test_subcommand before, sorry to be blunt, but "ew!".
So we're ad-hoc grepping trace2 JSON output just to find out whether we invoked some subcommand. But unlike test_expect_code etc. this one doesn't run git for you, but instead we have temp *.txt files and the command disconnected from the run.
And because you're using "grep" and "! grep" to test, you're hiding the difference between "did not find this line" v.s. "did not find anything at all".
Because of that the second test using test_subcommand is either buggy or painfully non-obvious. We check that "run --auto" doesn't contain a "auto --quiet", but in reality it doesn't contain any subcommands at all. We didn't run any because it exited with "nothing to pack".
I think converting the whole thing to something like the WIP/RFC patch below is much better and more readable.
The pattern is basically stolen from test_commit and check_sub_test_lib_test_err, respectively.
As an aside: The new test_expect_process_tree function would be much less painful if we had a helper that took the JSON output and emitted some sensible subset of the information therein. For now I just ad-hoc grepped the "perf" output. AFAICT the only way to get "depth" from trace2's JSON so to count slashes in SIDs.
In the case of your tests you're mostly/(only?) interested in a slice of the "child_start" events. If we had a helper to spew out a pretty-print version of that (or a subset of it) we could test_cmp against that directly.
t/t7900-maintenance.sh +32 -19
@@ -30,28 +30,41 @@ test_expect_success 'help text' ' ' test_expect_success 'run [--auto|--quiet]' ' - GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \ - git maintenance run 2>/dev/null && - GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \ - git maintenance run --auto 2>/dev/null && - GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \ - git maintenance run --no-quiet 2>/dev/null && - test_subcommand git gc --quiet <run-no-auto.txt && - test_subcommand ! git gc --auto --quiet <run-auto.txt && - test_subcommand git gc --no-quiet <run-no-quiet.txt + test_expect_process_tree --depth 0 git maintenance run <<-\OUT 3<<-\ERR && + git gc --quiet + OUT + ERR + + test_expect_process_tree --depth 0 git maintenance run --auto <<-\OUT 3<<-\ERR && + OUT + ERR + + test_expect_process_tree --depth 0 git maintenance run --quiet <<-\OUT 3<<-\ERR && + git gc --quiet + OUT + ERR + + test_expect_process_tree --depth 0 git maintenance run --no-quiet <<-\OUT 3<<-\ERR + git gc --no-quiet + OUT + ERR ' test_expect_success 'maintenance.auto config option' ' - GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 && - test_subcommand git maintenance run --auto --quiet <default && - GIT_TRACE2_EVENT="$(pwd)/true" \ - git -c maintenance.auto=true \ - commit --quiet --allow-empty -m 2 && - test_subcommand git maintenance run --auto --quiet <true && - GIT_TRACE2_EVENT="$(pwd)/false" \ - git -c maintenance.auto=false \ - commit --quiet --allow-empty -m 3 && - test_subcommand ! git maintenance run --auto --quiet <false + test_expect_process_tree git commit --quiet --allow-empty -m 1 <<-\OUT 3<<-\ERR && + git maintenance run --auto --quiet + OUT + ERR + + test_expect_process_tree git commit --quiet --allow-empty -m 2 <<-\OUT 3<<-\ERR && + git maintenance run --auto --quiet + OUT + ERR + + test_expect_process_tree git commit --quiet --allow-empty -m 3 <<-\OUT 3<<-\ERR + git maintenance run --auto --quiet + OUT + ERR ' test_expect_success 'maintenance.<task>.enabled' '
t/test-lib-functions.sh +32 -0
@@ -1625,6 +1625,38 @@ test_path_is_hidden () { return 1 } +test_expect_process_tree () { + depth= && + >actual && + cat >expect && + cat <&3 >expect.err + while test $# != 0 + do + case "$1" in + --depth) + depth="$2" + shift + ;; + *) + break + ;; + esac + shift + done && + log="$(pwd)/proc-tree.txt" && + >"$log" && + GIT_TRACE2_PERF="$log" "$@" 2>actual.err && + grep "child_start" proc-tree.txt >proc-tree-start.txt || : && + if test -n "$depth" + then + grep " d$depth " proc-tree-start.txt >tmp.txt || : && + mv tmp.txt proc-tree-start.txt + fi && + sed -e 's/^.*argv:\[//' -e 's/\]$//' <proc-tree-start.txt >actual && + test_cmp expect actual && + test_cmp expect.err actual.err +} 7>&2 2>&4 + # Check that the given command was invoked as part of the # trace2-format trace on stdin. #
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
Quoted 10 lines · Ævar Arnfjörð Bjarmason
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Review
Quoted 1 line · Derrick Stolee via GitGitGadget
(quoted diff) +1
-0+ GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null &&I see this is following some established convention in the file, but is there really not a way to make this pass without directing stderr to
/dev/null? It makes ad-hoc debugging when reviewing harder.
Quoted 3 lines
As I later found out this is copy/pasted to get around the fact that --quiet is dependent on
isatty(), so without this the result would be different under --verbose and non-verbose testing.
Yes, adding --quiet directly is a better pattern.
Quoted 7 lines
So that dates back to
3ddaad0e060(maintenance: add --quiet option, 2020-09-17), but I see other quiet=isatty(2) in related code. I wish we could isolate that particular behavior so removing the 2>/dev/null when debugging the tests doesn't cause you to run into this, maybe an explicit --quiet or --no-quiet option for all but one test that's checking thatisatty()behavior?
Quoted 5 lines
Quoted 3 lines
I tried just removing it, but then (in an earlier test case) the "
test_subcommand" fails because it can't find the line we're looking for, so us piping stderr to/dev/nullimpacts our trace2 output?I hadn't seen seen
test_subcommandbefore, sorry to be blunt, but "ew!".
Saying "I'm about to be rude" before being rude doesn't excuse it. I had to step away and get over this comment before I could examine the actually constructive feedback below.
A better way to communicate the same information could be "This test helper seems more complicated than necessary, and has some gaps that could be filled." I completely agree with this statement.
Quoted 8 lines
So we're ad-hoc grepping trace2 JSON output just to find out whether we invoked some subcommand. But unlike
test_expect_codeetc. this one doesn't run git for you, but instead we have temp *.txt files and the command disconnected from the run.And because you're using "grep" and "! grep" to test, you're hiding the difference between "did not find this line" v.s. "did not find anything at all".
You're right that there is value in comparing the ordered list of subcommands run by a given Git command. That will catch buggy tests that are checking that a subcommand doesn't run.
Quoted 4 lines
Because of that the second test using
test_subcommandis either buggy or painfully non-obvious. We check that "run --auto" doesn't contain a "auto --quiet", but in reality it doesn't contain any subcommands at all. We didn't run any because it exited with "nothing to pack".
That exit is from the third command, which does not pass the --auto command. The "nothing to pack" output is from the 'git gc --no-quiet' subcommand that is being checked in this third test.
Quoted 2 lines
I think converting the whole thing to something like the WIP/RFC patch below is much better and more readable.
This is an interesting approach. I don't see you using the ERR that you are inputting anywhere, so that seems like an unnecessary bloat to the consumers. But maybe I haven't discovered all of the places where this would be useful, but it seems better to pipe stderr to a file for later comparison when needed.
Quoted 8 lines
t/test-lib-functions.sh +8
-0+test_expect_process_tree () { + depth= && + >actual && + cat >expect && + cat <&3 >expect.err + while test $# != 0 + do + case "$1" in
t/test-lib-functions.sh test_path_is_hidden () { · line 1645
Quoted 10 lines
t/test-lib-functions.sh +10
-0+ --depth) + depth="$2" + shift + ;; + *) + break + ;; + esac + shift + done &&
Do you have an example where this is being checked? Or can depth be left as 1 for now?
t/test-lib-functions.sh test_path_is_hidden () { · line 1658
Quoted 13 lines
t/test-lib-functions.sh +13
-0+ log="$(pwd)/proc-tree.txt" && + >"$log" && + GIT_TRACE2_PERF="$log" "$@" 2>actual.err && + grep "child_start" proc-tree.txt >proc-tree-start.txt || : && + if test -n "$depth" + then + grep " d$depth " proc-tree-start.txt >tmp.txt || : && + mv tmp.txt proc-tree-start.txt + fi && + sed -e 's/^.*argv:\[//' -e 's/\]$//' <proc-tree-start.txt >actual && + test_cmp expect actual && + test_cmp expect.err actual.err +} 7>&2 2>&4
I think similar ideas could apply to test_region. Giving it a try now.
-Stolee
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
Quoted 30 lines · Derrick Stolee
Quoted 10 lines · Ævar Arnfjörð Bjarmason
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Review
Quoted 1 line · Derrick Stolee via GitGitGadget
(quoted diff) +1
-0+ GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null &&I see this is following some established convention in the file, but is there really not a way to make this pass without directing stderr to
/dev/null? It makes ad-hoc debugging when reviewing harder.Quoted 3 lines
As I later found out this is copy/pasted to get around the fact that --quiet is dependent on
isatty(), so without this the result would be different under --verbose and non-verbose testing.Yes, adding --quiet directly is a better pattern.
Quoted 7 lines
So that dates back to
3ddaad0e060(maintenance: add --quiet option, 2020-09-17), but I see other quiet=isatty(2) in related code. I wish we could isolate that particular behavior so removing the 2>/dev/null when debugging the tests doesn't cause you to run into this, maybe an explicit --quiet or --no-quiet option for all but one test that's checking thatisatty()behavior?Quoted 5 lines
Quoted 3 lines
I tried just removing it, but then (in an earlier test case) the "
test_subcommand" fails because it can't find the line we're looking for, so us piping stderr to/dev/nullimpacts our trace2 output?I hadn't seen seen
test_subcommandbefore, sorry to be blunt, but "ew!".
Quoted 7 lines
Saying "I'm about to be rude" before being rude doesn't excuse it. I had to step away and get over this comment before I could examine the actually constructive feedback below.
A better way to communicate the same information could be "This test helper seems more complicated than necessary, and has some gaps that could be filled." I completely agree with this statement.
I didn't mean to be rude, my understanding of "blunt" is more like "forthright" or "without mincing words" or something to that effect, but I'm not a native speaker of English, so I'll take your understanding over mine and apologize, sorry.
What I meant to say is: When we look at some prior art in the test suite that does similar things this pattern is needlessly convoluted, and separates the command invocation from the test assert itself. Something similar-ish to check_sub_test_lib_test would allow you to run the command, but do any asserting of the proc tree in the helper.
Quoted 22 lines
Quoted 8 lines
So we're ad-hoc grepping trace2 JSON output just to find out whether we invoked some subcommand. But unlike
test_expect_codeetc. this one doesn't run git for you, but instead we have temp *.txt files and the command disconnected from the run.And because you're using "grep" and "! grep" to test, you're hiding the difference between "did not find this line" v.s. "did not find anything at all".
You're right that there is value in comparing the ordered list of subcommands run by a given Git command. That will catch buggy tests that are checking that a subcommand doesn't run.
Quoted 4 lines
Because of that the second test using
test_subcommandis either buggy or painfully non-obvious. We check that "run --auto" doesn't contain a "auto --quiet", but in reality it doesn't contain any subcommands at all. We didn't run any because it exited with "nothing to pack".That exit is from the third command, which does not pass the --auto command. The "nothing to pack" output is from the 'git gc --no-quiet' subcommand that is being checked in this third test.
Quoted 8 lines
Quoted 2 lines
I think converting the whole thing to something like the WIP/RFC patch below is much better and more readable.
This is an interesting approach. I don't see you using the ERR that you are inputting anywhere, so that seems like an unnecessary bloat to the consumers. But maybe I haven't discovered all of the places where this would be useful, but it seems better to pipe stderr to a file for later comparison when needed.
Yes, it's probably not a good default here. For the test-lib.sh tests there's check_sub_test_lib_test and check_sub_test_lib_test_err, most of the tests only test stdout.
Quoted 12 lines
Quoted 12 lines
t/test-lib-functions.sh +12
-0+test_expect_process_tree () { + depth= && + >actual && + cat >expect && + cat <&3 >expect.err + while test $# != 0 + do + case "$1" in + --depth) + depth="$2" + shift + ;;
Quoted 8 lines
t/test-lib-functions.sh test_path_is_hidden () { · line 1645
Quoted 6 lines
t/test-lib-functions.sh +6
-0+ *) + break + ;; + esac + shift + done &&Do you have an example where this is being checked? Or can depth be left as 1 for now?
It can probably be hardcoded, but I was hoping someone more familiar with trace2 would chime in, but I'm fairly sure there's not a way to do it without parsing the existing output with either some clever grep/awk-ing of the PERF output, or stateful parsing of the JSON.
I thought that for git maintenance tests perhaps something wanted to assert that we didn't have maintenance invoking maintenance, or that something expected to prune refs really invoked the relevant prune command via "gc".
Quoted 8 lines
Quoted 8 lines
t/test-lib-functions.sh +8
-0+ log="$(pwd)/proc-tree.txt" && + >"$log" && + GIT_TRACE2_PERF="$log" "$@" 2>actual.err && + grep "child_start" proc-tree.txt >proc-tree-start.txt || : && + if test -n "$depth" + then + grep " d$depth " proc-tree-start.txt >tmp.txt || : && + mv tmp.txt proc-tree-start.txt
Quoted 8 lines
t/test-lib-functions.sh test_path_is_hidden () { · line 1658
Quoted 5 lines
t/test-lib-functions.sh +5
-0+ fi && + sed -e 's/^.*argv:\[//' -e 's/\]$//' <proc-tree-start.txt >actual && + test_cmp expect actual && + test_cmp expect.err actual.err +} 7>&2 2>&4I think similar ideas could apply to
test_region. Giving it a try now.
Probably, I didn't even notice that one...
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
Quoted 65 lines · Ævar Arnfjörð Bjarmason
Quoted 9 lines · Derrick Stolee
Quoted 2 lines · Ævar Arnfjörð Bjarmason
I think converting the whole thing to something like the WIP/RFC patch below is much better and more readable.
This is an interesting approach. I don't see you using the ERR that you are inputting anywhere, so that seems like an unnecessary bloat to the consumers. But maybe I haven't discovered all of the places where this would be useful, but it seems better to pipe stderr to a file for later comparison when needed.
Yes, it's probably not a good default here. For the
test-lib.shtests there'scheck_sub_test_lib_testandcheck_sub_test_lib_test_err, most of the tests only test stdout.Quoted 12 lines
Quoted 12 lines
(quoted diff) +12
-0+test_expect_process_tree () { + depth= && + >actual && + cat >expect && + cat <&3 >expect.err + while test $# != 0 + do + case "$1" in + --depth) + depth="$2" + shift + ;;Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ *) + break + ;; + esac + shift + done &&Do you have an example where this is being checked? Or can depth be left as 1 for now?
It can probably be hardcoded, but I was hoping someone more familiar with trace2 would chime in, but I'm fairly sure there's not a way to do it without parsing the existing output with either some clever grep/awk-ing of the PERF output, or stateful parsing of the JSON.
I thought that for git maintenance tests perhaps something wanted to assert that we didn't have maintenance invoking maintenance, or that something expected to prune refs really invoked the relevant prune command via "gc".
Quoted 8 lines
Quoted 8 lines
(quoted diff) +8
-0+ log="$(pwd)/proc-tree.txt" && + >"$log" && + GIT_TRACE2_PERF="$log" "$@" 2>actual.err && + grep "child_start" proc-tree.txt >proc-tree-start.txt || : && + if test -n "$depth" + then + grep " d$depth " proc-tree-start.txt >tmp.txt || : && + mv tmp.txt proc-tree-start.txtQuoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ fi && + sed -e 's/^.*argv:\[//' -e 's/\]$//' <proc-tree-start.txt >actual && + test_cmp expect actual && + test_cmp expect.err actual.err +} 7>&2 2>&4I think similar ideas could apply to
test_region. Giving it a try now.
Quoted 1 line
Probably, I didn't even notice that one...
I gave this a few hours today, and I'm giving up. I'm the first to admit that I don't have the correct scripting skills to do some of these things.
I've got what I tried below. It certainly looks like it would work. It solves the problem of "what if the test is flaky?" by ensuring that all subcommands (at depth 0) match the inputs exactly.
However, the problem comes when trying to make that work for all of the maintenance tests, specifically the 'incremental-repack' task. That task dynamically computes a --batch-size=X parameter, and that is not stable across runs of the script.
This was avoided in the past by only checking for the first of three subcommands when verifying that the 'incremental-repack' task worked. That is, except for the EXPENSIVE test that checks that the --batch-size maxes out at 2g.
The thing that might make these changing parameters work is to allow the specified lines be a _prefix_ of the actual parameters. Or, let each line be a pattern that is checked against that line. Issues come up with how to handle this line-by-line check that I was unable to overcome.
The good news is that the idea of adding a '--prefetch' option to 'git fetch' makes the change to t7900-maintenance.sh much easier, making this change to test_subcommand less of a priority.
I include my attempt here as a patch. Feel free to take whatever you want of it, or none of it and start over. I do think that it makes the test script look much nicer.
Thanks,
-Stolee
-- >8 --
From 449d098f2a13860f44b2e6fb96fb2dd5872b511b Mon Sep 17 00:00:00 2001
From: Derrick Stolee <[email protected]>
Date: Fri, 9 Apr 2021 08:42:26 -0400
Subject: [PATCH 1/2] test-lib: add test_subcommands
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The test_subcommand helper in test-lib-functions.sh satisfied a need to check for certain subcommands in Git processes. This is especially needed in t7900-maintenance.sh to ensure that the maintenance builtin properly calls certain subcommands based on command-line options, config values, and the state of the repository.
However, test_subcommand has some complexities in its use that can be improved. First, it requires that the caller knows to create a log file with GIT_TRACE2_EVENT then supply that to test_subcommand. Further, it only checks that some exact subcommands exist or do not exist. It does not guarantee that the list of subcommands exactly matches a given list. Because of this drawback, the tests that check a subcommand does _not_ run are particularly flaky to slight changes in behavior.
Introduce a new helper, test_subcommands, that resolves these drawbacks:
1. It runs the supplied command and handles the trace log itself.
2. It takes a list of commands overs stdin and compares this to the complete list of subcommands run from the top-level command.
The helper does not test that the full subcommand tree matches, because that would cause tests to be too fragile to changes unrelated to the component being tested. This could easily be extended to allow the full tree with an option, if desired.
To ensure we only check the first level, use GIT_TRACE2_PERF output and scan for " d0 " in the rows that include the "child_start" event. The last column includes a way to scrape the subcommand itself from the trace. Sometimes arguments are quoted, such as when passing a refspec with '*' to the subcommand. This makes it difficult to create a matching string within the single-quoted test definitions, so strip these single quotes from the arguments before matching the input.
Only modify one test in t7900-maintenance.sh. The rest of the callers to test_subcommand will be converted to test_subcommands in a later change.
--- t/t7900-maintenance.sh | 16 +++++++--------- t/test-lib-functions.sh | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-)
t/t7900-maintenance.sh +7 -9
@@ -30,15 +30,13 @@ test_expect_success 'help text' ' ' test_expect_success 'run [--auto|--quiet]' ' - GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \ - git maintenance run 2>/dev/null && - GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \ - git maintenance run --auto 2>/dev/null && - GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \ - git maintenance run --no-quiet 2>/dev/null && - test_subcommand git gc --quiet <run-no-auto.txt && - test_subcommand ! git gc --auto --quiet <run-auto.txt && - test_subcommand git gc --no-quiet <run-no-quiet.txt + test_subcommands git maintenance run --quiet <<-EOF && + git gc --quiet + EOF + test_subcommands git maintenance run --auto --quiet </dev/null && + test_subcommands git maintenance run --no-quiet <<-EOF + git gc --no-quiet + EOF ' test_expect_success 'maintenance.auto config option' '
t/test-lib-functions.sh +24 -0
@@ -1658,6 +1658,30 @@ test_subcommand () { fi } +# Run a command and ensure it succeeds. Use the +# GIT_TRACE2_PERF logs to ensure that every subcommand +# run by this top-level Git process is exactly the +# set supplied over stdin. +# +# Redirects stderr to a file named "err". This can +# be used by tests, but it also provides consistent +# use of isatty(2) which can affect subcommand calls. +test_subcommands () { + local log line && + + cat >expect && + + log="$(pwd)"/subcommand-trace.txt && + + GIT_TRACE2_PERF="$log" "$@" 2>err && + grep "child_start" "$log" | grep " d0 " >processes || : && + + sed -e 's/^.*argv:\[//' -e 's/\]$//' -e "s/'//g" <processes >actual && + test_cmp expect actual && + + rm -f "$log" expect actual processes +} + # Check that the given command was invoked as part of the # trace2-format trace on stdin. #
Signature · 34 lines
2.31.1.vfs.0.0 --- >8 --- And here is the follow-up that attempts to change the rest of the tests in t7900-maintenance.sh. However, this leads to flaky tests because of the --batch-size changes. --- >8 --- From 61f142ba7fcde8ae2a84f18005f384a4544b7741 Mon Sep 17 00:00:00 2001 From: Derrick Stolee <[email protected]> Date: Fri, 9 Apr 2021 11:13:42 -0400 Subject: [PATCH 2/2] t7900: convert to test_subcommands Replace the remaining uses of test_subcommand with test_subcommands, allowing us to delete the old helper. Most of these replacements are straightforward. However, some are a bit more subtle, specifically because we now are checking the full ordered set of subcommands. Some places we were only testing one of multiple subcommands that would be run by a task. These are expanded to include the full set. When working with the prefetch task, the refspecs that are passed to the subcommand are normally quoted with single quotes in the GIT_TRACE2_PERF output, but those characters are removed in test_subcommands, allowing our tests in t7900-maintenance.sh to avoid escaping such quotes. Signed-off-by: Derrick Stolee <[email protected]> --- t/t7900-maintenance.sh | 285 +++++++++++++++++++--------------------- t/test-lib-functions.sh | 33 ----- 2 files changed, 133 insertions(+), 185 deletions(-)
t/t7900-maintenance.sh +133 -152
378 lines not on this page · raw message
t/test-lib-functions.sh +0 -33
@@ -1625,39 +1625,6 @@ test_path_is_hidden () { return 1 } -# Check that the given command was invoked as part of the -# trace2-format trace on stdin. -# -# test_subcommand [!] <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 git upload-pack "$PATH" <event.log -# -# If the first parameter passed is !, this instead checks that -# the given command was not called. -# -test_subcommand () { - 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 -} - # Run a command and ensure it succeeds. Use the # GIT_TRACE2_PERF logs to ensure that every subcommand # run by this top-level Git process is exactly the
Signature · 1 line
2.31.1.vfs.0.0
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
Quoted 68 lines · Derrick Stolee
Quoted 65 lines · Ævar Arnfjörð Bjarmason
Quoted 9 lines · Derrick Stolee
Quoted 2 lines · Ævar Arnfjörð Bjarmason
I think converting the whole thing to something like the WIP/RFC patch below is much better and more readable.
This is an interesting approach. I don't see you using the ERR that you are inputting anywhere, so that seems like an unnecessary bloat to the consumers. But maybe I haven't discovered all of the places where this would be useful, but it seems better to pipe stderr to a file for later comparison when needed.
Yes, it's probably not a good default here. For the
test-lib.shtests there'scheck_sub_test_lib_testandcheck_sub_test_lib_test_err, most of the tests only test stdout.Quoted 12 lines
Quoted 12 lines
(quoted diff) +12
-0+test_expect_process_tree () { + depth= && + >actual && + cat >expect && + cat <&3 >expect.err + while test $# != 0 + do + case "$1" in + --depth) + depth="$2" + shift + ;;Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ *) + break + ;; + esac + shift + done &&Do you have an example where this is being checked? Or can depth be left as 1 for now?
It can probably be hardcoded, but I was hoping someone more familiar with trace2 would chime in, but I'm fairly sure there's not a way to do it without parsing the existing output with either some clever grep/awk-ing of the PERF output, or stateful parsing of the JSON.
I thought that for git maintenance tests perhaps something wanted to assert that we didn't have maintenance invoking maintenance, or that something expected to prune refs really invoked the relevant prune command via "gc".
Quoted 8 lines
Quoted 8 lines
(quoted diff) +8
-0+ log="$(pwd)/proc-tree.txt" && + >"$log" && + GIT_TRACE2_PERF="$log" "$@" 2>actual.err && + grep "child_start" proc-tree.txt >proc-tree-start.txt || : && + if test -n "$depth" + then + grep " d$depth " proc-tree-start.txt >tmp.txt || : && + mv tmp.txt proc-tree-start.txtQuoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ fi && + sed -e 's/^.*argv:\[//' -e 's/\]$//' <proc-tree-start.txt >actual && + test_cmp expect actual && + test_cmp expect.err actual.err +} 7>&2 2>&4I think similar ideas could apply to
test_region. Giving it a try now.Quoted 1 line
Probably, I didn't even notice that one...
Quoted 7 lines
I gave this a few hours today, and I'm giving up. I'm the first to admit that I don't have the correct scripting skills to do some of these things.
I've got what I tried below. It certainly looks like it would work. It solves the problem of "what if the test is flaky?" by ensuring that all subcommands (at depth 0) match the inputs exactly.
Looks good!
Quoted 16 lines
However, the problem comes when trying to make that work for all of the maintenance tests, specifically the 'incremental-repack' task. That task dynamically computes a --batch-size=X parameter, and that is not stable across runs of the script.
This was avoided in the past by only checking for the first of three subcommands when verifying that the 'incremental-repack' task worked. That is, except for the EXPENSIVE test that checks that the --batch-size maxes out at 2g.
The thing that might make these changing parameters work is to allow the specified lines be a _prefix_ of the actual parameters. Or, let each line be a pattern that is checked against that line. Issues come up with how to handle this line-by-line check that I was unable to overcome.
Quoted 7 lines
The good news is that the idea of adding a '--prefetch' option to 'git fetch' makes the change to
t7900-maintenance.shmuch easier, making this change totest_subcommandless of a priority.I include my attempt here as a patch. Feel free to take whatever you want of it, or none of it and start over. I do think that it makes the test script look much nicer.
I think a good way to deal with that is to have a lower-level helper function that doesn't do the test_cmp, and instead just runs the command, and leaves the stdout/stderr files in-place for another "check" helper.
On a WIP topic I split up the t0000-basic.sh "test test-lib.sh itself" code to do pretty much that:
https://github.com/avar/git/blob/avar/support-test-verbose-under-prove-2/t/lib-subtest.sh
It's basically back to the existing model of "run a command and grep it later", except that we can pass the JSON through some basic parser, and extract common cases like test_cmp, prefix munging before test_cmp etc.
Re: [PATCH 5/5] maintenance: allow custom refspecs during prefetch
In reply to Derrick Stolee via GitGitGadgetQuoted 5 lines · Derrick Stolee via GitGitGadget
This changes the names of the refs that would be fetched by the default refspec. Instead of "refs/prefetch/<remote>/<branch>" they will now go to "refs/prefetch/remotes/<remote>/<branch>". While this is a change, it is not a seriously breaking one: these refs are intended to be hidden and not used.
Not "a seriously breaking one" just because we'll assume nobody had a remote they'd named "remotes" and they'll need to manually clean that mess up (if needed), or ...?
Documentation/git-maintenance.txt prefetch:: · line 97
Quoted 7 lines
[...]
Documentation/git-maintenance.txt +1
-2objects 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. + refs are stored in `refs/prefetch/`. Also, tags are not updated.
So, "tags are not updated", but:
Quoted 23 lines
builtin/gc.c +21
-1-strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); +for (i =0 ; i < remote->fetch.nr; i++) { +struct refspec_item replace; +struct refspec_item *rsi = &remote->fetch.items[i]; +struct strbuf new_dst =STRBUF_INIT ; +size_t ignore_len =0 ; + +if (rsi->negative) { +strvec_push (&child.args, remote->fetch.raw[i]); +continue ; + } + +refspec_item_init (&replace, remote->fetch.raw[i],1 ); + +/* +* If a refspec dst starts with "refs/" at the start, +* then we will replace "refs/" with "refs/prefetch/". +* Otherwise, we will prepend the dst string with +* "refs/prefetch/". +*/ +if (!strncmp (replace.dst,"refs/" ,5 )) + ignore_len =5 ;
builtin/gc.c static int fetch_remote(struct remote *remote, void *cbdata) · line 921
Quoted 10 lines
builtin/gc.c +10
-0+ +strbuf_addstr (&new_dst,"refs/prefetch/" ); +strbuf_addstr (&new_dst, replace.dst + ignore_len); +free (replace.dst); + replace.dst =strbuf_detach (&new_dst,NULL ); + +strvec_push (&child.args,refspec_item_format (&replace)); + +refspec_item_clear (&replace); + }
Isn't a blanket replacement of refs/heads/* with refs/* going to change that? I haven't tested this so maybe it still doesn't work, but:
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 169
Quoted 9 lines
t/t7900-maintenance.sh +3
-2GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote1/* <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote2/* <run-prefetch.txt && + fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
It seems we should at least have a test for the case of having a refspec that pulls down tags.
I suspect that we could document this as an absolute before, as refs/heads/* is the only namespace that'll refuse tags, but now that we fetch refs/<whatever> that'll no longer be the case.
I'd think that this new behavior (if I'm right) is a feature, but that we need to update the docs/tests appropriately.
v2 cover Maintenance: adapt custom refspecs
In reply to Derrick Stolee via GitGitGadgetTom Saeger rightly pointed out [1] that the prefetch task ignores custom refspecs. This can lead to downloading more data than requested, and it doesn't even help the future foreground fetches that use that custom refspec.
[1]
https://lore.kernel.org/git/20210401184914.qmr7jhjbhp2mt3h6@dhcp-10-154-148-175.vpn.oracle.com/
This series fixes this problem by carefully replacing the start of each refspec's destination with "refs/prefetch/". If the destination already starts with "refs/", then that is replaced. Otherwise "refs/prefetch/" is just prepended.
In order to accomplish this safely, a new refspec_item_format() method is created and tested.
Patch 1 is just a preparation patch that makes the code simpler (and in hindsight it should have been written this way from the start).
Patch 2 is a simplification of test_subcommand that removes the need for escaping glob characters. Thanks, Eric Sunshine, for the tip of why my tests were failing on FreeBSD.
Patches 3-4 add refspec_item_format().
Patch 5 finally modifies the logic in the prefetch task to translate these refspecs.
Updates in V2
=============
Thanks for the close eye on this series. I appreciate the recommendations, which I believe I have responded to them all:
* Fixed typos.
* Made refspec_item_format() re-entrant. Consumers must free the buffer.
* Cleaned up style (quoting and tabbing).
Thanks, -Stolee
Derrick Stolee (5):
maintenance: simplify prefetch logic
test-lib: use exact match for test_subcommand
refspec: output a refspec item
test-tool: test refspec input/output
maintenance: allow custom refspecs during prefetch
Documentation/git-maintenance.txt | 3 +- Makefile | 1 + builtin/gc.c | 66 ++++++++++++++++++++----------- refspec.c | 23 +++++++++++ refspec.h | 2 + t/helper/test-refspec.c | 44 +++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t5511-refspec.sh | 41 +++++++++++++++++++ t/t7900-maintenance.sh | 43 +++++++++++++++++--- t/test-lib-functions.sh | 4 +- 11 files changed, 195 insertions(+), 34 deletions(-) create mode 100644 t/helper/test-refspec.c
base-commit: 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-924%2Fderrickstolee%2Fmaintenance%2Frefspec-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-924/derrickstolee/maintenance/refspec-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/924
Range-diff vs v1:
1: 3a94ff80657c ! 1: 5aa0cb06c3f2 maintenance: simplify prefetch logic
@@ Commit message
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also
- becuase it sets up an upcoming change to use the remote's refspec. This
+ because it sets up an upcoming change to use the remote's refspec. This
data is accessible from the 'struct remote' data that is now accessible in fetch_remote().
2: 2b74889c2a32 ! 2: d58a3e042ee8 test-lib: use exact match for test_subcommand
@@ Commit message
The use of 'grep' inside test_subcommand uses general patterns, leading to sometimes needing escape characters to avoid incorrect matches.
- Further, some platforms interpret different glob characters differently.
+ Further, some platforms interpret regular expression metacharacters
+ differently. Furthermore, it can be difficult to know which characters
+ need escaping since the actual regular expression language implemented
+ by various greps differs between platforms; for instance, some may
+ employ pure BRE, whereas others a mix of BRE & ERE.
- Use 'grep -F' to use an exact match. This requires removing escape
- characters from existing callers. Luckily, this is only one test that
- expects refspecs as part of the subcommand.
+ Sidestep this difficulty by using grep -F to use an exact match. This
+ requires removing escape characters from existing callers. Luckily,
+ this is only one test that expects refspecs as part of the subcommand.
- Reported-by: Eric Sunshine <[email protected]>
+ Helped-by: Eric Sunshine <[email protected]>
## t/t7900-maintenance.sh ##
@@ t/t7900-maintenance.sh: test_expect_success 'prefetch multiple remotes' '
fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
- test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt &&
- test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt &&
-+ test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt &&
-+ test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt &&
++ test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remote1/*" <run-prefetch.txt &&
++ test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remote2/*" <run-prefetch.txt &&
test_path_is_missing .git/refs/remotes &&
git log prefetch/remote1/one &&
git log prefetch/remote2/two &&
3: e10007e1cf8f ! 3: 96388d949b98 refspec: output a refspec item
@@ refspec.c: void refspec_item_clear(struct refspec_item *item)
item->exact_sha1 = 0;
}
-+const char *refspec_item_format(const struct refspec_item *rsi)
++char *refspec_item_format(const struct refspec_item *rsi)
+{
-+ static struct strbuf buf = STRBUF_INIT;
-+
-+ strbuf_reset(&buf);
++ struct strbuf buf = STRBUF_INIT;
+
+ if (rsi->matching)
-+ return ":";
++ return xstrdup(":");
+
+ if (rsi->negative)
+ strbuf_addch(&buf, '^');
@@ refspec.c: void refspec_item_clear(struct refspec_item *item)
+ strbuf_addstr(&buf, rsi->dst);
+ }
+
-+ return buf.buf;
++ return strbuf_detach(&buf, NULL);
+}
+
void refspec_init(struct refspec *rs, int fetch)
@@ refspec.h: int refspec_item_init(struct refspec_item *item, const char *refspec,
void refspec_item_init_or_die(struct refspec_item *item, const char *refspec,
int fetch);
void refspec_item_clear(struct refspec_item *item);
-+/*
-+ * Output a given refspec item to a string.
-+ */
-+const char *refspec_item_format(const struct refspec_item *rsi);
++char *refspec_item_format(const struct refspec_item *rsi);
+
void refspec_init(struct refspec *rs, int fetch);
void refspec_append(struct refspec *rs, const char *refspec);
4: c8d1de06f844 ! 4: bf296282323a test-tool: test refspec input/output
@@ t/helper/test-refspec.c (new)
+
+ while (strbuf_getline(&line, stdin) != EOF) {
+ struct refspec_item rsi;
++ char *buf;
+
+ if (!refspec_item_init(&rsi, line.buf, fetch)) {
+ printf("failed to parse %s\n", line.buf);
+ continue;
+ }
+
-+ printf("%s\n", refspec_item_format(&rsi));
++ buf = refspec_item_format(&rsi);
++ printf("%s\n", buf);
++ free(buf);
++
+ refspec_item_clear(&rsi);
+ }
+
++ strbuf_release(&line);
+ return 0;
+}
@@ t/t5511-refspec.sh: test_refspec fetch "refs/heads/${good}"
+test_expect_success 'test input/output round trip' '
+ cat >input <<-\EOF &&
-+ +refs/heads/*:refs/remotes/origin/*
-+ refs/heads/*:refs/remotes/origin/*
-+ refs/heads/main:refs/remotes/frotz/xyzzy
-+ :refs/remotes/frotz/deleteme
-+ ^refs/heads/secrets
-+ refs/heads/secret:refs/heads/translated
-+ refs/heads/secret:heads/translated
-+ refs/heads/secret:remotes/translated
-+ secret:translated
-+ refs/heads/*:remotes/xxy/*
-+ refs/heads*/for-linus:refs/remotes/mine/*
-+ 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed
-+ HEAD
-+ @
-+ :
++ +refs/heads/*:refs/remotes/origin/*
++ refs/heads/*:refs/remotes/origin/*
++ refs/heads/main:refs/remotes/frotz/xyzzy
++ :refs/remotes/frotz/deleteme
++ ^refs/heads/secrets
++ refs/heads/secret:refs/heads/translated
++ refs/heads/secret:heads/translated
++ refs/heads/secret:remotes/translated
++ secret:translated
++ refs/heads/*:remotes/xxy/*
++ refs/heads*/for-linus:refs/remotes/mine/*
++ 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed
++ HEAD
++ @
++ :
+ EOF
+ cat >expect <<-\EOF &&
-+ +refs/heads/*:refs/remotes/origin/*
-+ refs/heads/*:refs/remotes/origin/*
-+ refs/heads/main:refs/remotes/frotz/xyzzy
-+ :refs/remotes/frotz/deleteme
-+ ^refs/heads/secrets
-+ refs/heads/secret:refs/heads/translated
-+ refs/heads/secret:heads/translated
-+ refs/heads/secret:remotes/translated
-+ secret:translated
-+ refs/heads/*:remotes/xxy/*
-+ refs/heads*/for-linus:refs/remotes/mine/*
-+ 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed
-+ HEAD
-+ HEAD
-+ :
++ +refs/heads/*:refs/remotes/origin/*
++ refs/heads/*:refs/remotes/origin/*
++ refs/heads/main:refs/remotes/frotz/xyzzy
++ :refs/remotes/frotz/deleteme
++ ^refs/heads/secrets
++ refs/heads/secret:refs/heads/translated
++ refs/heads/secret:heads/translated
++ refs/heads/secret:remotes/translated
++ secret:translated
++ refs/heads/*:remotes/xxy/*
++ refs/heads*/for-linus:refs/remotes/mine/*
++ 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed
++ HEAD
++ HEAD
++ :
+ EOF
+ test-tool refspec <input >output &&
+ test_cmp expect output &&
5: 7f6c127dac48 ! 5: 9592224e3d42 maintenance: allow custom refspecs during prefetch
@@ builtin/gc.c: static int fetch_remote(struct remote *remote, void *cbdata)
+ struct refspec_item *rsi = &remote->fetch.items[i];
+ struct strbuf new_dst = STRBUF_INIT;
+ size_t ignore_len = 0;
++ char *replace_string;
+
+ if (rsi->negative) {
+ strvec_push(&child.args, remote->fetch.raw[i]);
@@ builtin/gc.c: static int fetch_remote(struct remote *remote, void *cbdata)
+ free(replace.dst);
+ replace.dst = strbuf_detach(&new_dst, NULL);
+
-+ strvec_push(&child.args, refspec_item_format(&replace));
++ replace_string = refspec_item_format(&replace);
++ strvec_push(&child.args, replace_string);
++ free(replace_string);
+
+ refspec_item_clear(&replace);
+ }
@@ t/t7900-maintenance.sh: test_expect_success 'prefetch multiple remotes' '
test_commit -C clone2 two &&
GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
-- test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remote1/* <run-prefetch.txt &&
-- test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remote2/* <run-prefetch.txt &&
-+ test_subcommand git fetch remote1 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote1/* <run-prefetch.txt &&
-+ test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote2/* <run-prefetch.txt &&
+- test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remote1/*" <run-prefetch.txt &&
+- test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remote2/*" <run-prefetch.txt &&
++ test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote1/*" <run-prefetch.txt &&
++ test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote2/*" <run-prefetch.txt &&
test_path_is_missing .git/refs/remotes &&
- git log prefetch/remote1/one &&
- git log prefetch/remote2/two &&
@@ t/t7900-maintenance.sh: test_expect_success 'prefetch multiple remotes' '
+ git -C clone1 branch -f special/secret/not-fetched HEAD &&
+
+ # create multiple refspecs for remote1
-+ git config --add remote.remote1.fetch +refs/heads/special/fetched:refs/heads/fetched &&
-+ git config --add remote.remote1.fetch ^refs/heads/special/secret/not-fetched &&
++ git config --add remote.remote1.fetch "+refs/heads/special/fetched:refs/heads/fetched" &&
++ git config --add remote.remote1.fetch "^refs/heads/special/secret/not-fetched" &&
+
+ GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null &&
+
@@ t/t7900-maintenance.sh: test_expect_success 'prefetch multiple remotes' '
+ rs2="+refs/heads/special/fetched:refs/prefetch/heads/fetched" &&
+ rs3="^refs/heads/special/secret/not-fetched" &&
+
-+ test_subcommand git fetch remote1 $fetchargs $rs1 $rs2 $rs3 <prefetch-refspec.txt &&
-+ test_subcommand git fetch remote2 $fetchargs +refs/heads/*:refs/prefetch/remotes/remote2/* <prefetch-refspec.txt &&
++ test_subcommand git fetch remote1 $fetchargs "$rs1" "$rs2" "$rs3" <prefetch-refspec.txt &&
++ test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote2/*" <prefetch-refspec.txt &&
+
+ # first refspec is overridden by second
+ test_must_fail git rev-parse refs/prefetch/special/fetched &&Signature · 1 line
gitgitgadget
PATCH v2 1/5 maintenance: simplify prefetch logic
From: Derrick Stolee <[email protected]>
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also because it sets up an upcoming change to use the remote's refspec. This data is accessible from the 'struct remote' data that is now accessible in fetch_remote().
--- builtin/gc.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-)
builtin/gc.c +8 -25
@@ -873,55 +873,38 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)return 0 ; } -static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" ,"--refmap=" ,NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote); +strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name);return !!run_command (&child); } -static int append_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 (struct maintenance_run_opts *opts) { -int result =0 ; -struct string_list_item *item; -struct string_list remotes =STRING_LIST_INIT_DUP ; -git_config_set_multivar_gently ("log.excludedecoration" ,"refs/prefetch/" ,"refs/prefetch/" ,CONFIG_FLAGS_FIXED_VALUE |CONFIG_FLAGS_MULTI_REPLACE ); -if (for_each_remote (append_remote, &remotes)) { -error (_ ("failed to fill remotes" )); - result =1 ; - goto cleanup; +if (for_each_remote (fetch_remote, opts)) { +error (_ ("failed to prefetch remotes" )); +return 1 ; } -for_each_string_list_item (item, &remotes) - result |=fetch_remote (item->string, opts); - -cleanup: -string_list_clear (&remotes,0 ); -return result; +return 0 ; }static int maintenance_task_gc (struct maintenance_run_opts *opts)
Signature · 1 line
gitgitgadget
Re: [PATCH v2 1/5] maintenance: simplify prefetch logic
Overall, the series looks pretty solid to me - which is why I've got a handful of small nits to relay. :)
Quoted 4 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +2
-1-static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;
[snip]
Quoted 1 line
if (
opts->quiet)
I worry about the lack of null-checking here.
- Emily
Re: [PATCH v2 1/5] maintenance: simplify prefetch logic
Quoted 11 lines · Emily Shaffer
Overall, the series looks pretty solid to me - which is why I've got a handful of small nits to relay. :)
Quoted 4 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +2
-1-static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;[snip]
Quoted 1 line
if (
opts->quiet)I worry about the lack of null-checking here.
If this was a general-purpose method, I'd agree with you. But since this is a static method being called exactly once (and always passing a non-NULL value) then I don't believe that NULL check is valuable here.
Thanks,
-Stolee
PATCH v2 2/5 test-lib: use exact match for test_subcommand
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The use of 'grep' inside test_subcommand uses general patterns, leading to sometimes needing escape characters to avoid incorrect matches. Further, some platforms interpret regular expression metacharacters differently. Furthermore, it can be difficult to know which characters need escaping since the actual regular expression language implemented by various greps differs between platforms; for instance, some may employ pure BRE, whereas others a mix of BRE & ERE.
Sidestep this difficulty by using grep -F to use an exact match. This requires removing escape characters from existing callers. Luckily, this is only one test that expects refspecs as part of the subcommand.
--- t/t7900-maintenance.sh | 4 ++-- t/test-lib-functions.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-)
t/t7900-maintenance.sh +2 -2
@@ -142,8 +142,8 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remote1/*" <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remote2/*" <run-prefetch.txt && test_path_is_missing .git/refs/remotes && git log prefetch/remote1/one && git log prefetch/remote2/two &&
t/test-lib-functions.sh +2 -2
@@ -1652,9 +1652,9 @@ test_subcommand () { if test -n "$negate" then - ! grep "\[$expr\]" + ! grep -F "[$expr]" else - grep "\[$expr\]" + grep -F "[$expr]" fi }
Signature · 1 line
gitgitgadget
PATCH v2 3/5 refspec: output a refspec item
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Add a new method, refspec_item_format(), that takes a 'struct refspec_item' pointer as input and returns a string for how that refspec item should be written to Git's config or a subcommand, such as 'git fetch'.
There are several subtleties regarding special-case refspecs that can occur and are represented in t5511-refspec.sh. These cases will be explored in new tests in the following change. It requires adding a new test helper in order to test this format directly, so that is saved for a separate change to keep this one focused on the logic of the format method.
A future change will consume this method when translating refspecs in the 'prefetch' task of the 'git maintenance' builtin.
--- refspec.c | 23 +++++++++++++++++++++++ refspec.h | 2 ++ 2 files changed, 25 insertions(+)
refspec.c +23 -0
@@ -180,6 +180,29 @@ void refspec_item_clear(struct refspec_item *item) item->exact_sha1 =0 ; } +char *refspec_item_format (const struct refspec_item *rsi) +{ +struct strbuf buf =STRBUF_INIT ; + +if (rsi->matching) +return xstrdup (":" ); + +if (rsi->negative) +strbuf_addch (&buf,'^' ); +else if (rsi->force) +strbuf_addch (&buf,'+' ); + +if (rsi->src) +strbuf_addstr (&buf, rsi->src); + +if (rsi->dst) { +strbuf_addch (&buf,':' ); +strbuf_addstr (&buf, rsi->dst); + } + +return strbuf_detach (&buf,NULL ); +} +void refspec_init (struct refspec *rs,int fetch) {memset (rs,0 ,sizeof (*rs));
refspec.h +2 -0
@@ -56,6 +56,8 @@ int refspec_item_init(struct refspec_item *item, const char *refspec,void refspec_item_init_or_die (struct refspec_item *item,const char *refspec,int fetch);void refspec_item_clear (struct refspec_item *item); +char *refspec_item_format (const struct refspec_item *rsi); +void refspec_init (struct refspec *rs,int fetch);void refspec_append (struct refspec *rs,const char *refspec); __attribute__((format (printf,2,3)))
Signature · 1 line
gitgitgadget
PATCH v2 4/5 test-tool: test refspec input/output
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
Add a new test-helper, 'test-tool refspec', that currently reads stdin line-by-line and translates the refspecs using the parsing logic of refspec_item_init() and writes them to output.
Create a test in t5511-refspec.sh that uses this helper to test several known special cases. This includes all of the special properties of the 'struct refspec_item', including:
* force: The refspec starts with '+'.
* pattern: Each side of the refspec has a glob character ('*')
* matching: The refspec is simply the string ":".
* exact_sha1: The 'src' string is a 40-character hex string.
* negative: The refspec starts with '^' and 'dst' is NULL.
While the exact_sha1 property doesn't require special logic in refspec_item_format, it is still tested here for completeness.
There is also the special-case refspec "@" which translates to "HEAD".
Note that if a refspec does not start with "refs/", then that is not incorporated as part of the 'struct refspec_item'. This behavior is confirmed by these tests. These refspecs still work in the wild because the refs layer interprets them appropriately as branches, prepending "refs/" or "refs/heads/" as necessary. I spent some time attempting to insert these prefixes explicitly in parse_refspec(), but these are several subtleties I was unable to overcome. If such a change were to be made, then this new test in t5511-refspec.sh will need to be updated with new output. For example, the input lines ending with "translated" are designed to demonstrate these subtleties.
--- Makefile | 1 + t/helper/test-refspec.c | 44 +++++++++++++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t5511-refspec.sh | 41 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 t/helper/test-refspec.c
Makefile +1 -0
@@ -734,6 +734,7 @@ TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o TEST_BUILTINS_OBJS += test-read-graph.o TEST_BUILTINS_OBJS += test-read-midx.o +TEST_BUILTINS_OBJS += test-refspec.o TEST_BUILTINS_OBJS += test-ref-store.o TEST_BUILTINS_OBJS += test-regex.o TEST_BUILTINS_OBJS += test-repository.o
t/helper/test-refspec.c new file +44 -0
44 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -55,6 +55,7 @@ static struct test_cmd cmds[] = { { "read-cache", cmd__read_cache }, { "read-graph", cmd__read_graph }, { "read-midx", cmd__read_midx }, + { "refspec", cmd__refspec }, { "ref-store", cmd__ref_store }, { "regex", cmd__regex }, { "repository", cmd__repository },
t/helper/test-tool.h +1 -0
@@ -44,6 +44,7 @@ int cmd__reach(int argc, const char **argv);int cmd__read_cache (int argc,const char **argv);int cmd__read_graph (int argc,const char **argv);int cmd__read_midx (int argc,const char **argv); +int cmd__refspec (int argc,const char **argv);int cmd__ref_store (int argc,const char **argv);int cmd__regex (int argc,const char **argv);int cmd__repository (int argc,const char **argv);
t/t5511-refspec.sh +41 -0
45 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v2 4/5] test-tool: test refspec input/output
Quoted 69 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
Add a new test-helper, 'test-tool refspec', that currently reads stdin line-by-line and translates the refspecs using the parsing logic of
refspec_item_init()and writes them to output.Create a test in
t5511-refspec.shthat uses this helper to test several known special cases. This includes all of the special properties of the 'structrefspec_item', including:* force: The refspec starts with '+'.
* pattern: Each side of the refspec has a glob character ('*')
* matching: The refspec is simply the string ":".
*exact_sha1: The 'src' string is a 40-character hex string.
* negative: The refspec starts with '^' and 'dst' is NULL.While the
exact_sha1property doesn't require special logic inrefspec_item_format, it is still tested here for completeness.There is also the special-case refspec "@" which translates to "HEAD".
Note that if a refspec does not start with "refs/", then that is not incorporated as part of the 'struct
refspec_item'. This behavior is confirmed by these tests. These refspecs still work in the wild because the refs layer interprets them appropriately as branches, prepending "refs/" or "refs/heads/" as necessary. I spent some time attempting to insert these prefixes explicitly inparse_refspec(), but these are several subtleties I was unable to overcome. If such a change were to be made, then this new test int5511-refspec.shwill need to be updated with new output. For example, the input lines ending with "translated" are designed to demonstrate these subtleties.Signed-off-by Derrick Stolee <[email protected]>--- Makefile | 1 + t/helper/test-refspec.c | 44 +++++++++++++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t5511-refspec.sh | 41 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 t/helper/test-refspec.cMakefile +1
-0@@ -734,6 +734,7 @@ TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o TEST_BUILTINS_OBJS += test-read-graph.o TEST_BUILTINS_OBJS += test-read-midx.o +TEST_BUILTINS_OBJS += test-refspec.o TEST_BUILTINS_OBJS += test-ref-store.o TEST_BUILTINS_OBJS += test-regex.o TEST_BUILTINS_OBJS += test-repository.ot/helper/test-refspec.c new file +9
-0@@ -0,0 +1,44 @@ +#include "cache.h" +#include "parse-options.h" +#include "refspec.h" +#include "strbuf.h" +#include "test-tool.h" + +static const char *const refspec_usage[] = { +N_ ("test-tool refspec [--fetch]" ), +NULL
t/helper/test-refspec.c line 19
Quoted 10 lines
t/helper/test-refspec.c new file +10
-0+}; + +int cmd__refspec (int argc,const char **argv) +{ +struct strbuf line =STRBUF_INIT ; +int fetch =0 ; + +struct option refspec_options [] = { +OPT_BOOL (0 ,"fetch" , &fetch, +N_ ("enable the 'fetch' option for parsing refpecs" )),
Typo here: s/refpecs/refspecs/
Quoted 102 lines
t/helper/test-refspec.c new file +25
-0+ OPT_END() + }; + + argc =parse_options (argc, argv,NULL , refspec_options, + refspec_usage,0 ); + +while (strbuf_getline (&line, stdin) !=EOF ) { +struct refspec_item rsi; +char *buf; + +if (!refspec_item_init (&rsi, line.buf, fetch)) { +printf ("failed to parse %s\n" , line.buf); +continue ; + } + + buf =refspec_item_format (&rsi); +printf ("%s\n" , buf); +free (buf); + +refspec_item_clear (&rsi); + } + +strbuf_release (&line); +return 0 ; +}t/helper/test-tool.c +1
-0@@ -55,6 +55,7 @@ static struct test_cmd cmds[] = { { "read-cache", cmd__read_cache }, { "read-graph", cmd__read_graph }, { "read-midx", cmd__read_midx }, + { "refspec", cmd__refspec }, { "ref-store", cmd__ref_store }, { "regex", cmd__regex }, { "repository", cmd__repository },t/helper/test-tool.h +1
-0@@ -44,6 +44,7 @@ int cmd__reach(int argc, const char **argv);int cmd__read_cache (int argc,const char **argv);int cmd__read_graph (int argc,const char **argv);int cmd__read_midx (int argc,const char **argv); +int cmd__refspec (int argc,const char **argv);int cmd__ref_store (int argc,const char **argv);int cmd__regex (int argc,const char **argv);int cmd__repository (int argc,const char **argv);t/t5511-refspec.sh +41
-0@@ -93,4 +93,45 @@ test_refspec fetch "refs/heads/${good}" bad=$(printf '\011tab') test_refspec fetch "refs/heads/${bad}" invalid +test_expect_success 'test input/output round trip' ' + cat >input <<-\EOF && + +refs/heads/*:refs/remotes/origin/* + refs/heads/*:refs/remotes/origin/* + refs/heads/main:refs/remotes/frotz/xyzzy + :refs/remotes/frotz/deleteme + ^refs/heads/secrets + refs/heads/secret:refs/heads/translated + refs/heads/secret:heads/translated + refs/heads/secret:remotes/translated + secret:translated + refs/heads/*:remotes/xxy/* + refs/heads*/for-linus:refs/remotes/mine/* + 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed + HEAD + @ + : + EOF + cat >expect <<-\EOF && + +refs/heads/*:refs/remotes/origin/* + refs/heads/*:refs/remotes/origin/* + refs/heads/main:refs/remotes/frotz/xyzzy + :refs/remotes/frotz/deleteme + ^refs/heads/secrets + refs/heads/secret:refs/heads/translated + refs/heads/secret:heads/translated + refs/heads/secret:remotes/translated + secret:translated + refs/heads/*:remotes/xxy/* + refs/heads*/for-linus:refs/remotes/mine/* + 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed + HEAD + HEAD + : + EOF + test-tool refspec <input >output && + test_cmp expect output && + test-tool refspec --fetch <input >output && + test_cmp expect output +' + test_doneSignature · 1 line
gitgitgadget
Re: [PATCH v2 4/5] test-tool: test refspec input/output
In reply to Derrick Stolee via GitGitGadgetQuoted 33 lines · Derrick Stolee via GitGitGadget
t/t5511-refspec.sh +25
-0@@ -93,4 +93,45 @@ test_refspec fetch "refs/heads/${good}" bad=$(printf '\011tab') test_refspec fetch "refs/heads/${bad}" invalid +test_expect_success 'test input/output round trip' ' + cat >input <<-\EOF && + +refs/heads/*:refs/remotes/origin/* + refs/heads/*:refs/remotes/origin/* + refs/heads/main:refs/remotes/frotz/xyzzy + :refs/remotes/frotz/deleteme + ^refs/heads/secrets + refs/heads/secret:refs/heads/translated + refs/heads/secret:heads/translated + refs/heads/secret:remotes/translated + secret:translated + refs/heads/*:remotes/xxy/* + refs/heads*/for-linus:refs/remotes/mine/* + 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed + HEAD + @ + : + EOF + cat >expect <<-\EOF && + +refs/heads/*:refs/remotes/origin/* + refs/heads/*:refs/remotes/origin/* + refs/heads/main:refs/remotes/frotz/xyzzy + :refs/remotes/frotz/deleteme + ^refs/heads/secrets + refs/heads/secret:refs/heads/translated
t/t5511-refspec.sh test_refspec fetch "refs/heads/${good}" · line 130
Quoted 10 lines
t/t5511-refspec.sh +10
-0+ refs/heads/secret:heads/translated + refs/heads/secret:remotes/translated + secret:translated + refs/heads/*:remotes/xxy/* + refs/heads*/for-linus:refs/remotes/mine/* + 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48:refs/heads/fixed + HEAD + HEAD + : + EOF
I don't like these expect/actual here. They are almost entirely identical, which means that the reader either A) spends a toilsome few minutes checking every single line to be sure they are not identical, or
B) reads the first three lines, decides they're the same, and misses the @->HEAD special case.
Why not instead run the test once for all the lines which should be the same before and after the parse, and again for all the lines which should differ, to reduce burden on the reader?
- Emily
PATCH v2 5/5 maintenance: allow custom refspecs during prefetch
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The prefetch task previously used the default refspec source plus a custom refspec destination to avoid colliding with remote refs:
+refs/heads/*:refs/prefetch/<remote>/*
However, some users customize their refspec to reduce how much data they download from specific remotes. This can involve restrictive patterns for fetching or negative patterns to avoid downloading some refs.
Modify fetch_remote() to iterate over the remote's refspec list and translate that into the appropriate prefetch scenario. Specifically, re-parse the raw form of the refspec into a new 'struct refspec' and modify the 'dst' member to replace a leading "refs/" substring with "refs/prefetch/", or prepend "refs/prefetch/" to 'dst' otherwise. Negative refspecs do not have a 'dst' so they can be transferred to the 'git fetch' command unmodified.
This prefix change provides the benefit of keeping whatever collisions may exist in the custom refspecs, if that is a desirable outcome.
This changes the names of the refs that would be fetched by the default refspec. Instead of "refs/prefetch/<remote>/<branch>" they will now go to "refs/prefetch/remotes/<remote>/<branch>". While this is a change, it is not a seriously breaking one: these refs are intended to be hidden and not used.
Update the documentation to be more generic about the destination refs. Do not mention custom refpecs explicitly, as that does not need to be highlighted in this documentation. The important part of placing refs in refs/prefetch remains.
--- Documentation/git-maintenance.txt | 3 +-- builtin/gc.c | 37 +++++++++++++++++++++++++- t/t7900-maintenance.sh | 43 ++++++++++++++++++++++++++----- 3 files changed, 74 insertions(+), 9 deletions(-)
Documentation/git-maintenance.txt +1 -2
@@ -94,8 +94,7 @@ prefetch:: 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. + refs are stored in `refs/prefetch/`. Also, tags are not updated. + This is done to avoid disrupting the remote-tracking branches. The end users expect these refs to stay unmoved unless they initiate a fetch. With prefetch
builtin/gc.c +36 -1
@@ -32,6 +32,7 @@#include "remote.h" #include "object-store.h" #include "exec-cmd.h" +#include "refspec.h" #define FAILED_RUN "failed to run %s"
@@ -877,6 +878,7 @@ static int fetch_remote(struct remote *remote, void *cbdata) {struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +int i; child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",
@@ -886,7 +888,40 @@ static int fetch_remote(struct remote *remote, void *cbdata)if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); +for (i =0 ; i < remote->fetch.nr; i++) { +struct refspec_item replace; +struct refspec_item *rsi = &remote->fetch.items[i]; +struct strbuf new_dst =STRBUF_INIT ; +size_t ignore_len =0 ; +char *replace_string; + +if (rsi->negative) { +strvec_push (&child.args, remote->fetch.raw[i]); +continue ; + } + +refspec_item_init (&replace, remote->fetch.raw[i],1 ); + +/* +* If a refspec dst starts with "refs/" at the start, +* then we will replace "refs/" with "refs/prefetch/". +* Otherwise, we will prepend the dst string with +* "refs/prefetch/". +*/ +if (!strncmp (replace.dst,"refs/" ,5 )) + ignore_len =5 ; + +strbuf_addstr (&new_dst,"refs/prefetch/" ); +strbuf_addstr (&new_dst, replace.dst + ignore_len); +free (replace.dst); + replace.dst =strbuf_detach (&new_dst,NULL ); + + replace_string =refspec_item_format (&replace); +strvec_push (&child.args, replace_string); +free (replace_string); + +refspec_item_clear (&replace); + }return !!run_command (&child); }
t/t7900-maintenance.sh +37 -6
57 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v2 5/5] maintenance: allow custom refspecs during prefetch
Quoted 107 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The prefetch task previously used the default refspec source plus a custom refspec destination to avoid colliding with remote refs:
+refs/heads/*:refs/prefetch/<remote>/*However, some users customize their refspec to reduce how much data they download from specific remotes. This can involve restrictive patterns for fetching or negative patterns to avoid downloading some refs.
Modify
fetch_remote()to iterate over the remote's refspec list and translate that into the appropriate prefetch scenario. Specifically, re-parse the raw form of the refspec into a new 'struct refspec' and modify the 'dst' member to replace a leading "refs/" substring with "refs/prefetch/", or prepend "refs/prefetch/" to 'dst' otherwise. Negative refspecs do not have a 'dst' so they can be transferred to the 'git fetch' command unmodified.This prefix change provides the benefit of keeping whatever collisions may exist in the custom refspecs, if that is a desirable outcome.
This changes the names of the refs that would be fetched by the default refspec. Instead of "refs/prefetch/<remote>/<branch>" they will now go to "refs/prefetch/remotes/<remote>/<branch>". While this is a change, it is not a seriously breaking one: these refs are intended to be hidden and not used.
Update the documentation to be more generic about the destination refs. Do not mention custom refpecs explicitly, as that does not need to be highlighted in this documentation. The important part of placing refs in refs/prefetch remains.
Reported-by Tom Saeger <[email protected]>Signed-off-by Derrick Stolee <[email protected]>--- Documentation/git-maintenance.txt | 3 +-- builtin/gc.c | 37 +++++++++++++++++++++++++- t/t7900-maintenance.sh | 43 ++++++++++++++++++++++++++----- 3 files changed, 74 insertions(+), 9 deletions(-)Documentation/git-maintenance.txt +1
-2@@ -94,8 +94,7 @@ prefetch:: 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. + refs are stored in `refs/prefetch/`. Also, tags are not updated. + This is done to avoid disrupting the remote-tracking branches. The end users expect these refs to stay unmoved unless they initiate a fetch. With prefetchbuiltin/gc.c +29
-1@@ -32,6 +32,7 @@#include "remote.h" #include "object-store.h" #include "exec-cmd.h" +#include "refspec.h" #define FAILED_RUN "failed to run %s"@@ -877,6 +878,7 @@ static int fetch_remote(struct remote *remote, void *cbdata) {struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +int i; child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",@@ -886,7 +888,40 @@ static int fetch_remote(struct remote *remote, void *cbdata)if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); +for (i =0 ; i < remote->fetch.nr; i++) { +struct refspec_item replace; +struct refspec_item *rsi = &remote->fetch.items[i]; +struct strbuf new_dst =STRBUF_INIT ; +size_t ignore_len =0 ; +char *replace_string; + +if (rsi->negative) { +strvec_push (&child.args, remote->fetch.raw[i]); +continue ; + } + +refspec_item_init (&replace, remote->fetch.raw[i],1 ); + +/* +* If a refspec dst starts with "refs/" at the start, +* then we will replace "refs/" with "refs/prefetch/". +* Otherwise, we will prepend the dst string with +* "refs/prefetch/". +*/ +if (!strncmp (replace.dst,"refs/" ,5 )) + ignore_len =5 ; + +strbuf_addstr (&new_dst,"refs/prefetch/" ); +strbuf_addstr (&new_dst, replace.dst + ignore_len); +free (replace.dst); + replace.dst =strbuf_detach (&new_dst,NULL );
builtin/gc.c static int fetch_remote(struct remote *remote, void *cbdata) · line 927
Quoted 10 lines
builtin/gc.c +7
-0+ + replace_string =refspec_item_format (&replace); +strvec_push (&child.args, replace_string); +free (replace_string); + +refspec_item_clear (&replace); + }return !!run_command (&child); }
Junio brought up the point about configs which 'fetch' have no dst
https://lore.kernel.org/git/[email protected]/
[remote "submaintainer1"]
url = ... repository of submaintainer #1 ... fetch = master
tagopt = --no-tags
This patch fixes segfault for config like above.
You might have ideas on a cleaner way to do this.
I did add child_process_clear.
--Tom
builtin/gc.c +12 -0
@@ -879,6 +879,7 @@ static int fetch_remote(struct remote *remote, void *cbdata)struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ;int i; +int nargs; child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",
@@ -888,6 +889,8 @@ static int fetch_remote(struct remote *remote, void *cbdata)if (opts->quiet)strvec_push (&child.args,"--quiet" ); + nargs = child.args.nr; +for (i =0 ; i < remote->fetch.nr; i++) {struct refspec_item replace;struct refspec_item *rsi = &remote->fetch.items[i];
@@ -900,6 +903,10 @@ static int fetch_remote(struct remote *remote, void *cbdata)continue ; } +if (!rsi->dst) { +continue ; + } +refspec_item_init (&replace, remote->fetch.raw[i],1 );/*
@@ -923,6 +930,12 @@ static int fetch_remote(struct remote *remote, void *cbdata)refspec_item_clear (&replace); } +/* skip remote if no refspecs to fetch */ +if (child.args.nr - nargs <=0 ) { +child_process_clear (&child); +return 0 ; + }
Re: [PATCH v2 5/5] maintenance: allow custom refspecs during prefetch
Quoted 3 lines · Tom Saeger
Junio brought up the point about configs which 'fetch' have no dst
https://lore.kernel.org/git/[email protected]/
Thank you for reminding me about this. It was on the other thread, and I forgot to go back to it as I was preparing this version.
Quoted 9 lines
[remote "submaintainer1"]
url = ... repository of submaintainer #1 ... fetch = master
tagopt = --no-tagsThis patch fixes segfault for config like above.
You might have ideas on a cleaner way to do this.
I did addchild_process_clear.
I will also add a test to ensure this scenario does not regress. -Stolee
Re: [PATCH v2 5/5] maintenance: allow custom refspecs during prefetch
In reply to Derrick Stolee via GitGitGadgetQuoted 22 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The prefetch task previously used the default refspec source plus a custom refspec destination to avoid colliding with remote refs:
+refs/heads/*:refs/prefetch/<remote>/*However, some users customize their refspec to reduce how much data they download from specific remotes. This can involve restrictive patterns for fetching or negative patterns to avoid downloading some refs.
Modify
fetch_remote()to iterate over the remote's refspec list and translate that into the appropriate prefetch scenario. Specifically, re-parse the raw form of the refspec into a new 'struct refspec' and modify the 'dst' member to replace a leading "refs/" substring with "refs/prefetch/", or prepend "refs/prefetch/" to 'dst' otherwise. Negative refspecs do not have a 'dst' so they can be transferred to the 'git fetch' command unmodified.This prefix change provides the benefit of keeping whatever collisions may exist in the custom refspecs, if that is a desirable outcome.
Quoted 8 lines
This changes the names of the refs that would be fetched by the default refspec. Instead of "refs/prefetch/<remote>/<branch>" they will now go to "refs/prefetch/remotes/<remote>/<branch>". While this is a change, it is not a seriously breaking one: these refs are intended to be hidden and not used.
Update the documentation to be more generic about the destination refs. Do not mention custom refpecs explicitly, as that does not need to be
Typo here: s/refpecs/refspecs/
Quoted 151 lines
highlighted in this documentation. The important part of placing refs in refs/prefetch remains.
Reported-by Tom Saeger <[email protected]>Signed-off-by Derrick Stolee <[email protected]>--- Documentation/git-maintenance.txt | 3 +-- builtin/gc.c | 37 +++++++++++++++++++++++++- t/t7900-maintenance.sh | 43 ++++++++++++++++++++++++++----- 3 files changed, 74 insertions(+), 9 deletions(-)Documentation/git-maintenance.txt +1
-2@@ -94,8 +94,7 @@ prefetch:: 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. + refs are stored in `refs/prefetch/`. Also, tags are not updated. + This is done to avoid disrupting the remote-tracking branches. The end users expect these refs to stay unmoved unless they initiate a fetch. With prefetchbuiltin/gc.c +36
-1@@ -32,6 +32,7 @@#include "remote.h" #include "object-store.h" #include "exec-cmd.h" +#include "refspec.h" #define FAILED_RUN "failed to run %s"@@ -877,6 +878,7 @@ static int fetch_remote(struct remote *remote, void *cbdata) {struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +int i; child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",@@ -886,7 +888,40 @@ static int fetch_remote(struct remote *remote, void *cbdata)if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); +for (i =0 ; i < remote->fetch.nr; i++) { +struct refspec_item replace; +struct refspec_item *rsi = &remote->fetch.items[i]; +struct strbuf new_dst =STRBUF_INIT ; +size_t ignore_len =0 ; +char *replace_string; + +if (rsi->negative) { +strvec_push (&child.args, remote->fetch.raw[i]); +continue ; + } + +refspec_item_init (&replace, remote->fetch.raw[i],1 ); + +/* +* If a refspec dst starts with "refs/" at the start, +* then we will replace "refs/" with "refs/prefetch/". +* Otherwise, we will prepend the dst string with +* "refs/prefetch/". +*/ +if (!strncmp (replace.dst,"refs/" ,5 )) + ignore_len =5 ; + +strbuf_addstr (&new_dst,"refs/prefetch/" ); +strbuf_addstr (&new_dst, replace.dst + ignore_len); +free (replace.dst); + replace.dst =strbuf_detach (&new_dst,NULL ); + + replace_string =refspec_item_format (&replace); +strvec_push (&child.args, replace_string); +free (replace_string); + +refspec_item_clear (&replace); + }return !!run_command (&child); }t/t7900-maintenance.sh +37
-6@@ -142,20 +142,51 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remote1/*" <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remote2/*" <run-prefetch.txt && + test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote1/*" <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote2/*" <run-prefetch.txt && test_path_is_missing .git/refs/remotes && - git log prefetch/remote1/one && - git log prefetch/remote2/two && + git log prefetch/remotes/remote1/one && + git log prefetch/remotes/remote2/two && git fetch --all && - test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one && - test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two && + test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one && + test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two && test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && ! grep "prefetch" log ' +test_expect_success 'prefetch custom refspecs' ' + git -C clone1 branch -f special/fetched HEAD && + git -C clone1 branch -f special/secret/not-fetched HEAD && + + # create multiple refspecs for remote1 + git config --add remote.remote1.fetch "+refs/heads/special/fetched:refs/heads/fetched" && + git config --add remote.remote1.fetch "^refs/heads/special/secret/not-fetched" && + + GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null && + + fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && + + # skips second refspec because it is not a pattern type + rs1="+refs/heads/*:refs/prefetch/remotes/remote1/*" && + rs2="+refs/heads/special/fetched:refs/prefetch/heads/fetched" && + rs3="^refs/heads/special/secret/not-fetched" && + + test_subcommand git fetch remote1 $fetchargs "$rs1" "$rs2" "$rs3" <prefetch-refspec.txt && + test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote2/*" <prefetch-refspec.txt && + + # first refspec is overridden by second + test_must_fail git rev-parse refs/prefetch/special/fetched && + git rev-parse refs/prefetch/heads/fetched && + + # possible incorrect places for the non-fetched ref + test_must_fail git rev-parse refs/prefetch/remotes/remote1/secret/not-fetched && + test_must_fail git rev-parse refs/prefetch/remotes/remote1/not-fetched && + test_must_fail git rev-parse refs/heads/secret/not-fetched && + test_must_fail git rev-parse refs/heads/not-fetched +' + test_expect_success 'prefetch and existing log.excludeDecoration values' ' git config --unset-all log.excludeDecoration && git config log.excludeDecoration refs/remotes/remote1/ &&Signature · 1 line
gitgitgadget
Re: [PATCH v2 5/5] maintenance: allow custom refspecs during prefetch
In reply to Derrick Stolee via GitGitGadgetQuoted 1 line · Derrick Stolee via GitGitGadget
(quoted diff) +0
-0@@ -877,6 +878,7 @@ static int fetch_remote(struct remote *remote, void *cbdata)
[snip]
builtin/gc.c static int fetch_remote(struct remote *remote, void *cbdata) · line 912
Quoted 8 lines
builtin/gc.c +8
-0+/* +* If a refspec dst starts with "refs/" at the start, +* then we will replace "refs/" with "refs/prefetch/". +* Otherwise, we will prepend the dst string with +* "refs/prefetch/". +*/ +if (!strncmp (replace.dst,"refs/" ,5 )) + ignore_len =5 ;
Using a literal string plus the literal value of the string length, twice, doesn't sit great with me...
builtin/gc.c static int fetch_remote(struct remote *remote, void *cbdata) · line 915
Quoted 3 lines
builtin/gc.c +3
-0+ +strbuf_addstr (&new_dst,"refs/prefetch/" ); +strbuf_addstr (&new_dst, replace.dst + ignore_len);
...plus with some ugly array pointer math. :) Why not use git-compat-util.h:skip_prefix() instead of doing your own math? (In fact, the doc comment on skip_prefix() talks about using it exactly for stripping "refs/" off the beginning of a string :) )
- Emily
Re: [PATCH v2 5/5] maintenance: allow custom refspecs during prefetch
In reply to Derrick Stolee via GitGitGadgett/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 171
Quoted 13 lines
t/t7900-maintenance.sh +13
-0+test_expect_success 'prefetch custom refspecs' ' + git -C clone1 branch -f special/fetched HEAD && + git -C clone1 branch -f special/secret/not-fetched HEAD && + + # create multiple refspecs for remote1 + git config --add remote.remote1.fetch "+refs/heads/special/fetched:refs/heads/fetched" && + git config --add remote.remote1.fetch "^refs/heads/special/secret/not-fetched" && + + GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null && + + fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && + + # skips second refspec because it is not a pattern type
What second refspec is being skipped?
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 177
Quoted 6 lines
t/t7900-maintenance.sh +6
-0+ rs1="+refs/heads/*:refs/prefetch/remotes/remote1/*" && + rs2="+refs/heads/special/fetched:refs/prefetch/heads/fetched" && + rs3="^refs/heads/special/secret/not-fetched" && + + test_subcommand git fetch remote1 $fetchargs "$rs1" "$rs2" "$rs3" <prefetch-refspec.txt && + test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote2/*" <prefetch-refspec.txt &&
How is this command generated? I don't see any mention of remote2 in this test. (If it's because this repo was configured in a previous test and some of the configuration carried over, I think it's best to start in a new repo or at least have the previous config be cleared.)
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 188
Quoted 11 lines
t/t7900-maintenance.sh +11
-0+ + # first refspec is overridden by second + test_must_fail git rev-parse refs/prefetch/special/fetched && + git rev-parse refs/prefetch/heads/fetched && + + # possible incorrect places for the non-fetched ref + test_must_fail git rev-parse refs/prefetch/remotes/remote1/secret/not-fetched && + test_must_fail git rev-parse refs/prefetch/remotes/remote1/not-fetched && + test_must_fail git rev-parse refs/heads/secret/not-fetched && + test_must_fail git rev-parse refs/heads/not-fetched +'
Other than this and the other comments that others have brought up, this series looks good.
v3 cover Maintenance: adapt custom refspecs
In reply to Derrick Stolee via GitGitGadgetTom Saeger rightly pointed out [1] that the prefetch task ignores custom refspecs. This can lead to downloading more data than requested, and it doesn't even help the future foreground fetches that use that custom refspec.
[1]
https://lore.kernel.org/git/20210401184914.qmr7jhjbhp2mt3h6@dhcp-10-154-148-175.vpn.oracle.com/
This series fixes this problem by carefully replacing the start of each refspec's destination with "refs/prefetch/". If the destination already starts with "refs/", then that is replaced. Otherwise "refs/prefetch/" is just prepended.
This happens inside of git fetch when a --prefetch option is given. This allows us to maniuplate a struct refspec_item instead of a full refspec string. It also simplifies our logic in testing the prefetch task.
Update in V3
============
* The fix is almost completely rewritten as an update to 'git fetch'. See the new PATCH 2 for this update.
* There was some discussion of rewriting test_subcommand, but that can be delayed until a proper solution is found to complications around softer matches.
Updates in V2
=============
Thanks for the close eye on this series. I appreciate the recommendations, which I believe I have responded to them all:
* Fixed typos.
* Made refspec_item_format() re-entrant. Consumers must free the buffer.
* Cleaned up style (quoting and tabbing).
Thanks, -Stolee
Derrick Stolee (3):
maintenance: simplify prefetch logic
fetch: add --prefetch option
maintenance: use 'git fetch --prefetch'
Documentation/fetch-options.txt | 5 +++ Documentation/git-maintenance.txt | 6 ++-- builtin/fetch.c | 56 +++++++++++++++++++++++++++++++ builtin/gc.c | 36 +++++--------------- t/t5582-fetch-negative-refspec.sh | 30 +++++++++++++++++ t/t7900-maintenance.sh | 14 ++++---- 6 files changed, 109 insertions(+), 38 deletions(-)
base-commit: 89b43f80a514aee58b662ad606e6352e03eaeee4
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-924%2Fderrickstolee%2Fmaintenance%2Frefspec-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-924/derrickstolee/maintenance/refspec-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/924
Range-diff vs v2:
1: 5aa0cb06c3f2 = 1: 4c0e983ba56f maintenance: simplify prefetch logic
2: d58a3e042ee8 < -: ------------ test-lib: use exact match for test_subcommand
3: 96388d949b98 < -: ------------ refspec: output a refspec item
4: bf296282323a < -: ------------ test-tool: test refspec input/output
-: ------------ > 2: 7f488eea6dbd fetch: add --prefetch option
5: 9592224e3d42 ! 3: ed055d772452 maintenance: allow custom refspecs during prefetch
@@ Metadata
Author: Derrick Stolee <[email protected]>
## Commit message ##
- maintenance: allow custom refspecs during prefetch
+ maintenance: use 'git fetch --prefetch'
- The prefetch task previously used the default refspec source plus a
- custom refspec destination to avoid colliding with remote refs:
+ The 'prefetch' maintenance task previously forced the following refspec
+ for each remote:
+refs/heads/*:refs/prefetch/<remote>/*
- However, some users customize their refspec to reduce how much data they
- download from specific remotes. This can involve restrictive patterns
- for fetching or negative patterns to avoid downloading some refs.
+ If a user has specified a more strict refspec for the remote, then this
+ prefetch task downloads more objects than necessary.
- Modify fetch_remote() to iterate over the remote's refspec list and
- translate that into the appropriate prefetch scenario. Specifically,
- re-parse the raw form of the refspec into a new 'struct refspec' and
- modify the 'dst' member to replace a leading "refs/" substring with
- "refs/prefetch/", or prepend "refs/prefetch/" to 'dst' otherwise.
- Negative refspecs do not have a 'dst' so they can be transferred to the
- 'git fetch' command unmodified.
-
- This prefix change provides the benefit of keeping whatever collisions
- may exist in the custom refspecs, if that is a desirable outcome.
-
- This changes the names of the refs that would be fetched by the default
- refspec. Instead of "refs/prefetch/<remote>/<branch>" they will now go
- to "refs/prefetch/remotes/<remote>/<branch>". While this is a change, it
- is not a seriously breaking one: these refs are intended to be hidden
- and not used.
+ The previous change introduced the '--prefetch' option to 'git fetch'
+ which manipulates the remote's refspec to place all resulting refs into
+ refs/prefetch/, with further partitioning based on the destinations of
+ those refspecs.
Update the documentation to be more generic about the destination refs.
- Do not mention custom refpecs explicitly, as that does not need to be
+ Do not mention custom refspecs explicitly, as that does not need to be
highlighted in this documentation. The important part of placing refs in
- refs/prefetch remains.
+ refs/prefetch/ remains.
Reported-by: Tom Saeger <[email protected]>
Signed-off-by: Derrick Stolee <[email protected]>
## Documentation/git-maintenance.txt ##
-@@ Documentation/git-maintenance.txt: prefetch::
+@@ Documentation/git-maintenance.txt: commit-graph::
+ prefetch::
+ The `prefetch` 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
+- 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.
-+ refs are stored in `refs/prefetch/`. Also, tags are not updated.
++ command is run. The configured refspec is modified to place all
++ requested refs within `refs/prefetch/`. Also, tags are not updated.
+
This is done to avoid disrupting the remote-tracking branches. The end users
expect these refs to stay unmoved unless they initiate a fetch. With prefetch
## builtin/gc.c ##
-@@
- #include "remote.h"
- #include "object-store.h"
- #include "exec-cmd.h"
-+#include "refspec.h"
-
- #define FAILED_RUN "failed to run %s"
-
@@ builtin/gc.c: static int fetch_remote(struct remote *remote, void *cbdata)
- {
- struct maintenance_run_opts *opts = cbdata;
struct child_process child = CHILD_PROCESS_INIT;
-+ int i;
child.git_cmd = 1;
- strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",
-@@ builtin/gc.c: static int fetch_remote(struct remote *remote, void *cbdata)
+- strvec_pushl(&child.args, "fetch", remote->name, "--prune", "--no-tags",
++ strvec_pushl(&child.args, "fetch", remote->name,
++ "--prefetch", "--prune", "--no-tags",
+ "--no-write-fetch-head", "--recurse-submodules=no",
+- "--refmap=", NULL);
++ NULL);
+
if (opts->quiet)
strvec_push(&child.args, "--quiet");
- strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote->name);
-+ for (i = 0; i < remote->fetch.nr; i++) {
-+ struct refspec_item replace;
-+ struct refspec_item *rsi = &remote->fetch.items[i];
-+ struct strbuf new_dst = STRBUF_INIT;
-+ size_t ignore_len = 0;
-+ char *replace_string;
-+
-+ if (rsi->negative) {
-+ strvec_push(&child.args, remote->fetch.raw[i]);
-+ continue;
-+ }
-+
-+ refspec_item_init(&replace, remote->fetch.raw[i], 1);
-+
-+ /*
-+ * If a refspec dst starts with "refs/" at the start,
-+ * then we will replace "refs/" with "refs/prefetch/".
-+ * Otherwise, we will prepend the dst string with
-+ * "refs/prefetch/".
-+ */
-+ if (!strncmp(replace.dst, "refs/", 5))
-+ ignore_len = 5;
-+
-+ strbuf_addstr(&new_dst, "refs/prefetch/");
-+ strbuf_addstr(&new_dst, replace.dst + ignore_len);
-+ free(replace.dst);
-+ replace.dst = strbuf_detach(&new_dst, NULL);
-+
-+ replace_string = refspec_item_format(&replace);
-+ strvec_push(&child.args, replace_string);
-+ free(replace_string);
-+
-+ refspec_item_clear(&replace);
-+ }
-
+-
return !!run_command(&child);
}
+
## t/t7900-maintenance.sh ##
@@ t/t7900-maintenance.sh: test_expect_success 'prefetch multiple remotes' '
+ test_commit -C clone1 one &&
test_commit -C clone2 two &&
GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
- fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
-- test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remote1/*" <run-prefetch.txt &&
-- test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remote2/*" <run-prefetch.txt &&
-+ test_subcommand git fetch remote1 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote1/*" <run-prefetch.txt &&
-+ test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote2/*" <run-prefetch.txt &&
+- fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
+- test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt &&
+- test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt &&
++ fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" &&
++ test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt &&
++ test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt &&
test_path_is_missing .git/refs/remotes &&
- git log prefetch/remote1/one &&
- git log prefetch/remote2/two &&
@@ t/t7900-maintenance.sh: test_expect_success 'prefetch multiple remotes' '
test_cmp_config refs/prefetch/ log.excludedecoration &&
git log --oneline --decorate --all >log &&
- ! grep "prefetch" log
- '
-
-+test_expect_success 'prefetch custom refspecs' '
-+ git -C clone1 branch -f special/fetched HEAD &&
-+ git -C clone1 branch -f special/secret/not-fetched HEAD &&
-+
-+ # create multiple refspecs for remote1
-+ git config --add remote.remote1.fetch "+refs/heads/special/fetched:refs/heads/fetched" &&
-+ git config --add remote.remote1.fetch "^refs/heads/special/secret/not-fetched" &&
-+
-+ GIT_TRACE2_EVENT="$(pwd)/prefetch-refspec.txt" git maintenance run --task=prefetch 2>/dev/null &&
-+
-+ fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
-+
-+ # skips second refspec because it is not a pattern type
-+ rs1="+refs/heads/*:refs/prefetch/remotes/remote1/*" &&
-+ rs2="+refs/heads/special/fetched:refs/prefetch/heads/fetched" &&
-+ rs3="^refs/heads/special/secret/not-fetched" &&
-+
-+ test_subcommand git fetch remote1 $fetchargs "$rs1" "$rs2" "$rs3" <prefetch-refspec.txt &&
-+ test_subcommand git fetch remote2 $fetchargs "+refs/heads/*:refs/prefetch/remotes/remote2/*" <prefetch-refspec.txt &&
-+
-+ # first refspec is overridden by second
-+ test_must_fail git rev-parse refs/prefetch/special/fetched &&
-+ git rev-parse refs/prefetch/heads/fetched &&
-+
-+ # possible incorrect places for the non-fetched ref
-+ test_must_fail git rev-parse refs/prefetch/remotes/remote1/secret/not-fetched &&
-+ test_must_fail git rev-parse refs/prefetch/remotes/remote1/not-fetched &&
-+ test_must_fail git rev-parse refs/heads/secret/not-fetched &&
-+ test_must_fail git rev-parse refs/heads/not-fetched
-+'
-+
- test_expect_success 'prefetch and existing log.excludeDecoration values' '
- git config --unset-all log.excludeDecoration &&
- git config log.excludeDecoration refs/remotes/remote1/ &&Signature · 1 line
gitgitgadget
PATCH v3 1/3 maintenance: simplify prefetch logic
From: Derrick Stolee <[email protected]>
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also because it sets up an upcoming change to use the remote's refspec. This data is accessible from the 'struct remote' data that is now accessible in fetch_remote().
--- builtin/gc.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-)
builtin/gc.c +8 -25
@@ -873,55 +873,38 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)return 0 ; } -static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" ,"--refmap=" ,NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote); +strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name);return !!run_command (&child); } -static int append_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 (struct maintenance_run_opts *opts) { -int result =0 ; -struct string_list_item *item; -struct string_list remotes =STRING_LIST_INIT_DUP ; -git_config_set_multivar_gently ("log.excludedecoration" ,"refs/prefetch/" ,"refs/prefetch/" ,CONFIG_FLAGS_FIXED_VALUE |CONFIG_FLAGS_MULTI_REPLACE ); -if (for_each_remote (append_remote, &remotes)) { -error (_ ("failed to fill remotes" )); - result =1 ; - goto cleanup; +if (for_each_remote (fetch_remote, opts)) { +error (_ ("failed to prefetch remotes" )); +return 1 ; } -for_each_string_list_item (item, &remotes) - result |=fetch_remote (item->string, opts); - -cleanup: -string_list_clear (&remotes,0 ); -return result; +return 0 ; }static int maintenance_task_gc (struct maintenance_run_opts *opts)
Signature · 1 line
gitgitgadget
Re: [PATCH v3 1/3] maintenance: simplify prefetch logic
Quoted 19 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also because it sets up an upcoming change to use the remote's refspec. This data is accessible from the 'struct remote' data that is now accessible in
fetch_remote().Signed-off-by Derrick Stolee <[email protected]>--- builtin/gc.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-)builtin/gc.c +0
-0
builtin/gc.c static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) · line 879
Quoted 8 lines
builtin/gc.c +2
-1@@ -873,55 +873,38 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)return 0 ; } -static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ;
I think this might be appropriate to add:
if (remote->skip_default_update)
return 0;maintenance prefetch is acting like git fetch --all So it should also skip remotes with configs skipfetchall = true Agree?
Quoted 58 lines
builtin/gc.c +6
-25child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" ,"--refmap=" ,NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote); +strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name);return !!run_command (&child); } -static int append_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 (struct maintenance_run_opts *opts) { -int result =0 ; -struct string_list_item *item; -struct string_list remotes =STRING_LIST_INIT_DUP ; -git_config_set_multivar_gently ("log.excludedecoration" ,"refs/prefetch/" ,"refs/prefetch/" ,CONFIG_FLAGS_FIXED_VALUE |CONFIG_FLAGS_MULTI_REPLACE ); -if (for_each_remote (append_remote, &remotes)) { -error (_ ("failed to fill remotes" )); - result =1 ; - goto cleanup; +if (for_each_remote (fetch_remote, opts)) { +error (_ ("failed to prefetch remotes" )); +return 1 ; } -for_each_string_list_item (item, &remotes) - result |=fetch_remote (item->string, opts); - -cleanup: -string_list_clear (&remotes,0 ); -return result; +return 0 ; }static int maintenance_task_gc (struct maintenance_run_opts *opts) --gitgitgadget
Re: [PATCH v3 1/3] maintenance: simplify prefetch logic
Quoted 11 lines · Tom Saeger
builtin/gc.c static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) · line 879
Quoted 5 lines · Derrick Stolee via GitGitGadget
builtin/gc.c +2
-1-static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ;I think this might be appropriate to add:
Quoted 7 lines
if (remote->skip_default_update) return 0;maintenance prefetch is acting like
git fetch --allSo it should also skip remotes with configsskipfetchall = trueAgree?
TIL about skipfetchall. I think that's a good idea to introduce. It'll be a new patch, not added in this one.
Thanks,
-Stolee
PATCH v3 2/3 fetch: add --prefetch option
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The --prefetch option will be used by the 'prefetch' maintenance task instead of sending refspecs explicitly across the command-line. The intention is to modify the refspec to place all results in refs/prefetch/ instead of anywhere else.
Create helper method filter_prefetch_refspec() to modify a given refspec to fit the rules expected of the prefetch task:
* Negative refspecs are preserved.
* Refspecs without a destination are removed.
* Refspecs whose source starts with "refs/tags/" are removed.
* Other refspecs are placed within "refs/prefetch/".
Finally, we add the 'force' option to ensure that prefetch refs are replaced as necessary.
--- Documentation/fetch-options.txt | 5 +++ builtin/fetch.c | 56 +++++++++++++++++++++++++++++++ t/t5582-fetch-negative-refspec.sh | 30 +++++++++++++++++ 3 files changed, 91 insertions(+)
Documentation/fetch-options.txt +5 -0
@@ -110,6 +110,11 @@ ifndef::git-pull[] setting `fetch.writeCommitGraph`. endif::git-pull[] +--prefetch:: + Modify the configured refspec to place all refs into the + `refs/prefetch/` namespace. See the `prefetch` task in + linkgit:git-maintenance[1]. + -p:: --prune:: Before fetching, remove any remote-tracking references that no
builtin/fetch.c +56 -0
80 lines not on this page · raw message
t/t5582-fetch-negative-refspec.sh +30 -0
@@ -240,4 +240,34 @@ test_expect_success "push with matching +: and negative refspec" ' git -C two push -v one ' +test_expect_success '--prefetch correctly modifies refspecs' ' + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && + git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore && + git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" && + + git tag -a -m never never-fetch-tag HEAD && + + git branch bogus/fetched HEAD~1 && + git branch bogus/ignore HEAD && + + git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore && + + # correctly handle when refspec set becomes empty + # after removing the refs/tags/* refspec. + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && + + git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + + # The refspec for refs that are not fully qualified + # are filtered multiple times. + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v3 2/3] fetch: add --prefetch option
Quoted 83 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The --prefetch option will be used by the 'prefetch' maintenance task instead of sending refspecs explicitly across the command-line. The intention is to modify the refspec to place all results in refs/prefetch/ instead of anywhere else.
Create helper method
filter_prefetch_refspec()to modify a given refspec to fit the rules expected of the prefetch task:* Negative refspecs are preserved.
* Refspecs without a destination are removed.
* Refspecs whose source starts with "refs/tags/" are removed.
* Other refspecs are placed within "refs/prefetch/".Finally, we add the 'force' option to ensure that prefetch refs are replaced as necessary.
Signed-off-by Derrick Stolee <[email protected]>--- Documentation/fetch-options.txt | 5 +++ builtin/fetch.c | 56 +++++++++++++++++++++++++++++++ t/t5582-fetch-negative-refspec.sh | 30 +++++++++++++++++ 3 files changed, 91 insertions(+)Documentation/fetch-options.txt +5
-0@@ -110,6 +110,11 @@ ifndef::git-pull[] setting `fetch.writeCommitGraph`. endif::git-pull[] +--prefetch:: + Modify the configured refspec to place all refs into the + `refs/prefetch/` namespace. See the `prefetch` task in + linkgit:git-maintenance[1]. + -p:: --prune:: Before fetching, remove any remote-tracking references that nobuiltin/fetch.c +20
-0@@ -48,6 +48,7 @@ enum {static int fetch_prune_config =-1 ;/* unspecified */ static int fetch_show_forced_updates =1 ;static uint64_t forced_updates_ms =0 ; +static int prefetch =0 ;static int prune =-1 ;/* unspecified */ #define PRUNE_BY_DEFAULT 0/* do we prune by default? */ @@ -158,6 +159,8 @@ static struct option builtin_fetch_options[] = { N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),OPT_INTEGER ('j' ,"jobs" , &max_jobs,N_ ("number of submodules fetched in parallel" )), +OPT_BOOL (0 ,"prefetch" , &prefetch, +N_ ("modify the refspec to place all refs within refs/prefetch/" )),OPT_BOOL ('p' ,"prune" , &prune,N_ ("prune remote-tracking branches no longer on remote" )), OPT_BOOL('P', "prune-tags", &prune_tags,@@ -436,6 +439,55 @@ static void find_non_local_tags(const struct ref *refs,oidset_clear (&fetch_oids); } +static void filter_prefetch_refspec (struct refspec *rs) +{ +int i; + +if (!prefetch) +return ; + +for (i =0 ; i < rs->nr; i++) { +struct strbuf new_dst =STRBUF_INIT ; +char *old_dst; +const char *sub =NULL ; + +if (rs->items[i].negative) +continue ; +if (!rs->items[i].dst || + (rs->items[i].src && + !strncmp (rs->items[i].src,"refs/tags/" ,10 ))) {
builtin/fetch.c static void find_non_local_tags(const struct ref *refs, · line 468
Quoted 10 lines
builtin/fetch.c +10
-0+int j; + +free (rs->items[i].src); +free (rs->items[i].dst); + +for (j = i +1 ; j < rs->nr; j++) { + rs->items[j -1 ] = rs->items[j]; + rs->raw[j -1 ] = rs->raw[j]; + } + rs->nr--;
Hmm, don't you need to do 'i--;' here?
(Sorry in advance if this is nonsense, I am just skimming the patches without reading the whole series carefully).
Maybe try a test which has an entry, which requires the 'prefetch' modification, that immediately follows a 'tag' or 'empty dst' entry. (I can't quite tell, just reading the email, whether that is covered by the tests below - so please just ignore me if it already works ;)
ATB,
Ramsay Jones
Quoted 76 lines
builtin/fetch.c +26
-0+continue ; + } + + old_dst = rs->items[i].dst; +strbuf_addstr (&new_dst,"refs/prefetch/" ); + +/* +* If old_dst starts with "refs/", then place +* sub after that prefix. Otherwise, start at +* the beginning of the string. +*/ +if (!skip_prefix (old_dst,"refs/" , &sub)) + sub = old_dst; +strbuf_addstr (&new_dst, sub); + + rs->items[i].dst =strbuf_detach (&new_dst,NULL ); + rs->items[i].force =1 ; + +free (old_dst); + } +} + static struct ref *get_ref_map(struct remote *remote, const struct ref *remote_refs, struct refspec *rs,@@ -452,6 +504,10 @@ static struct ref *get_ref_map(struct remote *remote,struct hashmap existing_refs;int existing_refs_populated =0 ; +filter_prefetch_refspec (rs); +if (remote) +filter_prefetch_refspec (&remote->fetch); +if (rs->nr) {struct refspec *fetch_refspec;t/t5582-fetch-negative-refspec.sh +30
-0@@ -240,4 +240,34 @@ test_expect_success "push with matching +: and negative refspec" ' git -C two push -v one ' +test_expect_success '--prefetch correctly modifies refspecs' ' + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && + git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore && + git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" && + + git tag -a -m never never-fetch-tag HEAD && + + git branch bogus/fetched HEAD~1 && + git branch bogus/ignore HEAD && + + git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore && + + # correctly handle when refspec set becomes empty + # after removing the refs/tags/* refspec. + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && + + git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + + # The refspec for refs that are not fully qualified + # are filtered multiple times. + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore +' + test_done
Re: [PATCH v3 2/3] fetch: add --prefetch option
Quoted 24 lines · Derrick Stolee via GitGitGadget
Quoted 11 lines
From: Derrick Stolee <[email protected]>
builtin/fetch.c +10
-0+for (i =0 ; i < rs->nr; i++) { +struct strbuf new_dst =STRBUF_INIT ; +char *old_dst; +const char *sub =NULL ; + +if (rs->items[i].negative) +continue ; +if (!rs->items[i].dst || + (rs->items[i].src && + !strncmp (rs->items[i].src,"refs/tags/" ,10 ))) {builtin/fetch.c static void find_non_local_tags(const struct ref *refs, · line 468
Quoted 10 lines
builtin/fetch.c +10
-0+int j; + +free (rs->items[i].src); +free (rs->items[i].dst); + +for (j = i +1 ; j < rs->nr; j++) { + rs->items[j -1 ] = rs->items[j]; + rs->raw[j -1 ] = rs->raw[j]; + } + rs->nr--;Hmm, don't you need to do 'i--;' here?
Quoted 7 lines
(Sorry in advance if this is nonsense, I am just skimming the patches without reading the whole series carefully).
Maybe try a test which has an entry, which requires the 'prefetch' modification, that immediately follows a 'tag' or 'empty dst' entry. (I can't quite tell, just reading the email, whether that is covered by the tests below - so please just ignore me if it already works ;)
You are absolutely right! Thanks.
-Stolee
PATCH v3 3/3 maintenance: use 'git fetch --prefetch'
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The 'prefetch' maintenance task previously forced the following refspec for each remote:
+refs/heads/*:refs/prefetch/<remote>/*
If a user has specified a more strict refspec for the remote, then this prefetch task downloads more objects than necessary.
The previous change introduced the '--prefetch' option to 'git fetch' which manipulates the remote's refspec to place all resulting refs into refs/prefetch/, with further partitioning based on the destinations of those refspecs.
Update the documentation to be more generic about the destination refs. Do not mention custom refspecs explicitly, as that does not need to be highlighted in this documentation. The important part of placing refs in refs/prefetch/ remains.
--- Documentation/git-maintenance.txt | 6 ++---- builtin/gc.c | 7 +++---- t/t7900-maintenance.sh | 14 +++++++------- 3 files changed, 12 insertions(+), 15 deletions(-)
Documentation/git-maintenance.txt +2 -4
@@ -92,10 +92,8 @@ commit-graph:: prefetch:: The `prefetch` 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. + command is run. The configured refspec is modified to place all + requested refs within `refs/prefetch/`. Also, tags are not updated. + This is done to avoid disrupting the remote-tracking branches. The end users expect these refs to stay unmoved unless they initiate a fetch. With prefetch
builtin/gc.c +3 -4
@@ -879,15 +879,14 @@ static int fetch_remote(struct remote *remote, void *cbdata)struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name, +"--prefetch" ,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" , -"--refmap=" ,NULL ); +NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); -return !!run_command (&child); }
t/t7900-maintenance.sh +7 -7
@@ -141,15 +141,15 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone1 one && test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && - fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt && + fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" && + test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt && test_path_is_missing .git/refs/remotes && - git log prefetch/remote1/one && - git log prefetch/remote2/two && + git log prefetch/remotes/remote1/one && + git log prefetch/remotes/remote2/two && git fetch --all && - test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one && - test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two && + test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one && + test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two && test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log &&
Signature · 1 line
gitgitgadget
Quoted 2 lines · Derrick Stolee via GitGitGadget
* The fix is almost completely rewritten as an update to 'git fetch'. See the new PATCH 2 for this update.
I do agree that it gives us the most flexibility there with nice encapsulation. Nobody other than "git fetch" needs to know how it computes which remote refs are fetched given the real pathspec, and the only thing the caller with "--prefetch" is interested in is that the prefetch operation would not contaminate the remote-tracking refs.
Great idea. I wish I were the one who thought of it first ;-)
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee via GitGitGadget
* The fix is almost completely rewritten as an update to 'git fetch'. See the new PATCH 2 for this update.
Quoted 8 lines
I do agree that it gives us the most flexibility there with nice encapsulation. Nobody other than "git fetch" needs to know how it computes which remote refs are fetched given the real pathspec, and the only thing the caller with "--prefetch" is interested in is that the prefetch operation would not contaminate the remote-tracking refs.
Great idea. I wish I were the one who thought of it first ;-)
Yes - this simplifies things greatly!
I do have one case that fails prefetch though.
It's a case where all the remote's fetch configs are filtered out.
Example:
[remote "pr-924"]
url = https://github.com/gitgitgadget/git
fetch = +refs/tags/pr-924/derrickstolee/maintenance/refspec-v3 skipfetchall = true
tagopt = --no-tags
In this case, running git fetch pr-924 will fetch and update FETCH_HEAD, but running with maintenance prefetch task results in:
fatal: Couldn't find remote ref HEAD
error: failed to prefetch remotes
error: task 'prefetch' failed
I tracked this down a bit, but don't have a suggestion how to fix it.
builtin/fetch.c get_ref_map makes two calls to filter_prefetch_refspec, one for 'rs' and another for 'remote->fetch'.
filter_prefetch_refspec works and filters out the above fetch config. This correctly yields conditionrs->nr == 0 and remote->fetch.nr == 0
Later a call is made to get_remote_ref(remote_refs, "HEAD") which fails, leading to fatal: Couldn't find remote ref HEAD
Should this be expected, or should this now be special-cased for 'prefetch' somehow?
Regards,
--Tom
Quoted 29 lines · Tom Saeger
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee via GitGitGadget
* The fix is almost completely rewritten as an update to 'git fetch'. See the new PATCH 2 for this update.
Quoted 8 lines
I do agree that it gives us the most flexibility there with nice encapsulation. Nobody other than "git fetch" needs to know how it computes which remote refs are fetched given the real pathspec, and the only thing the caller with "--prefetch" is interested in is that the prefetch operation would not contaminate the remote-tracking refs.
Great idea. I wish I were the one who thought of it first ;-)
Yes - this simplifies things greatly!
I do have one case that fails prefetch though.
It's a case where all the remote's fetch configs are filtered out.Example:
[remote "pr-924"]
url = https://github.com/gitgitgadget/git
fetch = +refs/tags/pr-924/derrickstolee/maintenance/refspec-v3 skipfetchall = true
tagopt = --no-tags
Quoted 8 lines
In this case, running
git fetch pr-924will fetch and updateFETCH_HEAD, but running with maintenance prefetch task results in:fatal: Couldn't find remote ref HEAD
error: failed to prefetch remotes
error: task 'prefetch' failedI tracked this down a bit, but don't have a suggestion how to fix it.
This ugly hack fixes this failure. I'll keep staring at it.
builtin/fetch.c +3 -0
@@ -508,6 +508,9 @@ static struct ref *get_ref_map(struct remote *remote,if (remote)filter_prefetch_refspec (&remote->fetch); +if (prefetch && !rs->nr && remote && !remote->fetch.nr) +return NULL ; +if (rs->nr) {struct refspec *fetch_refspec;
--
Quoted 17 lines
builtin/fetch.cget_ref_mapmakes two calls tofilter_prefetch_refspec, one for 'rs' and another for 'remote->fetch'.
filter_prefetch_refspecworks and filters out the above fetch config. This correctly yields conditionrs->nr == 0andremote->fetch.nr == 0Later a call is made to
get_remote_ref(remote_refs, "HEAD")which fails, leading tofatal: Couldn't find remote ref HEADShould this be expected, or should this now be special-cased for 'prefetch' somehow?
Regards,
--Tom
Quoted 48 lines · Tom Saeger
Quoted 29 lines · Tom Saeger
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · Derrick Stolee via GitGitGadget
* The fix is almost completely rewritten as an update to 'git fetch'. See the new PATCH 2 for this update.
Quoted 8 lines
I do agree that it gives us the most flexibility there with nice encapsulation. Nobody other than "git fetch" needs to know how it computes which remote refs are fetched given the real pathspec, and the only thing the caller with "--prefetch" is interested in is that the prefetch operation would not contaminate the remote-tracking refs.
Great idea. I wish I were the one who thought of it first ;-)
Yes - this simplifies things greatly!
I do have one case that fails prefetch though.
It's a case where all the remote's fetch configs are filtered out.Example:
[remote "pr-924"]
url = https://github.com/gitgitgadget/git
fetch = +refs/tags/pr-924/derrickstolee/maintenance/refspec-v3 skipfetchall = true
tagopt = --no-tagsQuoted 8 lines
In this case, running
git fetch pr-924will fetch and updateFETCH_HEAD, but running with maintenance prefetch task results in:fatal: Couldn't find remote ref HEAD
error: failed to prefetch remotes
error: task 'prefetch' failedI tracked this down a bit, but don't have a suggestion how to fix it.
This ugly hack fixes this failure. I'll keep staring at it.
builtin/fetch.c +0
-0
builtin/fetch.c static struct ref *get_ref_map(struct remote *remote,
Quoted 10 lines
builtin/fetch.c +3
-1@@ -508,6 +508,9 @@ static struct ref *get_ref_map(struct remote *remote,if (remote)filter_prefetch_refspec (&remote->fetch); +if (prefetch && !rs->nr && remote && !remote->fetch.nr) +return NULL ; +if (rs->nr) {struct refspec *fetch_refspec; --
Less ugly fix...
builtin/fetch.c +2 -0
@@ -576,6 +576,8 @@ static struct ref *get_ref_map(struct remote *remote,if (has_merge && !strcmp (branch->remote_name, remote->name))add_merge_config (&ref_map, remote_refs, branch, &tail); + }else if (prefetch) { + ; }else { ref_map =get_remote_ref (remote_refs,"HEAD" );if (!ref_map)
--
Other ideas?
Quoted 19 lines
Quoted 19 lines
builtin/fetch.cget_ref_mapmakes two calls tofilter_prefetch_refspec, one for 'rs' and another for 'remote->fetch'.
filter_prefetch_refspecworks and filters out the above fetch config. This correctly yields conditionrs->nr == 0andremote->fetch.nr == 0Later a call is made to
get_remote_ref(remote_refs, "HEAD")which fails, leading tofatal: Couldn't find remote ref HEADShould this be expected, or should this now be special-cased for 'prefetch' somehow?
Regards,
--Tom
On 4/12/21 1:41 PM, Tom Saeger wrote:>
builtin/fetch.c static struct ref *get_ref_map(struct remote *remote, · line 581
Quoted 13 lines
Less ugly fix...
builtin/fetch.c +2
-0@@ -576,6 +576,8 @@ static struct ref *get_ref_map(struct remote *remote,if (has_merge && !strcmp (branch->remote_name, remote->name))add_merge_config (&ref_map, remote_refs, branch, &tail); + }else if (prefetch) { + ; }else {
I'll give this a try, but with "else if (!prefetch)" for the last block,instead.
Thanks for your diligent testing! It's helping a lot.
Thanks,
-Stolee
v4 cover Maintenance: adapt custom refspecs
In reply to Derrick Stolee via GitGitGadgetTom Saeger rightly pointed out [1] that the prefetch task ignores custom refspecs. This can lead to downloading more data than requested, and it doesn't even help the future foreground fetches that use that custom refspec.
[1]
https://lore.kernel.org/git/20210401184914.qmr7jhjbhp2mt3h6@dhcp-10-154-148-175.vpn.oracle.com/
This series fixes this problem by carefully replacing the start of each refspec's destination with "refs/prefetch/". If the destination already starts with "refs/", then that is replaced. Otherwise "refs/prefetch/" is just prepended.
This happens inside of git fetch when a --prefetch option is given. This allows us to maniuplate a struct refspec_item instead of a full refspec string. It also simplifies our logic in testing the prefetch task.
Updates in V4
=============
* Two bugs were fixed. Thanks, Ramsay and Tom, for pointing out the issues. Tests are added that prevent regressions.
* A new patch is added to respect remote.<name>.skipFetchAll. This is added at the end to take advantage of the simpler test design after --prefetch is added.
Update in V3
============
* The fix is almost completely rewritten as an update to 'git fetch'. See the new PATCH 2 for this update.
* There was some discussion of rewriting test_subcommand, but that can be delayed until a proper solution is found to complications around softer matches.
Updates in V2
=============
Thanks for the close eye on this series. I appreciate the recommendations, which I believe I have responded to them all:
* Fixed typos.
* Made refspec_item_format() re-entrant. Consumers must free the buffer.
* Cleaned up style (quoting and tabbing).
Thanks, -Stolee
Derrick Stolee (4):
maintenance: simplify prefetch logic
fetch: add --prefetch option
maintenance: use 'git fetch --prefetch'
maintenance: respect remote.*.skipFetchAll
Documentation/fetch-options.txt | 5 +++ Documentation/git-maintenance.txt | 6 ++-- builtin/fetch.c | 59 ++++++++++++++++++++++++++++++- builtin/gc.c | 39 +++++++------------- t/t5582-fetch-negative-refspec.sh | 43 ++++++++++++++++++++++ t/t7900-maintenance.sh | 22 +++++++----- 6 files changed, 134 insertions(+), 40 deletions(-)
base-commit: 89b43f80a514aee58b662ad606e6352e03eaeee4
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-924%2Fderrickstolee%2Fmaintenance%2Frefspec-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-924/derrickstolee/maintenance/refspec-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/924
Range-diff vs v3:
1: 4c0e983ba56f = 1: 4c0e983ba56f maintenance: simplify prefetch logic
2: 7f488eea6dbd ! 2: 73b4e8496746 fetch: add --prefetch option
@@ Commit message
Finally, we add the 'force' option to ensure that prefetch refs are
replaced as necessary.
+ There are some interesting cases that are worth testing.
+
+ An earlier version of this change dropped the "i--" from the loop that
+ deletes a refspec item and shifts the remaining entries down. This
+ allowed some refspecs to not be modified. The subtle part about the
+ first --prefetch test is that the "refs/tags/*" refspec appears directly
+ before the "refs/heads/bogus/*" refspec. Without that "i--", this
+ ordering would remove the "refs/tags/*" refspec and leave the last one
+ unmodified, placing the result in "refs/heads/*".
+
+ It is possible to have an empty refspec. This is typically the case for
+ remotes other than the origin, where users want to fetch a specific tag
+ or branch. To correctly test this case, we need to further remove the
+ upstream remote for the local branch. Thus, we are testing a refspec
+ that will be deleted, leaving nothing to fetch.
+
+ Helped-by: Tom Saeger <[email protected]>
+ Helped-by: Ramsay Jones <[email protected]>
Signed-off-by: Derrick Stolee <[email protected]>
## Documentation/fetch-options.txt ##
@@ builtin/fetch.c: static void find_non_local_tags(const struct ref *refs,
+ rs->raw[j - 1] = rs->raw[j];
+ }
+ rs->nr--;
++ i--;
+ continue;
+ }
+
@@ builtin/fetch.c: static struct ref *get_ref_map(struct remote *remote,
if (rs->nr) {
struct refspec *fetch_refspec;
+@@ builtin/fetch.c: static struct ref *get_ref_map(struct remote *remote,
+ if (has_merge &&
+ !strcmp(branch->remote_name, remote->name))
+ add_merge_config(&ref_map, remote_refs, branch, &tail);
+- } else {
++ } else if (!prefetch) {
+ ref_map = get_remote_ref(remote_refs, "HEAD");
+ if (!ref_map)
+ die(_("Couldn't find remote ref HEAD"));
## t/t5582-fetch-negative-refspec.sh ##
@@ t/t5582-fetch-negative-refspec.sh: test_expect_success "push with matching +: and negative refspec" '
@@ t/t5582-fetch-negative-refspec.sh: test_expect_success "push with matching +: an
+test_expect_success '--prefetch correctly modifies refspecs' '
+ git -C one config --unset-all remote.origin.fetch &&
-+ git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
+ git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore &&
++ git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
+ git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" &&
+
+ git tag -a -m never never-fetch-tag HEAD &&
@@ t/t5582-fetch-negative-refspec.sh: test_expect_success "push with matching +: an
+ git -C one rev-parse refs/prefetch/bogus/fetched &&
+ test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore
+'
++
++test_expect_success '--prefetch succeeds when refspec becomes empty' '
++ git checkout bogus/fetched &&
++ test_commit extra &&
++
++ git -C one config --unset-all remote.origin.fetch &&
++ git -C one config --unset branch.main.remote &&
++ git -C one config remote.origin.fetch "+refs/tags/extra" &&
++ git -C one config remote.origin.skipfetchall true &&
++ git -C one config remote.origin.tagopt "--no-tags" &&
++
++ git -C one fetch --prefetch
++'
+
test_done
3: ed055d772452 = 3: 565ed8a18929 maintenance: use 'git fetch --prefetch'
-: ------------ > 4: 92652fd9e6e1 maintenance: respect remote.*.skipFetchAllSignature · 1 line
gitgitgadget
PATCH v4 1/4 maintenance: simplify prefetch logic
From: Derrick Stolee <[email protected]>
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also because it sets up an upcoming change to use the remote's refspec. This data is accessible from the 'struct remote' data that is now accessible in fetch_remote().
--- builtin/gc.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-)
builtin/gc.c +8 -25
@@ -873,55 +873,38 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)return 0 ; } -static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" ,"--refmap=" ,NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote); +strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name);return !!run_command (&child); } -static int append_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 (struct maintenance_run_opts *opts) { -int result =0 ; -struct string_list_item *item; -struct string_list remotes =STRING_LIST_INIT_DUP ; -git_config_set_multivar_gently ("log.excludedecoration" ,"refs/prefetch/" ,"refs/prefetch/" ,CONFIG_FLAGS_FIXED_VALUE |CONFIG_FLAGS_MULTI_REPLACE ); -if (for_each_remote (append_remote, &remotes)) { -error (_ ("failed to fill remotes" )); - result =1 ; - goto cleanup; +if (for_each_remote (fetch_remote, opts)) { +error (_ ("failed to prefetch remotes" )); +return 1 ; } -for_each_string_list_item (item, &remotes) - result |=fetch_remote (item->string, opts); - -cleanup: -string_list_clear (&remotes,0 ); -return result; +return 0 ; }static int maintenance_task_gc (struct maintenance_run_opts *opts)
Signature · 1 line
gitgitgadget
Re: [PATCH v4 1/4] maintenance: simplify prefetch logic
Quoted 10 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The previous logic filled a string list with the names of each remote, but instead we could simply run the appropriate 'git fetch' data directly in the remote iterator. Do this for reduced code size, but also because it sets up an upcoming change to use the remote's refspec. This data is accessible from the 'struct remote' data that is now accessible in
fetch_remote().Signed-off-by Derrick Stolee <[email protected]>
Quoted 75 lines
--- builtin/gc.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-)builtin/gc.c +8
-25@@ -873,55 +873,38 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)return 0 ; } -static int fetch_remote (const char *remote,struct maintenance_run_opts *opts) +static int fetch_remote (struct remote *remote,void *cbdata) { +struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" ,"--refmap=" ,NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote); +strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name);return !!run_command (&child); } -static int append_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 (struct maintenance_run_opts *opts) { -int result =0 ; -struct string_list_item *item; -struct string_list remotes =STRING_LIST_INIT_DUP ; -git_config_set_multivar_gently ("log.excludedecoration" ,"refs/prefetch/" ,"refs/prefetch/" ,CONFIG_FLAGS_FIXED_VALUE |CONFIG_FLAGS_MULTI_REPLACE ); -if (for_each_remote (append_remote, &remotes)) { -error (_ ("failed to fill remotes" )); - result =1 ; - goto cleanup; +if (for_each_remote (fetch_remote, opts)) { +error (_ ("failed to prefetch remotes" )); +return 1 ; } -for_each_string_list_item (item, &remotes) - result |=fetch_remote (item->string, opts); - -cleanup: -string_list_clear (&remotes,0 ); -return result; +return 0 ; }static int maintenance_task_gc (struct maintenance_run_opts *opts)Signature · 1 line
gitgitgadget
PATCH v4 2/4 fetch: add --prefetch option
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The --prefetch option will be used by the 'prefetch' maintenance task instead of sending refspecs explicitly across the command-line. The intention is to modify the refspec to place all results in refs/prefetch/ instead of anywhere else.
Create helper method filter_prefetch_refspec() to modify a given refspec to fit the rules expected of the prefetch task:
* Negative refspecs are preserved.
* Refspecs without a destination are removed.
* Refspecs whose source starts with "refs/tags/" are removed.
* Other refspecs are placed within "refs/prefetch/".
Finally, we add the 'force' option to ensure that prefetch refs are replaced as necessary.
There are some interesting cases that are worth testing.
An earlier version of this change dropped the "i--" from the loop that deletes a refspec item and shifts the remaining entries down. This allowed some refspecs to not be modified. The subtle part about the first --prefetch test is that the "refs/tags/*" refspec appears directly before the "refs/heads/bogus/*" refspec. Without that "i--", this ordering would remove the "refs/tags/*" refspec and leave the last one unmodified, placing the result in "refs/heads/*".
It is possible to have an empty refspec. This is typically the case for remotes other than the origin, where users want to fetch a specific tag or branch. To correctly test this case, we need to further remove the upstream remote for the local branch. Thus, we are testing a refspec that will be deleted, leaving nothing to fetch.
--- Documentation/fetch-options.txt | 5 +++ builtin/fetch.c | 59 ++++++++++++++++++++++++++++++- t/t5582-fetch-negative-refspec.sh | 43 ++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-)
Documentation/fetch-options.txt +5 -0
@@ -110,6 +110,11 @@ ifndef::git-pull[] setting `fetch.writeCommitGraph`. endif::git-pull[] +--prefetch:: + Modify the configured refspec to place all refs into the + `refs/prefetch/` namespace. See the `prefetch` task in + linkgit:git-maintenance[1]. + -p:: --prune:: Before fetching, remove any remote-tracking references that no
builtin/fetch.c +58 -1
89 lines not on this page · raw message
t/t5582-fetch-negative-refspec.sh +43 -0
47 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v4 2/4] fetch: add --prefetch option
Quoted 149 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The --prefetch option will be used by the 'prefetch' maintenance task instead of sending refspecs explicitly across the command-line. The intention is to modify the refspec to place all results in refs/prefetch/ instead of anywhere else.
Create helper method
filter_prefetch_refspec()to modify a given refspec to fit the rules expected of the prefetch task:* Negative refspecs are preserved.
* Refspecs without a destination are removed.
* Refspecs whose source starts with "refs/tags/" are removed.
* Other refspecs are placed within "refs/prefetch/".Finally, we add the 'force' option to ensure that prefetch refs are replaced as necessary.
There are some interesting cases that are worth testing.
An earlier version of this change dropped the "i--" from the loop that deletes a refspec item and shifts the remaining entries down. This allowed some refspecs to not be modified. The subtle part about the first --prefetch test is that the "refs/tags/*" refspec appears directly before the "refs/heads/bogus/*" refspec. Without that "i--", this ordering would remove the "refs/tags/*" refspec and leave the last one unmodified, placing the result in "refs/heads/*".
It is possible to have an empty refspec. This is typically the case for remotes other than the origin, where users want to fetch a specific tag or branch. To correctly test this case, we need to further remove the upstream remote for the local branch. Thus, we are testing a refspec that will be deleted, leaving nothing to fetch.
Helped-by Tom Saeger <[email protected]>Helped-by Ramsay Jones <[email protected]>Signed-off-by Derrick Stolee <[email protected]>--- Documentation/fetch-options.txt | 5 +++ builtin/fetch.c | 59 ++++++++++++++++++++++++++++++- t/t5582-fetch-negative-refspec.sh | 43 ++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-)Documentation/fetch-options.txt +5
-0@@ -110,6 +110,11 @@ ifndef::git-pull[] setting `fetch.writeCommitGraph`. endif::git-pull[] +--prefetch:: + Modify the configured refspec to place all refs into the + `refs/prefetch/` namespace. See the `prefetch` task in + linkgit:git-maintenance[1]. + -p:: --prune:: Before fetching, remove any remote-tracking references that nobuiltin/fetch.c +57
-0@@ -48,6 +48,7 @@ enum {static int fetch_prune_config =-1 ;/* unspecified */ static int fetch_show_forced_updates =1 ;static uint64_t forced_updates_ms =0 ; +static int prefetch =0 ;static int prune =-1 ;/* unspecified */ #define PRUNE_BY_DEFAULT 0/* do we prune by default? */ @@ -158,6 +159,8 @@ static struct option builtin_fetch_options[] = { N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),OPT_INTEGER ('j' ,"jobs" , &max_jobs,N_ ("number of submodules fetched in parallel" )), +OPT_BOOL (0 ,"prefetch" , &prefetch, +N_ ("modify the refspec to place all refs within refs/prefetch/" )),OPT_BOOL ('p' ,"prune" , &prune,N_ ("prune remote-tracking branches no longer on remote" )), OPT_BOOL('P', "prune-tags", &prune_tags,@@ -436,6 +439,56 @@ static void find_non_local_tags(const struct ref *refs,oidset_clear (&fetch_oids); } +static void filter_prefetch_refspec (struct refspec *rs) +{ +int i; + +if (!prefetch) +return ; + +for (i =0 ; i < rs->nr; i++) { +struct strbuf new_dst =STRBUF_INIT ; +char *old_dst; +const char *sub =NULL ; + +if (rs->items[i].negative) +continue ; +if (!rs->items[i].dst || + (rs->items[i].src && + !strncmp (rs->items[i].src,"refs/tags/" ,10 ))) { +int j; + +free (rs->items[i].src); +free (rs->items[i].dst); + +for (j = i +1 ; j < rs->nr; j++) { + rs->items[j -1 ] = rs->items[j]; + rs->raw[j -1 ] = rs->raw[j]; + } + rs->nr--; + i--; +continue ; + } + + old_dst = rs->items[i].dst; +strbuf_addstr (&new_dst,"refs/prefetch/" ); + +/* +* If old_dst starts with "refs/", then place +* sub after that prefix. Otherwise, start at +* the beginning of the string. +*/ +if (!skip_prefix (old_dst,"refs/" , &sub)) + sub = old_dst; +strbuf_addstr (&new_dst, sub); + + rs->items[i].dst =strbuf_detach (&new_dst,NULL ); + rs->items[i].force =1 ; + +free (old_dst); + } +} + static struct ref *get_ref_map(struct remote *remote, const struct ref *remote_refs, struct refspec *rs,@@ -452,6 +505,10 @@ static struct ref *get_ref_map(struct remote *remote,struct hashmap existing_refs;int existing_refs_populated =0 ; +filter_prefetch_refspec (rs); +if (remote) +filter_prefetch_refspec (&remote->fetch); +if (rs->nr) {struct refspec *fetch_refspec;
builtin/fetch.c static struct ref *get_ref_map(struct remote *remote, · line 580
Quoted 5 lines
builtin/fetch.c +1
-1@@ -520,7 +577,7 @@ static struct ref *get_ref_map(struct remote *remote,if (has_merge && !strcmp (branch->remote_name, remote->name))add_merge_config (&ref_map, remote_refs, branch, &tail); - }else { + }else if (!prefetch) {
That works for me.
Quoted 32 lines
builtin/fetch.c +0
-0ref_map =get_remote_ref (remote_refs,"HEAD" );if (!ref_map)die (_ ("Couldn't find remote ref HEAD" ));t/t5582-fetch-negative-refspec.sh +21
-0@@ -240,4 +240,47 @@ test_expect_success "push with matching +: and negative refspec" ' git -C two push -v one ' +test_expect_success '--prefetch correctly modifies refspecs' ' + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && + git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" && + + git tag -a -m never never-fetch-tag HEAD && + + git branch bogus/fetched HEAD~1 && + git branch bogus/ignore HEAD && + + git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore && + + # correctly handle when refspec set becomes empty + # after removing the refs/tags/* refspec. + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && +
t/t5582-fetch-negative-refspec.sh test_expect_success "push with matching +: and negative refspec" ' · line 273
Quoted 10 lines
t/t5582-fetch-negative-refspec.sh +10
-0+ git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + + # The refspec for refs that are not fully qualified + # are filtered multiple times. + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore +' + +test_expect_success '--prefetch succeeds when refspec becomes empty' '
technically this will get skipped based only on "skipfetchall" right?
The remote could have an empty-set of refspecs or multiple
valid refspecs post filter_prefetch_refspec, but the remote gets skipped altogether.
perhaps '--prefetch succeeds when remote.skipfetchall is true' '
anyway this is looking pretty solid
Quoted 16 lines
t/t5582-fetch-negative-refspec.sh +12
-0+ git checkout bogus/fetched && + test_commit extra && + + git -C one config --unset-all remote.origin.fetch && + git -C one config --unset branch.main.remote && + git -C one config remote.origin.fetch "+refs/tags/extra" && + git -C one config remote.origin.skipfetchall true && + git -C one config remote.origin.tagopt "--no-tags" && + + git -C one fetch --prefetch +' + test_doneSignature · 1 line
gitgitgadget
Re: [PATCH v4 2/4] fetch: add --prefetch option
Quoted 203 lines · Tom Saeger
Quoted 149 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The --prefetch option will be used by the 'prefetch' maintenance task instead of sending refspecs explicitly across the command-line. The intention is to modify the refspec to place all results in refs/prefetch/ instead of anywhere else.
Create helper method
filter_prefetch_refspec()to modify a given refspec to fit the rules expected of the prefetch task:* Negative refspecs are preserved.
* Refspecs without a destination are removed.
* Refspecs whose source starts with "refs/tags/" are removed.
* Other refspecs are placed within "refs/prefetch/".Finally, we add the 'force' option to ensure that prefetch refs are replaced as necessary.
There are some interesting cases that are worth testing.
An earlier version of this change dropped the "i--" from the loop that deletes a refspec item and shifts the remaining entries down. This allowed some refspecs to not be modified. The subtle part about the first --prefetch test is that the "refs/tags/*" refspec appears directly before the "refs/heads/bogus/*" refspec. Without that "i--", this ordering would remove the "refs/tags/*" refspec and leave the last one unmodified, placing the result in "refs/heads/*".
It is possible to have an empty refspec. This is typically the case for remotes other than the origin, where users want to fetch a specific tag or branch. To correctly test this case, we need to further remove the upstream remote for the local branch. Thus, we are testing a refspec that will be deleted, leaving nothing to fetch.
Helped-by Tom Saeger <[email protected]>Helped-by Ramsay Jones <[email protected]>Signed-off-by Derrick Stolee <[email protected]>--- Documentation/fetch-options.txt | 5 +++ builtin/fetch.c | 59 ++++++++++++++++++++++++++++++- t/t5582-fetch-negative-refspec.sh | 43 ++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-)Documentation/fetch-options.txt +5
-0@@ -110,6 +110,11 @@ ifndef::git-pull[] setting `fetch.writeCommitGraph`. endif::git-pull[] +--prefetch:: + Modify the configured refspec to place all refs into the + `refs/prefetch/` namespace. See the `prefetch` task in + linkgit:git-maintenance[1]. + -p:: --prune:: Before fetching, remove any remote-tracking references that nobuiltin/fetch.c +57
-0@@ -48,6 +48,7 @@ enum {static int fetch_prune_config =-1 ;/* unspecified */ static int fetch_show_forced_updates =1 ;static uint64_t forced_updates_ms =0 ; +static int prefetch =0 ;static int prune =-1 ;/* unspecified */ #define PRUNE_BY_DEFAULT 0/* do we prune by default? */ @@ -158,6 +159,8 @@ static struct option builtin_fetch_options[] = { N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),OPT_INTEGER ('j' ,"jobs" , &max_jobs,N_ ("number of submodules fetched in parallel" )), +OPT_BOOL (0 ,"prefetch" , &prefetch, +N_ ("modify the refspec to place all refs within refs/prefetch/" )),OPT_BOOL ('p' ,"prune" , &prune,N_ ("prune remote-tracking branches no longer on remote" )), OPT_BOOL('P', "prune-tags", &prune_tags,@@ -436,6 +439,56 @@ static void find_non_local_tags(const struct ref *refs,oidset_clear (&fetch_oids); } +static void filter_prefetch_refspec (struct refspec *rs) +{ +int i; + +if (!prefetch) +return ; + +for (i =0 ; i < rs->nr; i++) { +struct strbuf new_dst =STRBUF_INIT ; +char *old_dst; +const char *sub =NULL ; + +if (rs->items[i].negative) +continue ; +if (!rs->items[i].dst || + (rs->items[i].src && + !strncmp (rs->items[i].src,"refs/tags/" ,10 ))) { +int j; + +free (rs->items[i].src); +free (rs->items[i].dst); + +for (j = i +1 ; j < rs->nr; j++) { + rs->items[j -1 ] = rs->items[j]; + rs->raw[j -1 ] = rs->raw[j]; + } + rs->nr--; + i--; +continue ; + } + + old_dst = rs->items[i].dst; +strbuf_addstr (&new_dst,"refs/prefetch/" ); + +/* +* If old_dst starts with "refs/", then place +* sub after that prefix. Otherwise, start at +* the beginning of the string. +*/ +if (!skip_prefix (old_dst,"refs/" , &sub)) + sub = old_dst; +strbuf_addstr (&new_dst, sub); + + rs->items[i].dst =strbuf_detach (&new_dst,NULL ); + rs->items[i].force =1 ; + +free (old_dst); + } +} + static struct ref *get_ref_map(struct remote *remote, const struct ref *remote_refs, struct refspec *rs,@@ -452,6 +505,10 @@ static struct ref *get_ref_map(struct remote *remote,struct hashmap existing_refs;int existing_refs_populated =0 ; +filter_prefetch_refspec (rs); +if (remote) +filter_prefetch_refspec (&remote->fetch); +if (rs->nr) {struct refspec *fetch_refspec;builtin/fetch.c static struct ref *get_ref_map(struct remote *remote, · line 580
Quoted 5 lines
builtin/fetch.c +1
-1@@ -520,7 +577,7 @@ static struct ref *get_ref_map(struct remote *remote,if (has_merge && !strcmp (branch->remote_name, remote->name))add_merge_config (&ref_map, remote_refs, branch, &tail); - }else { + }else if (!prefetch) {That works for me.
Quoted 32 lines
builtin/fetch.c +0
-0ref_map =get_remote_ref (remote_refs,"HEAD" );if (!ref_map)die (_ ("Couldn't find remote ref HEAD" ));t/t5582-fetch-negative-refspec.sh +21
-0@@ -240,4 +240,47 @@ test_expect_success "push with matching +: and negative refspec" ' git -C two push -v one ' +test_expect_success '--prefetch correctly modifies refspecs' ' + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && + git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" && + + git tag -a -m never never-fetch-tag HEAD && + + git branch bogus/fetched HEAD~1 && + git branch bogus/ignore HEAD && + + git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore && + + # correctly handle when refspec set becomes empty + # after removing the refs/tags/* refspec. + git -C one config --unset-all remote.origin.fetch && + git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" && +t/t5582-fetch-negative-refspec.sh test_expect_success "push with matching +: and negative refspec" ' · line 273
Quoted 10 lines
t/t5582-fetch-negative-refspec.sh +10
-0+ git -C one fetch --prefetch --no-tags && + test_must_fail git -C one rev-parse never-fetch-tag && + + # The refspec for refs that are not fully qualified + # are filtered multiple times. + git -C one rev-parse refs/prefetch/bogus/fetched && + test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore +' + +test_expect_success '--prefetch succeeds when refspec becomes empty' 'technically this will get skipped based only on "skipfetchall" right?
Quoted 4 lines
The remote could have an empty-set of refspecs or multiple
valid refspecs postfilter_prefetch_refspec, but the remote gets skipped altogether.perhaps '--prefetch succeeds when remote.skipfetchall is true' '
Forget what I said here. Now seeing this is using 'git fetch --prefetch'
directly and not using mainenance --task=prefetch
Quoted 21 lines
anyway this is looking pretty solid
Reviewed-by Tom Saeger <[email protected]>Quoted 16 lines
t/t5582-fetch-negative-refspec.sh +12
-0+ git checkout bogus/fetched && + test_commit extra && + + git -C one config --unset-all remote.origin.fetch && + git -C one config --unset branch.main.remote && + git -C one config remote.origin.fetch "+refs/tags/extra" && + git -C one config remote.origin.skipfetchall true && + git -C one config remote.origin.tagopt "--no-tags" && + + git -C one fetch --prefetch +' + test_doneSignature · 1 line
gitgitgadget
PATCH v4 3/4 maintenance: use 'git fetch --prefetch'
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
The 'prefetch' maintenance task previously forced the following refspec for each remote:
+refs/heads/*:refs/prefetch/<remote>/*
If a user has specified a more strict refspec for the remote, then this prefetch task downloads more objects than necessary.
The previous change introduced the '--prefetch' option to 'git fetch' which manipulates the remote's refspec to place all resulting refs into refs/prefetch/, with further partitioning based on the destinations of those refspecs.
Update the documentation to be more generic about the destination refs. Do not mention custom refspecs explicitly, as that does not need to be highlighted in this documentation. The important part of placing refs in refs/prefetch/ remains.
--- Documentation/git-maintenance.txt | 6 ++---- builtin/gc.c | 7 +++---- t/t7900-maintenance.sh | 14 +++++++------- 3 files changed, 12 insertions(+), 15 deletions(-)
Documentation/git-maintenance.txt +2 -4
@@ -92,10 +92,8 @@ commit-graph:: prefetch:: The `prefetch` 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. + command is run. The configured refspec is modified to place all + requested refs within `refs/prefetch/`. Also, tags are not updated. + This is done to avoid disrupting the remote-tracking branches. The end users expect these refs to stay unmoved unless they initiate a fetch. With prefetch
builtin/gc.c +3 -4
@@ -879,15 +879,14 @@ static int fetch_remote(struct remote *remote, void *cbdata)struct child_process child =CHILD_PROCESS_INIT ; child.git_cmd =1 ; -strvec_pushl (&child.args,"fetch" , remote->name,"--prune" ,"--no-tags" , +strvec_pushl (&child.args,"fetch" , remote->name, +"--prefetch" ,"--prune" ,"--no-tags" ,"--no-write-fetch-head" ,"--recurse-submodules=no" , -"--refmap=" ,NULL ); +NULL );if (opts->quiet)strvec_push (&child.args,"--quiet" ); -strvec_pushf (&child.args,"+refs/heads/*:refs/prefetch/%s/*" , remote->name); -return !!run_command (&child); }
t/t7900-maintenance.sh +7 -7
@@ -141,15 +141,15 @@ test_expect_success 'prefetch multiple remotes' ' test_commit -C clone1 one && test_commit -C clone2 two && GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null && - fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" && - test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt && - test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt && + fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" && + test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt && + test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt && test_path_is_missing .git/refs/remotes && - git log prefetch/remote1/one && - git log prefetch/remote2/two && + git log prefetch/remotes/remote1/one && + git log prefetch/remotes/remote2/two && git fetch --all && - test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one && - test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two && + test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one && + test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two && test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log &&
Signature · 1 line
gitgitgadget
PATCH v4 4/4 maintenance: respect remote.*.skipFetchAll
In reply to Derrick Stolee via GitGitGadgetFrom: Derrick Stolee <[email protected]>
If a remote has the skipFetchAll setting enabled, then that remote is not intended for frequent fetching. It makes sense to not fetch that data during the 'prefetch' maintenance task. Skip that remote in the iteration without error. The skip_default_update member is initialized in remote.c:handle_config() as part of initializing the 'struct remote'.
--- builtin/gc.c | 3 +++ t/t7900-maintenance.sh | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-)
builtin/gc.c +3 -0
@@ -878,6 +878,9 @@ static int fetch_remote(struct remote *remote, void *cbdata)struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +if (remote->skip_default_update) +return 0 ; + child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prefetch", "--prune", "--no-tags",
t/t7900-maintenance.sh +7 -1
@@ -153,7 +153,13 @@ test_expect_success 'prefetch multiple remotes' ' test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && - ! grep "prefetch" log + ! grep "prefetch" log && + + test_when_finished git config --unset remote.remote1.skipFetchAll && + git config remote.remote1.skipFetchAll true && + GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null && + test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt && + test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt ' test_expect_success 'prefetch and existing log.excludeDecoration values' '
Signature · 1 line
gitgitgadget
Re: [PATCH v4 4/4] maintenance: respect remote.*.skipFetchAll
Quoted 37 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
If a remote has the skipFetchAll setting enabled, then that remote is not intended for frequent fetching. It makes sense to not fetch that data during the 'prefetch' maintenance task. Skip that remote in the iteration without error. The
skip_default_updatemember is initialized in remote.c:handle_config() as part of initializing the 'struct remote'.Signed-off-by Derrick Stolee <[email protected]>--- builtin/gc.c | 3 +++ t/t7900-maintenance.sh | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-)builtin/gc.c +3
-0@@ -878,6 +878,9 @@ static int fetch_remote(struct remote *remote, void *cbdata)struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +if (remote->skip_default_update) +return 0 ; + child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prefetch", "--prune", "--no-tags",t/t7900-maintenance.sh +0
-1@@ -153,7 +153,13 @@ test_expect_success 'prefetch multiple remotes' ' test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && - ! grep "prefetch" log
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 165
Quoted 10 lines
t/t7900-maintenance.sh +7
-0+ ! grep "prefetch" log && + + test_when_finished git config --unset remote.remote1.skipFetchAll && + git config remote.remote1.skipFetchAll true && + GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null && + test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt && + test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt ' test_expect_success 'prefetch and existing log.excludeDecoration values' '
Without having read the code I'd have very much expected a "remote.*.skipFetchAll" to impact:
git fetch --all
Or:
git remote update --all # --all does not exist yet
As e.g. remote.<name>.skipDefaultUpdate would do (i.e. impact "git remote update" ...).
I suspect naming it like this started as a hack around the lack of 4-level .ini config keys, i.e. so we could do:
maintenance.remote.<name>.skipFetchAll = true
But I wonder if we couldn't give this a less confusing name still, even the pseudo-command form of:
maintenanceRemote.<name>.skipFetchAll = true
Or something...
Re: [PATCH v4 4/4] maintenance: respect remote.*.skipFetchAll
Quoted 49 lines · Ævar Arnfjörð Bjarmason
Quoted 46 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
If a remote has the skipFetchAll setting enabled, then that remote is not intended for frequent fetching. It makes sense to not fetch that data during the 'prefetch' maintenance task. Skip that remote in the iteration without error. The
skip_default_updatemember is initialized in remote.c:handle_config() as part of initializing the 'struct remote'.Signed-off-by Derrick Stolee <[email protected]>--- builtin/gc.c | 3 +++ t/t7900-maintenance.sh | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-)builtin/gc.c +3
-0@@ -878,6 +878,9 @@ static int fetch_remote(struct remote *remote, void *cbdata)struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +if (remote->skip_default_update) +return 0 ; + child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prefetch", "--prune", "--no-tags",t/t7900-maintenance.sh +7
-1@@ -153,7 +153,13 @@ test_expect_success 'prefetch multiple remotes' ' test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && - ! grep "prefetch" log + ! grep "prefetch" log && + + test_when_finished git config --unset remote.remote1.skipFetchAll && + git config remote.remote1.skipFetchAll true && + GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null && + test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt && + test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt '
Quoted 6 lines
t/t7900-maintenance.sh test_expect_success 'prefetch multiple remotes' ' · line 165
Quoted 1 line
t/t7900-maintenance.sh +0
-0test_expect_success 'prefetch and existing log.excludeDecoration values' 'Without having read the code I'd have very much expected a "remote.*.skipFetchAll" to impact:
git fetch --all
'skipFetchAll' indeed impacts 'git fetch --all'
But this patch doesn't add "skipFetchAll", instead it just honors that config if set during 'git maintenance --task=prefetch'
See v3 discussion: https://lore.kernel.org/git/[email protected]/
Quoted 11 lines
Or:
git remote update --all # --all does not exist yet
As e.g. remote.<name>.skipDefaultUpdate would do (i.e. impact "git remote update" ...).
I suspect naming it like this started as a hack around the lack of 4-level .ini config keys, i.e. so we could do:
Quoted 9 lines
maintenance.remote.<name>.skipFetchAll = true
But I wonder if we couldn't give this a less confusing name still, even the pseudo-command form of:
maintenanceRemote.<name>.skipFetchAll = true
Or something...
Whether or not additional maintenance specific configs are desired, that might be something to consider. I've thought about this for a few of my repos which have remotes which require a VPN connection. Perhaps
I want to skip those during 'prefetch'? Or maybe instead define a 'remotes.prefetch' group and only prefetch remotes listed?
That all said - I wouldn't hold-up this patch series for it.
--Tom
Re: [PATCH v4 4/4] maintenance: respect remote.*.skipFetchAll
In reply to Derrick Stolee via GitGitGadgetQuoted 9 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
If a remote has the skipFetchAll setting enabled, then that remote is not intended for frequent fetching. It makes sense to not fetch that data during the 'prefetch' maintenance task. Skip that remote in the iteration without error. The
skip_default_updatemember is initialized in remote.c:handle_config() as part of initializing the 'struct remote'.Signed-off-by Derrick Stolee <[email protected]>
Quoted 10 lines
--- builtin/gc.c | 3 +++ t/t7900-maintenance.sh | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-)builtin/gc.c +0
-0
builtin/gc.c static int fetch_remote(struct remote *remote, void *cbdata) · line 883
Quoted 6 lines
builtin/gc.c +3
-0@@ -878,6 +878,9 @@ static int fetch_remote(struct remote *remote, void *cbdata)struct maintenance_run_opts *opts = cbdata;struct child_process child =CHILD_PROCESS_INIT ; +if (remote->skip_default_update) +return 0 ; +
Well that is way easier than doing it in 'fetch'.
Quoted 24 lines
builtin/gc.c +0
-0child.git_cmd =1 ; strvec_pushl(&child.args, "fetch", remote->name, "--prefetch", "--prune", "--no-tags",t/t7900-maintenance.sh +7
-1@@ -153,7 +153,13 @@ test_expect_success 'prefetch multiple remotes' ' test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && - ! grep "prefetch" log + ! grep "prefetch" log && + + test_when_finished git config --unset remote.remote1.skipFetchAll && + git config remote.remote1.skipFetchAll true && + GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null && + test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt && + test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt ' test_expect_success 'prefetch and existing log.excludeDecoration values' 'Signature · 1 line
gitgitgadget