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 intoAGWA:masterfrom
Conversation
|
Hope we have response from the maintainers |
|
@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. |
|
I created a build here. https://github.com/maxisam/git-crypt/actions/runs/11787683064 It works great and also works with this #105 |
|
Any chance we can get this merged soon? |
|
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 |
|
Oh, would be so nice to have it merged so we can run multiple Claude sessions on our project… |
|
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 bash
set -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 ]]; then
echo "$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 . >&2
echo "$WORKTREE_DIR"scripts/worktree-remove.sh #!/usr/bin/env bash
set -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
fi
if ! git -C "$WORKTREE_PATH" diff --cached --quiet 2>/dev/null; then
HAS_DIFF=true
fi
if [[ $(git -C "$WORKTREE_PATH" rev-list "$MAIN_HEAD..$WORKTREE_HEAD" --count 2>/dev/null) != "0" ]]; then
HAS_DIFF=true
fi
if [[ $HAS_DIFF == true ]]; then
echo "Worktree has changes, keeping at $WORKTREE_PATH" >&2
exit 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 || true
if [[ -n $BRANCH && $BRANCH != "HEAD" ]]; then
git -C "$MAIN_REPO_DIR" branch -D "$BRANCH" 2>/dev/null || true
fiWhen 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). |
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