From 02f35ae341963a6dcafead4020fe93280c894f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20St=C3=BCrmer?= Date: Wed, 1 Jul 2026 06:03:06 +0200 Subject: [PATCH 1/2] Fix git-crypt in linked worktrees (per-worktree fallback) --- commands.cpp | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/commands.cpp b/commands.cpp index 6b3c498..af66188 100644 --- a/commands.cpp +++ b/commands.cpp @@ -237,27 +237,50 @@ static void validate_key_name_or_throw (const char* key_name) } } -static std::string get_internal_state_path () +static std::string git_rev_parse (const char* flag) { - // git rev-parse --git-dir std::vector command; command.push_back("git"); command.push_back("rev-parse"); - command.push_back("--git-dir"); + command.push_back(flag); std::stringstream output; if (!successful_exit(exec_command(command, output))) { - throw Error("'git rev-parse --git-dir' failed - is this a Git repository?"); + throw Error(std::string("'git rev-parse ") + flag + "' failed - is this a Git repository?"); } std::string path; std::getline(output, path); - path += "/git-crypt"; - return path; } +static std::string get_internal_state_path () +{ + // The git-crypt state directory (which holds the symmetric keys) lives in + // the shared git directory. In a linked worktree, --git-dir points at the + // per-worktree directory (.git/worktrees/), which does not contain + // the keys. Prefer the per-worktree directory when it has already been + // provisioned (preserving the ability to keep per-worktree state), and + // otherwise fall back to the common git directory shared by every + // worktree. This lets `git worktree add` decrypt transparently instead of + // failing in the smudge filter with "Unable to open key file". + const std::string per_worktree(git_rev_parse("--git-dir") + "/git-crypt"); + if (access(per_worktree.c_str(), F_OK) == 0) { + return per_worktree; + } + + const std::string common(git_rev_parse("--git-common-dir") + "/git-crypt"); + if (access(common.c_str(), F_OK) == 0) { + return common; + } + + // Neither exists yet (e.g. during `git-crypt init`); create under the + // per-worktree directory, matching historical behaviour. In the main + // worktree --git-dir and --git-common-dir are identical. + return per_worktree; +} + static std::string get_internal_keys_path (const std::string& internal_state_path) { return internal_state_path + "/keys"; From 6e368c4ea974dbcaf2a3d4d691fe8ebfc385a57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20St=C3=BCrmer?= Date: Wed, 1 Jul 2026 08:44:32 +0200 Subject: [PATCH 2/2] Create initial git-crypt state in common dir, not per-worktree --- commands.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/commands.cpp b/commands.cpp index af66188..b65be43 100644 --- a/commands.cpp +++ b/commands.cpp @@ -275,10 +275,12 @@ static std::string get_internal_state_path () return common; } - // Neither exists yet (e.g. during `git-crypt init`); create under the - // per-worktree directory, matching historical behaviour. In the main - // worktree --git-dir and --git-common-dir are identical. - return per_worktree; + // Neither exists yet (e.g. during `git-crypt init`, or the first unlock in + // a linked worktree); create under the common git directory shared by every + // worktree so that the key is reachable from all of them. In the main + // worktree --git-dir and --git-common-dir are identical, so this matches + // historical behaviour there. + return common; } static std::string get_internal_keys_path (const std::string& internal_state_path)