Move internal state dir to 'common' subdir in git path. (Fixes bug with multiple worktrees) - #222
Move internal state dir to 'common' subdir in git path. (Fixes bug with multiple worktrees)#222morganwahl wants to merge 1 commit into
Conversation
baguilarq
commented
Nov 3, 2021
Hope we have response from the maintainers |
alerque
commented
Jan 11, 2023
@AGWA Any chance this can get reviewed and included? I'm starting to use more worktree based things myself and this needs to be fixed. |
maxisam
commented
Nov 11, 2024
I created a build here. https://github.com/maxisam/git-crypt/actions/runs/11787683064 It works great and also works with this #105 |
jwaldrip
commented
Dec 11, 2024
Any chance we can get this merged soon? |
kqvanity
commented
Mar 15, 2025
I applied this patch, but still facing the same issue. Being unable to create a workree in an unlocked state. $ git worktree add newtree2
Preparing worktree (checking out 'newtree2')
git-crypt: Error: Unable to open key file - have you unlocked/initialized this repository yet?
error: external filter '"git-crypt" smudge' failed 1
error: external filter '"git-crypt" smudge' failed
fatal: .config/gh/.env: smudge filter git-crypt failed
$ uname -a
Linux nixos 6.6.76 #1-NixOS SMP PREEMPT_DYNAMIC Sat Feb 8 08:52:39 UTC 2025 x86_64 GNU/Linux
$ ls /nix/store | grep --ignore-case 'git-crypt-0.7.0$'
14xknggr1bqwqpcia8dqgjp4l4s8yi73-git-crypt-0.7.0 |
SAGV
commented
Dec 28, 2025
Oh, would be so nice to have it merged so we can run multiple Claude sessions on our project… |
franzwilhelm
commented
Jan 5, 2026
I just sent this to @AGWA on email, hoping to get a review and merge :) |
It's really a shame that git-crypt is not easily compatible with worktrees, especially considering how useful they are with tools like claude-code. At least there's a way to work around this problem when using claude-code via hooks: To {
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "bash scripts/worktree-create.sh"
}
]
}
],
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "bash scripts/worktree-remove.sh"
}
]
}
]
}
}Then the scripts: scripts/worktree-create.sh: #!/usr/bin/env bashset -euo pipefail
INPUT=$(cat)
NAME=$(echo "$INPUT"| jq -r '.name')
CWD=$(echo "$INPUT"| jq -r '.cwd')
WORKTREE_DIR="$CWD/.claude/worktrees/$NAME"if [[ -d$WORKTREE_DIR ]];thenecho"$WORKTREE_DIR"exit 0
fi
mkdir -p "$(dirname "$WORKTREE_DIR")"
git worktree add --no-checkout -b "$NAME""$WORKTREE_DIR">&2
WORKTREE_ADMIN="$CWD/.git/worktrees/$(basename "$WORKTREE_DIR")"
cp -r "$CWD/.git/git-crypt""$WORKTREE_ADMIN/git-crypt"
git -C "$WORKTREE_DIR" reset HEAD >&2
git -C "$WORKTREE_DIR" checkout .>&2echo"$WORKTREE_DIR"scripts/worktree-remove.sh #!/usr/bin/env bashset -euo pipefail
INPUT=$(cat)
WORKTREE_PATH=$(echo "$INPUT"| jq -r '.worktree_path')
MAIN_GIT_DIR=$(git -C "$WORKTREE_PATH" rev-parse --path-format=absolute --git-common-dir)
MAIN_REPO_DIR=$(dirname "$MAIN_GIT_DIR")
MAIN_HEAD=$(git -C "$MAIN_REPO_DIR" rev-parse HEAD)
WORKTREE_HEAD=$(git -C "$WORKTREE_PATH" rev-parse HEAD)
HAS_DIFF=false
if! git -C "$WORKTREE_PATH" diff --quiet 2>/dev/null;then
HAS_DIFF=true
fiif! git -C "$WORKTREE_PATH" diff --cached --quiet 2>/dev/null;then
HAS_DIFF=true
fiif [[ $(git -C "$WORKTREE_PATH" rev-list "$MAIN_HEAD..$WORKTREE_HEAD" --count 2>/dev/null)!="0" ]];then
HAS_DIFF=true
fiif [[ $HAS_DIFF==true ]];thenecho"Worktree has changes, keeping at $WORKTREE_PATH">&2exit 0
fi
BRANCH=$(git -C "$WORKTREE_PATH" rev-parse --abbrev-ref HEAD 2>/dev/null ||echo"")
git worktree remove --force "$WORKTREE_PATH"2>/dev/null || rm -rf "$WORKTREE_PATH"
git -C "$MAIN_REPO_DIR" worktree prune 2>/dev/null ||trueif [[ -n$BRANCH&&$BRANCH!="HEAD" ]];then
git -C "$MAIN_REPO_DIR" branch -D "$BRANCH"2>/dev/null ||truefiWhen exiting claude code, it will remove the worktree if there are no changes (new commits, unstaged changes etc.), otherwise it preserve the worktree, but occasionally it may still prompt whether to keep the worktree (probably only when there are unstaged changes) but keep the worktree regardless of how you answer (unfortunately there does not seem to be a way around this currently). |
mischadiehm
commented
Mar 26, 2026
any chance this goes in soon? its a major drawback for many of us or do we need to move to a fork? |
SAGV
commented
Mar 26, 2026
Is there a good trusted fork by any chance? |
PR manifest (prs.txt), build script, and smoke test suite for rebuilding git-crypt with 4 unmerged upstream PRs: AGWA#311 (small files), AGWA#222 (worktrees), AGWA#180 (merge driver), AGWA#210 (empty files). Build runs in a disposable worktree, merges PRs in order, compiles, runs 12 end-to-end tests, and copies the binary out. Never touches the working tree or installs anything. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This allows unlocked repos to work correctly with multiple worktrees.
When doing
git worktree add newtree2in an "unlocked" repo, I get the following output and creating the new worktree fails.By putting
stracein front of the smudge command in.git/config, I see:Git is calling the smudge command when checking out the new working tree, which then fails to find the key file because
git rev-parseuses a separate dir for each worktree. Hence theworktrees/newtree2in the path. The command fails because there is nogit-cryptdir in.git/worktrees/newtree2.git rev-parse --git-pathshould be used instead ofgit rev-parse --git-dir, based on https://git-scm.com/docs/git-worktree#_details :More importantly,
git-cryptshould be using thecommonsubdir of the git dir to store in-use keys, since those need to be shared across workdirs. https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt-common