Thread (121 messages) flat view 121 messages, 8 authors, 2022-02-16

Re: [PATCH v5 5/5] worktree: copy sparse-checkout patterns and config on add

From: Eric Sunshine <hidden>
Date: 2022-02-06 11:30:20

On Mon, Jan 31, 2022 at 10:01 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted hunk ↗ jump to hunk
When adding a new worktree, it is reasonable to expect that we want to
use the current set of sparse-checkout settings for that new worktree.
This is particularly important for repositories where the worktree would
become too large to be useful. This is even more important when using
partial clone as well, since we want to avoid downloading the missing
blobs for files that should not be written to the new worktree.

The only way to create such a worktree without this intermediate step of
expanding the full worktree is to copy the sparse-checkout patterns and
config settings during 'git worktree add'. Each worktree has its own
sparse-checkout patterns, and the default behavior when the
sparse-checkout file is missing is to include all paths at HEAD. Thus,
we need to have patterns from somewhere, they might as well be the
current worktree's patterns. These are then modified independently in
the future.

In addition to the sparse-checkout file, copy the worktree config file
if worktree config is enabled and the file exists. This will copy over
any important settings to ensure the new worktree behaves the same as
the current one. The only exception we must continue to make is that
core.bare and core.worktree should become unset in the worktree's config
file.

Signed-off-by: Derrick Stolee <redacted>
---
diff --git a/builtin/worktree.c b/builtin/worktree.c
@@ -335,6 +335,66 @@ static int add_worktree(const char *path, const char *refname,
+       /*
+        * If the current worktree has sparse-checkout enabled, then copy
+        * the sparse-checkout patterns from the current worktree.
+        */
+       if (core_apply_sparse_checkout) {
+               char *from_file = git_pathdup("info/sparse-checkout");
+               char *to_file = xstrfmt("%s/worktrees/%s/info/sparse-checkout",
+                                       realpath.buf, name);
I think this is too fragile and may easily be broken by an unrelated
change since `realpath` is a temporary container which gets reused,
thus it holds different paths at different times. For instance, it
first holds the realpath of $GIT_DIR but then later holds the realpath
of $GIT_COMMON_DIR. If someone later comes along and reuses it for
some other path, then the code added by this patch may end up
breaking. To make this robust, you should instead use `sb_repo` which
already has the value "$GIT_DIR/worktrees/<name>":

    char *to_file = xstrfmt(%s/info/sparse-checkout", sb_repo.buf);
+               if (file_exists(from_file)) {
+                       if (safe_create_leading_directories(to_file) ||
+                           copy_file(to_file, from_file, 0666))
+                               error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"),
+                                     from_file, to_file);
+               }
+
+               free(from_file);
+               free(to_file);
+       }
+
+       /*
+        * If we are using worktree config, then copy all current config
+        * values from the current worktree into the new one, that way the
+        * new worktree behaves the same as this one.
+        */
As an aid for future readers, it might be helpful to extend this
content to explain _why_ the new worktree should behave the same as
the current one. Reproducing the important bits from the commit
message here in the comment could make it more useful.
+       if (repository_format_worktree_config) {
+               char *from_file = git_pathdup("config.worktree");
+               char *to_file = xstrfmt("%s/worktrees/%s/config.worktree",
+                                       realpath.buf, name);
Same comment about fragility of using `realpath`. Instead:

    char *to_file = xstrfmt("%s/config.worktree", sb_repo.buf);
+               if (file_exists(from_file)) {
+                       struct config_set cs = { { 0 }};
s/}}/} }/
+                       const char *str_value;
+                       int bool_value;
+
+                       if (safe_create_leading_directories(to_file) ||
+                           copy_file(to_file, from_file, 0666))
+                               die(_("failed to copy worktree config from '%s' to '%s'"),
+                                   from_file, to_file);
All the other error conditions handled by this patch use error() but
this one uses die(). Does this one really warrant aborting the `git
worktree add` operation? Perhaps instead just add a label below this
new chunk of code and `goto` the label (or indent this code further
and avoid adding a label).
+                       git_configset_init(&cs);
+                       git_configset_add_file(&cs, from_file);
+
+                       if (!git_configset_get_bool(&cs, "core.bare", &bool_value) &&
+                           bool_value &&
Nit: This would be slightly easier to understand if you named this
variable `bare` (as you did in patch [2/5]) rather than `bool_value`.
+                           git_config_set_multivar_in_file_gently(
+                                       to_file, "core.bare", NULL, "true", 0))
+                               error(_("failed to unset 'core.bare' in '%s'"), to_file);
+                       if (!git_configset_get_value(&cs, "core.worktree", &str_value) &&
In patch [2/5] you used git_configset_get_string_tmp() to retrieve
this setting, but here you're using git_configset_get_value(). Is
there a reason for the inconsistency?
quoted hunk ↗ jump to hunk
+                           git_config_set_in_file_gently(to_file,
+                                                         "core.worktree", NULL))
+                               error(_("failed to unset 'core.worktree' in '%s'"), to_file);
+
+                       git_configset_clear(&cs);
+               }
+
+               free(from_file);
+               free(to_file);
+       }
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
@@ -201,6 +201,21 @@ test_expect_success 'add to sparse-checkout' '
+test_expect_success 'worktree: add copies sparse-checkout patterns' '
+       cat repo/.git/info/sparse-checkout >old &&
+       test_when_finished cp old repo/.git/info/sparse-checkout &&
+       test_when_finished git -C repo worktree remove ../worktree &&
+       git -C repo sparse-checkout set --no-cone "/*" &&
+       git -C repo worktree add --quiet ../worktree 2>err &&
+       test_must_be_empty err &&
+       new=repo/.git/worktrees/worktree/info/sparse-checkout &&
For robustness, this should be using:

    new=$(git rev-parse --git-path info/sparse-checkout)

