diff options
-rw-r--r-- | Documentation/config/extensions.txt | 31 | ||||
-rw-r--r-- | Documentation/git-config.txt | 8 | ||||
-rw-r--r-- | Documentation/git-sparse-checkout.txt | 16 | ||||
-rw-r--r-- | Documentation/git-worktree.txt | 11 | ||||
-rw-r--r-- | builtin/sparse-checkout.c | 28 | ||||
-rw-r--r-- | builtin/worktree.c | 63 | ||||
-rw-r--r-- | config.c | 39 | ||||
-rw-r--r-- | config.h | 9 | ||||
-rw-r--r-- | sparse-index.c | 10 | ||||
-rwxr-xr-x | t/t1091-sparse-checkout-builtin.sh | 35 | ||||
-rwxr-xr-x | t/t2400-worktree-add.sh | 58 | ||||
-rw-r--r-- | worktree.c | 73 | ||||
-rw-r--r-- | worktree.h | 21 |
13 files changed, 353 insertions, 49 deletions
diff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt index 4e23d73cdc..bccaec7a96 100644 --- a/Documentation/config/extensions.txt +++ b/Documentation/config/extensions.txt @@ -6,3 +6,34 @@ extensions.objectFormat:: Note that this setting should only be set by linkgit:git-init[1] or linkgit:git-clone[1]. Trying to change it after initialization will not work and will produce hard-to-diagnose issues. + +extensions.worktreeConfig:: + If enabled, then worktrees will load config settings from the + `$GIT_DIR/config.worktree` file in addition to the + `$GIT_COMMON_DIR/config` file. Note that `$GIT_COMMON_DIR` and + `$GIT_DIR` are the same for the main working tree, while other + working trees have `$GIT_DIR` equal to + `$GIT_COMMON_DIR/worktrees/<id>/`. The settings in the + `config.worktree` file will override settings from any other + config files. ++ +When enabling `extensions.worktreeConfig`, you must be careful to move +certain values from the common config file to the main working tree's +`config.worktree` file, if present: ++ +* `core.worktree` must be moved from `$GIT_COMMON_DIR/config` to + `$GIT_COMMON_DIR/config.worktree`. +* If `core.bare` is true, then it must be moved from `$GIT_COMMON_DIR/config` + to `$GIT_COMMON_DIR/config.worktree`. ++ +It may also be beneficial to adjust the locations of `core.sparseCheckout` +and `core.sparseCheckoutCone` depending on your desire for customizable +sparse-checkout settings for each worktree. By default, the `git +sparse-checkout` builtin enables `extensions.worktreeConfig`, assigns +these config values on a per-worktree basis, and uses the +`$GIT_DIR/info/sparse-checkout` file to specify the sparsity for each +worktree independently. See linkgit:git-sparse-checkout[1] for more +details. ++ +For historical reasons, `extensions.worktreeConfig` is respected +regardless of the `core.repositoryFormatVersion` setting. diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 2285effb36..bdcfd94b64 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -141,9 +141,13 @@ from all available files. See also <<FILES>>. --worktree:: - Similar to `--local` except that `.git/config.worktree` is + Similar to `--local` except that `$GIT_DIR/config.worktree` is read from or written to if `extensions.worktreeConfig` is - present. If not it's the same as `--local`. + enabled. If not it's the same as `--local`. Note that `$GIT_DIR` + is equal to `$GIT_COMMON_DIR` for the main working tree, but is of + the form `$GIT_DIR/worktrees/<id>/` for other working trees. See + linkgit:git-worktree[1] to learn how to enable + `extensions.worktreeConfig`. -f <config-file>:: --file <config-file>:: diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt index b81dbe0654..94dad137b9 100644 --- a/Documentation/git-sparse-checkout.txt +++ b/Documentation/git-sparse-checkout.txt @@ -31,13 +31,21 @@ COMMANDS Describe the patterns in the sparse-checkout file. 'set':: - Enable the necessary config settings - (extensions.worktreeConfig, core.sparseCheckout, - core.sparseCheckoutCone) if they are not already enabled, and - write a set of patterns to the sparse-checkout file from the + Enable the necessary sparse-checkout config settings + (`core.sparseCheckout`, `core.sparseCheckoutCone`, and + `index.sparse`) if they are not already set to the desired values, + and write a set of patterns to the sparse-checkout file from the list of arguments following the 'set' subcommand. Update the working directory to match the new patterns. + +To ensure that adjusting the sparse-checkout settings within a worktree +does not alter the sparse-checkout settings in other worktrees, the 'set' +subcommand will upgrade your repository config to use worktree-specific +config if not already present. The sparsity defined by the arguments to +the 'set' subcommand are stored in the worktree-specific sparse-checkout +file. See linkgit:git-worktree[1] and the documentation of +`extensions.worktreeConfig` in linkgit:git-config[1] for more details. ++ When the `--stdin` option is provided, the patterns are read from standard in as a newline-delimited list instead of from the arguments. + diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 9e862fbcf7..b8d53c4830 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -286,8 +286,8 @@ CONFIGURATION FILE ------------------ By default, the repository `config` file is shared across all working trees. If the config variables `core.bare` or `core.worktree` are -already present in the config file, they will be applied to the main -working trees only. +present in the common config file and `extensions.worktreeConfig` is +disabled, then they will be applied to the main working tree only. In order to have configuration specific to working trees, you can turn on the `worktreeConfig` extension, e.g.: @@ -307,11 +307,16 @@ them to the `config.worktree` of the main working tree. You may also take this opportunity to review and move other configuration that you do not want to share to all working trees: - - `core.worktree` and `core.bare` should never be shared + - `core.worktree` should never be shared. + + - `core.bare` should not be shared if the value is `core.bare=true`. - `core.sparseCheckout` is recommended per working tree, unless you are sure you always use sparse checkout for all working trees. +See the documentation of `extensions.worktreeConfig` in +linkgit:git-config[1] for more details. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index a311483a7d..f2356554e5 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -15,6 +15,7 @@ #include "wt-status.h" #include "quote.h" #include "sparse-index.h" +#include "worktree.h" static const char *empty_base = ""; @@ -361,26 +362,23 @@ enum sparse_checkout_mode { static int set_config(enum sparse_checkout_mode mode) { - const char *config_path; - - if (upgrade_repository_format(1) < 0) - die(_("unable to upgrade repository format to enable worktreeConfig")); - if (git_config_set_gently("extensions.worktreeConfig", "true")) { - error(_("failed to set extensions.worktreeConfig setting")); + /* Update to use worktree config, if not already. */ + if (init_worktree_config(the_repository)) { + error(_("failed to initialize worktree config")); return 1; } - config_path = git_path("config.worktree"); - git_config_set_in_file_gently(config_path, - "core.sparseCheckout", - mode ? "true" : NULL); - - git_config_set_in_file_gently(config_path, - "core.sparseCheckoutCone", - mode == MODE_CONE_PATTERNS ? "true" : NULL); + if (repo_config_set_worktree_gently(the_repository, + "core.sparseCheckout", + mode ? "true" : "false") || + repo_config_set_worktree_gently(the_repository, + "core.sparseCheckoutCone", + mode == MODE_CONE_PATTERNS ? + "true" : "false")) + return 1; if (mode == MODE_NO_PATTERNS) - set_sparse_index_config(the_repository, 0); + return set_sparse_index_config(the_repository, 0); return 0; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 0d0809276f..e23e6343d0 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -335,6 +335,69 @@ static int add_worktree(const char *path, const char *refname, strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); + /* + * 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/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. + */ + if (repository_format_worktree_config) { + char *from_file = git_pathdup("config.worktree"); + char *to_file = xstrfmt("%s/config.worktree", + sb_repo.buf); + + if (file_exists(from_file)) { + struct config_set cs = { { 0 } }; + const char *core_worktree; + int bare; + + if (safe_create_leading_directories(to_file) || + copy_file(to_file, from_file, 0666)) { + error(_("failed to copy worktree config from '%s' to '%s'"), + from_file, to_file); + goto worktree_copy_cleanup; + } + + git_configset_init(&cs); + git_configset_add_file(&cs, from_file); + + if (!git_configset_get_bool(&cs, "core.bare", &bare) && + bare && + 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", &core_worktree) && + 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); + } + +worktree_copy_cleanup: + free(from_file); + free(to_file); + } + strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; @@ -21,6 +21,7 @@ #include "dir.h" #include "color.h" #include "refs.h" +#include "worktree.h" struct config_source { struct config_source *prev; @@ -2294,8 +2295,8 @@ int git_configset_get_string(struct config_set *cs, const char *key, char **dest return 1; } -int git_configset_get_string_tmp(struct config_set *cs, const char *key, - const char **dest) +static int git_configset_get_string_tmp(struct config_set *cs, const char *key, + const char **dest) { const char *value; if (!git_configset_get_value(cs, key, &value)) { @@ -3000,6 +3001,20 @@ int git_config_set_gently(const char *key, const char *value) return git_config_set_multivar_gently(key, value, NULL, 0); } +int repo_config_set_worktree_gently(struct repository *r, + const char *key, const char *value) +{ + /* Only use worktree-specific config if it is is already enabled. */ + if (repository_format_worktree_config) { + char *file = repo_git_path(r, "config.worktree"); + int ret = git_config_set_multivar_in_file_gently( + file, key, value, NULL, 0); + free(file); + return ret; + } + return repo_config_set_multivar_gently(r, key, value, NULL, 0); +} + void git_config_set(const char *key, const char *value) { git_config_set_multivar(key, value, NULL, 0); @@ -3297,14 +3312,28 @@ void git_config_set_multivar_in_file(const char *config_filename, int git_config_set_multivar_gently(const char *key, const char *value, const char *value_pattern, unsigned flags) { - return git_config_set_multivar_in_file_gently(NULL, key, value, value_pattern, - flags); + return repo_config_set_multivar_gently(the_repository, key, value, + value_pattern, flags); +} + +int repo_config_set_multivar_gently(struct repository *r, const char *key, + const char *value, + const char *value_pattern, unsigned flags) +{ + char *file = repo_git_path(r, "config"); + int res = git_config_set_multivar_in_file_gently(file, + key, value, + value_pattern, + flags); + free(file); + return res; } void git_config_set_multivar(const char *key, const char *value, const char *value_pattern, unsigned flags) { - git_config_set_multivar_in_file(NULL, key, value, value_pattern, + git_config_set_multivar_in_file(git_path("config"), + key, value, value_pattern, flags); } @@ -267,6 +267,13 @@ void git_config_set_in_file(const char *, const char *, const char *); int git_config_set_gently(const char *, const char *); /** + * Write a config value that should apply to the current worktree. If + * extensions.worktreeConfig is enabled, then the write will happen in the + * current worktree's config. Otherwise, write to the common config file. + */ +int repo_config_set_worktree_gently(struct repository *, const char *, const char *); + +/** * write config values to `.git/config`, takes a key/value pair as parameter. */ void git_config_set(const char *, const char *); @@ -294,6 +301,7 @@ int git_config_parse_key(const char *, char **, size_t *); int git_config_set_multivar_gently(const char *, const char *, const char *, unsigned); void git_config_set_multivar(const char *, const char *, const char *, unsigned); +int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned); int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, unsigned); /** @@ -466,7 +474,6 @@ void git_configset_clear(struct config_set *cs); int git_configset_get_value(struct config_set *cs, const char *key, const char **dest); int git_configset_get_string(struct config_set *cs, const char *key, char **dest); -int git_configset_get_string_tmp(struct config_set *cs, const char *key, const char **dest); int git_configset_get_int(struct config_set *cs, const char *key, int *dest); int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest); int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); diff --git a/sparse-index.c b/sparse-index.c index 08f54747bb..fdbe97b976 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -99,13 +99,9 @@ static int convert_to_sparse_rec(struct index_state *istate, int set_sparse_index_config(struct repository *repo, int enable) { - int res; - char *config_path = repo_git_path(repo, "config.worktree"); - res = git_config_set_in_file_gently(config_path, - "index.sparse", - enable ? "true" : NULL); - free(config_path); - + int res = repo_config_set_worktree_gently(repo, + "index.sparse", + enable ? "true" : "false"); prepare_repo_settings(repo); repo->settings.sparse_index = enable; return res; diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index 3592d12442..502d42d183 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -126,7 +126,7 @@ test_expect_success 'switching to cone mode with non-cone mode patterns' ' cd bad-patterns && git sparse-checkout init && git sparse-checkout add dir && - git config core.sparseCheckoutCone true && + git config --worktree core.sparseCheckoutCone true && test_must_fail git sparse-checkout add dir 2>err && grep "existing sparse-checkout patterns do not use cone mode" err ) @@ -155,9 +155,9 @@ test_expect_success 'interaction with clone --no-checkout (unborn index)' ' ' test_expect_success 'set enables config' ' - git init empty-config && + git init worktree-config && ( - cd empty-config && + cd worktree-config && test_commit test file && test_path_is_missing .git/config.worktree && git sparse-checkout set nothing && @@ -210,6 +210,21 @@ test_expect_success 'add to sparse-checkout' ' check_files repo "a folder1 folder2" ' +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="$(git -C worktree rev-parse --git-path info/sparse-checkout)" && + 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 +' + test_expect_success 'cone mode: match patterns' ' git -C repo config --worktree core.sparseCheckoutCone true && rm -rf repo/a repo/folder1 repo/folder2 && @@ -261,7 +276,7 @@ test_expect_success 'sparse-index enabled and disabled' ' test_cmp expect actual && git -C repo config --list >config && - ! grep index.sparse config + test_cmp_config -C repo false index.sparse ' test_expect_success 'cone mode: init and set' ' @@ -524,13 +539,13 @@ test_expect_success 'interaction with submodules' ' ' test_expect_success 'different sparse-checkouts with worktrees' ' + git -C repo sparse-checkout set --cone deep folder1 && git -C repo worktree add --detach ../worktree && - check_files worktree "a deep folder1 folder2" && - git -C worktree sparse-checkout init --cone && - git -C repo sparse-checkout set folder1 && - git -C worktree sparse-checkout set deep/deeper1 && - check_files repo a folder1 && - check_files worktree a deep + check_files worktree "a deep folder1" && + git -C repo sparse-checkout set --cone folder1 && + git -C worktree sparse-checkout set --cone deep/deeper1 && + check_files repo "a folder1" && + check_files worktree "a deep" ' test_expect_success 'set using filename keeps file on-disk' ' diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 37ad79470f..43139af08f 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -165,8 +165,62 @@ 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 && + # Simple check that a Git command does not + # immediately fail with the current setup + git status + ) && + 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 && + + # Add config values that are erroneous to have in + # a config.worktree file outside of the main + # working tree, to check that Git filters them out + # when copying config during "git worktree add". + git config --worktree core.bare true && + git config --worktree core.worktree "$(pwd)" && + + # We want to check that bogus.key is copied + git config --worktree bogus.key value && + git config --unset core.bare && + git worktree add ../there4 main && + cd ../there4 && + + # Simple check that a Git command does not + # immediately fail with the current setup + git status && + git worktree add --detach ../there5 && + cd ../there5 && + 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 && + + cat >expect <<-EOF && + init.t + EOF + + ls there4 >actual && + test_cmp expect actual && + ls there5 >actual && + test_cmp expect actual ' test_expect_success 'checkout with grafts' ' diff --git a/worktree.c b/worktree.c index e8f6f6ae6f..90fc085f76 100644 --- a/worktree.c +++ b/worktree.c @@ -5,6 +5,7 @@ #include "worktree.h" #include "dir.h" #include "wt-status.h" +#include "config.h" void free_worktrees(struct worktree **worktrees) { @@ -821,3 +822,75 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, *wtpath = path; return 0; } + +static int move_config_setting(const char *key, const char *value, + const char *from_file, const char *to_file) +{ + if (git_config_set_in_file_gently(to_file, key, value)) + return error(_("unable to set %s in '%s'"), key, to_file); + if (git_config_set_in_file_gently(from_file, key, NULL)) + return error(_("unable to unset %s in '%s'"), key, from_file); + return 0; +} + +int init_worktree_config(struct repository *r) +{ + int res = 0; + int bare = 0; + struct config_set cs = { { 0 } }; + const char *core_worktree; + char *common_config_file; + char *main_worktree_file; + + /* + * If the extension is already enabled, then we can skip the + * upgrade process. + */ + if (repository_format_worktree_config) + return 0; + if ((res = git_config_set_gently("extensions.worktreeConfig", "true"))) + return error(_("failed to set extensions.worktreeConfig setting")); + + common_config_file = xstrfmt("%s/config", r->commondir); + main_worktree_file = xstrfmt("%s/config.worktree", r->commondir); + + git_configset_init(&cs); + git_configset_add_file(&cs, common_config_file); + + /* + * If core.bare is true in the common config file, then we need to + * move it to the main worktree's config file or it will break all + * worktrees. If it is false, then leave it in place because it + * _could_ be negating a global core.bare=true. + */ + if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) { + if ((res = move_config_setting("core.bare", "true", + common_config_file, + main_worktree_file))) + goto cleanup; + } + /* + * If core.worktree is set, then the main worktree is located + * somewhere different than the parent of the common Git dir. + * Relocate that value to avoid breaking all worktrees with this + * upgrade to worktree config. + */ + if (!git_configset_get_value(&cs, "core.worktree", &core_worktree)) { + if ((res = move_config_setting("core.worktree", core_worktree, + common_config_file, + main_worktree_file))) + goto cleanup; + } + + /* + * Ensure that we use worktree config for the remaining lifetime + * of the current process. + */ + repository_format_worktree_config = 1; + +cleanup: + git_configset_clear(&cs); + free(common_config_file); + free(main_worktree_file); + return res; +} diff --git a/worktree.h b/worktree.h index 9e06fcbdf3..e9e839926b 100644 --- a/worktree.h +++ b/worktree.h @@ -183,4 +183,25 @@ void strbuf_worktree_ref(const struct worktree *wt, struct strbuf *sb, const char *refname); +/** + * Enable worktree config for the first time. This will make the following + * adjustments: + * + * 1. Add extensions.worktreeConfig=true in the common config file. + * + * 2. If the common config file has a core.worktree value, then that value + * is moved to the main worktree's config.worktree file. + * + * 3. If the common config file has a core.bare enabled, then that value + * is moved to the main worktree's config.worktree file. + * + * If extensions.worktreeConfig is already true, then this method + * terminates early without any of the above steps. The existing config + * arrangement is assumed to be intentional. + * + * Returns 0 on success. Reports an error message and returns non-zero + * if any of these steps fail. + */ +int init_worktree_config(struct repository *r); + #endif |