worktree: create init_worktree_config()

Upgrading a repository to use extensions.worktreeConfig is non-trivial. There are several steps involved, including moving some config settings from the common config file to the main worktree's config.worktree file. The previous change updated the documentation with all of these details. Commands such as 'git sparse-checkout set' upgrade the repository to use extensions.worktreeConfig without following these steps, causing some user pain in some special cases. Create a helper method, init_worktree_config(), that will be used in a later change to fix this behavior within 'git sparse-checkout set'. The method is carefully documented in worktree.h. Note that we do _not_ upgrade the repository format version to 1 during this process. The worktree config extension must be considered by Git and third-party tools even if core.repositoryFormatVersion is 0 for historical reasons documented in 11664196ac ("Revert "check_repository_format_gently(): refuse extensions for old repositories"", 2020-07-15). This is a special case for this extension, and newer extensions (such as extensions.objectFormat) still need to upgrade the repository format version. Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Reviewed-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Feb 7, 2022 at 21:32 UTC 615a84ad788b26260c5b053ace2d5720ea5f05c5
2 files changed +94
worktree.c
+73
@@ -5,6 +5,7 @@
5 #include "worktree.h"
6 #include "dir.h"
7 #include "wt-status.h"
8 +#include "config.h"
9
10 void free_worktrees(struct worktree **worktrees)
11 {
@@ -826,3 +827,75 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath,
827 *wtpath = path;
828 return 0;
829 }
830 +
831 +static int move_config_setting(const char *key, const char *value,
832 + const char *from_file, const char *to_file)
833 +{
834 + if (git_config_set_in_file_gently(to_file, key, value))
835 + return error(_("unable to set %s in '%s'"), key, to_file);
836 + if (git_config_set_in_file_gently(from_file, key, NULL))
837 + return error(_("unable to unset %s in '%s'"), key, from_file);
838 + return 0;
839 +}
840 +
841 +int init_worktree_config(struct repository *r)
842 +{
843 + int res = 0;
844 + int bare = 0;
845 + struct config_set cs = { { 0 } };
846 + const char *core_worktree;
847 + char *common_config_file;
848 + char *main_worktree_file;
849 +
850 + /*
851 + * If the extension is already enabled, then we can skip the
852 + * upgrade process.
853 + */
854 + if (repository_format_worktree_config)
855 + return 0;
856 + if ((res = git_config_set_gently("extensions.worktreeConfig", "true")))
857 + return error(_("failed to set extensions.worktreeConfig setting"));
858 +
859 + common_config_file = xstrfmt("%s/config", r->commondir);
860 + main_worktree_file = xstrfmt("%s/config.worktree", r->commondir);
861 +
862 + git_configset_init(&cs);
863 + git_configset_add_file(&cs, common_config_file);
864 +
865 + /*
866 + * If core.bare is true in the common config file, then we need to
867 + * move it to the main worktree's config file or it will break all
868 + * worktrees. If it is false, then leave it in place because it
869 + * _could_ be negating a global core.bare=true.
870 + */
871 + if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
872 + if ((res = move_config_setting("core.bare", "true",
873 + common_config_file,
874 + main_worktree_file)))
875 + goto cleanup;
876 + }
877 + /*
878 + * If core.worktree is set, then the main worktree is located
879 + * somewhere different than the parent of the common Git dir.
880 + * Relocate that value to avoid breaking all worktrees with this
881 + * upgrade to worktree config.
882 + */
883 + if (!git_configset_get_value(&cs, "core.worktree", &core_worktree)) {
884 + if ((res = move_config_setting("core.worktree", core_worktree,
885 + common_config_file,
886 + main_worktree_file)))
887 + goto cleanup;
888 + }
889 +
890 + /*
891 + * Ensure that we use worktree config for the remaining lifetime
892 + * of the current process.
893 + */
894 + repository_format_worktree_config = 1;
895 +
896 +cleanup:
897 + git_configset_clear(&cs);
898 + free(common_config_file);
899 + free(main_worktree_file);
900 + return res;
901 +}
worktree.h
+21
@@ -183,4 +183,25 @@ void strbuf_worktree_ref(const struct worktree *wt,
183 struct strbuf *sb,
184 const char *refname);
185
186 +/**
187 + * Enable worktree config for the first time. This will make the following
188 + * adjustments:
189 + *
190 + * 1. Add extensions.worktreeConfig=true in the common config file.
191 + *
192 + * 2. If the common config file has a core.worktree value, then that value
193 + * is moved to the main worktree's config.worktree file.
194 + *
195 + * 3. If the common config file has a core.bare enabled, then that value
196 + * is moved to the main worktree's config.worktree file.
197 + *
198 + * If extensions.worktreeConfig is already true, then this method
199 + * terminates early without any of the above steps. The existing config
200 + * arrangement is assumed to be intentional.
201 + *
202 + * Returns 0 on success. Reports an error message and returns non-zero
203 + * if any of these steps fail.
204 + */
205 +int init_worktree_config(struct repository *r);
206 +
207 #endif