to retrieve ".git/worktrees/<id>/info/sparse-checkout" rather than
hard-coding "worktree" for "<id>".
quoted hunk ↗ jump to hunk
+       test_path_is_file $new &&
+       test_cmp repo/.git/info/sparse-checkout $new &&
+       git -C worktree sparse-checkout set --cone &&
+       test_cmp_config -C worktree true core.sparseCheckoutCone &&
+       test_must_fail git -C repo core.sparseCheckoutCone
+'
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index 37ad79470fb..3fb5b21b943 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -165,8 +165,50 @@ test_expect_success '"add" default branch of a bare repo' '
        (
                git clone --bare . bare2 &&
                cd bare2 &&
-               git worktree add ../there3 main
-       )
+               git worktree add ../there3 main &&
+               cd ../there3 &&
+               git status
+       ) &&
Is this some debugging code you forgot to remove or was `git status`
failing due to the bug(s) fixed by this patch series? I'm guessing the
latter since you also use `git status` in more tests below. Anyhow,
it's not very clear what the `git-status` is meant to be testing. An
in-code comment _might_ help. Even better, perhaps, would be to add a
new single-purpose test or a well-named function which explicitly
checks the conditions you want to test (i.e. that git-config doesn't
report core.bare as true or core.worktree as having a value).
+       cat >expect <<-EOF &&
+       init.t
+       EOF
+       ls there3 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"add" to bare repo with worktree config' '
+       (
+               git clone --bare . bare3 &&
+               cd bare3 &&
+               git config extensions.worktreeconfig true &&
+               git config --worktree core.bare true &&
+               git config --worktree core.worktree "$(pwd)" &&
It's not clear to the casual reader why these nonsensical keys are
being added. An in-code comment explaining the reason (i.e. you expect
the `git worktree add` operation to drop them in the new worktree)
would be beneficial for future readers.
+               git config --worktree bogus.key value &&
+               git config --unset core.bare &&
Why is this being unset? (Genuine question. Am I missing something obvious?)
+               git worktree add ../there4 main &&
+               cd ../there4 &&
+               git status &&
+               git worktree add --detach ../there5 &&
+               cd ../there5 &&
+               git status
+       ) &&
Same comment about the purpose of git-status not being obvious. A
well-named function which checks the specific conditions you're
looking for would be more clear than abstruse invocations of
git-status.
+       # the worktree has the arbitrary value copied.
+       test_cmp_config -C there4 value bogus.key &&
+       test_cmp_config -C there5 value bogus.key &&
+
+       # however, core.bare and core.worktree were removed.
+       test_must_fail git -C there4 config core.bare &&
+       test_must_fail git -C there4 config core.worktree &&
This is the comment I was looking for above. It's certainly okay to
have it here, but it was confusing above to not understand the reason
those nonsensical keys were being added.
+       cat >expect <<-EOF &&
+       init.t
+       EOF
+
+       ls there4 >actual &&
+       test_cmp expect actual &&
+       ls there5 >actual &&
+       test_cmp expect actual
 '
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help