Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b130a9b
Add --worktree flag to gh pr checkout
tidy-dev Jul 22, 2026
975faa3
Refine PR checkout worktree flag help
tidy-dev Jul 22, 2026
3b7f5ab
tidying..
tidy-dev Jul 22, 2026
9fc654e
Run submodule commands inside the worktree for pr checkout
tidy-dev Jul 22, 2026
9f14d1a
Simplify submodule worktree prefix to inline conditional
tidy-dev Jul 22, 2026
927f2dd
Preserve no-force safety when reusing a worktree for fork PRs
tidy-dev Jul 22, 2026
359fd10
Extract authenticatedCommand helper to dedupe -C handling
tidy-dev Jul 22, 2026
a5eea13
Create branch when reusing a worktree with a new --branch name
tidy-dev Jul 22, 2026
688751d
Harden worktree submodule prefixing and cover cmd.Dir stripping
tidy-dev Jul 22, 2026
57c67f1
Cover detach-reuse, worktree fetch dir, and symlink path resolution
tidy-dev Jul 23, 2026
0b5ccd1
Merge branch 'trunk' into tidy-dev-pr-checkout-worktree
tidy-dev Jul 23, 2026
9dd2235
Address review: restore TODO, flatten detachCmds, guard worktree symlink
tidy-dev Jul 29, 2026
c7adcce
Detect worktrees via git rev-parse and reject the current worktree
tidy-dev Jul 29, 2026
c891429
Fix worktree toplevel stub to match Windows absolute paths
tidy-dev Jul 29, 2026
d17503e
Resolve worktree target once instead of re-querying git
tidy-dev Jul 29, 2026
4678dcf
Trim redundant comments and clarify worktree field names
tidy-dev Jul 29, 2026
95863ce
Drop docs on self-explanatory worktree helpers
tidy-dev Jul 29, 2026
08b5bf2
Return ok bool from revParseFacts to satisfy nilerr
tidy-dev Jul 29, 2026
f9e0ab3
Clarify current-worktree rejection message
tidy-dev Jul 29, 2026
36a30c5
Bail out early on unusable --worktree paths
tidy-dev Jul 29, 2026
83ebb8c
test(pr/checkout): add acceptance tests for worktree checkout
babakks Aug 3, 2026
d2f477e
chore(pr checkout): polish worktree related tests
babakks Aug 4, 2026
8181d21
docs(skills): mention pr checkout worktree support
babakks Aug 4, 2026
cd635d8
docs(pr/checkout): add worktree usage example to help text
babakks Aug 4, 2026
5e6aa5a
fix(pr/checkout): pass -- before worktree path so dash paths work
babakks Aug 4, 2026
980366b
Match worktree rev-parse stub against absolute path on Windows
tidy-dev Aug 4, 2026
8f7afbd
Merge pull request #13946 from cli/tidy-dev-pr-checkout-worktree
tidy-dev Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions acceptance/testdata/pr/pr-checkout-worktree-detach.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Checkout a PR into a worktree with a detached HEAD, then reuse that worktree.

# Set up env vars
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}

# Use gh as a credential helper
exec gh auth setup-git

# Create a repository with a file so it has a default branch
exec gh repo create ${ORG}/${REPO} --add-readme --private

# Defer repo cleanup
defer gh repo delete --yes ${ORG}/${REPO}

# Clone the repo
exec gh repo clone ${ORG}/${REPO}

# Prepare a branch to PR
cd ${REPO}
exec git checkout -b feature-branch
exec git commit --allow-empty -m 'Empty Commit'
exec git push -u origin feature-branch

# Create the PR
exec gh pr create --title 'Feature Title' --body 'Feature Body'
stdout2env PR_URL

# Return to the default branch
exec git checkout main

# Checkout the PR into a fresh worktree with a detached HEAD
exec gh pr checkout ${PR_URL} --detach --worktree ../wt
exists ../wt

# The worktree HEAD is detached, so it has no symbolic ref
! exec git -C ../wt symbolic-ref -q HEAD

# The detached HEAD points at the PR head commit
exec git -C ../wt rev-parse HEAD
stdout2env WT_HEAD
exec git rev-parse origin/feature-branch
stdout ${WT_HEAD}

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'

# Checking out again into the same worktree reuses it and stays detached
exec gh pr checkout ${PR_URL} --detach --worktree ../wt
! exec git -C ../wt symbolic-ref -q HEAD
exec git -C ../wt rev-parse HEAD
stdout ${WT_HEAD}

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'
55 changes: 55 additions & 0 deletions acceptance/testdata/pr/pr-checkout-worktree-from-fork.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Checkout a fork PR whose head repository is not configured as a remote into a
# worktree, then reuse that worktree.

# Set up env vars
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}

# Use gh as a credential helper
exec gh auth setup-git

# Create a repository with a file so it has a default branch
exec gh repo create ${ORG}/${REPO} --add-readme --private

# Defer upstream cleanup
defer gh repo delete --yes ${ORG}/${REPO}

# Create a fork
exec gh repo fork ${ORG}/${REPO} --org ${ORG} --fork-name ${REPO}-fork
sleep 5

# Defer fork cleanup
defer gh repo delete --yes ${ORG}/${REPO}-fork

# Clone both repos
exec gh repo clone ${ORG}/${REPO}
exec gh repo clone ${ORG}/${REPO}-fork

# Prepare a branch to PR in the fork itself
cd ${REPO}-fork
exec git checkout -b feature-branch
exec git commit --allow-empty -m 'Empty Commit'
exec git push -u origin feature-branch

exec gh repo set-default ${ORG}/${REPO}-fork
exec gh pr create --title 'Feature Title' --body 'Feature Body'
stdout2env PR_URL

# From the upstream clone, where the fork is not a remote, check out the PR into a worktree
cd ${WORK}/${REPO}
exec gh pr checkout ${PR_URL} --worktree ../wt
exists ../wt
exec git -C ../wt rev-parse --abbrev-ref HEAD
stdout '(?m)^feature-branch$'

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'

# Reusing the same worktree for the same fork PR works
exec gh pr checkout ${PR_URL} --worktree ../wt
exec git -C ../wt rev-parse --abbrev-ref HEAD
stdout '(?m)^feature-branch$'

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'
135 changes: 135 additions & 0 deletions acceptance/testdata/pr/pr-checkout-worktree.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Checkout a PR into a git worktree, then reuse that worktree, rename its branch,
# force sync it, and add a second worktree once the local branch already exists.

# Set up env vars
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}

# Use gh as a credential helper
exec gh auth setup-git

# Create a repository with a file so it has a default branch
exec gh repo create ${ORG}/${REPO} --add-readme --private

# Defer repo cleanup
defer gh repo delete --yes ${ORG}/${REPO}

# Clone the repo
exec gh repo clone ${ORG}/${REPO}

# Prepare a branch to PR
cd ${REPO}
exec git checkout -b feature-branch
exec git commit --allow-empty -m 'Empty Commit'
exec git push -u origin feature-branch

# Create the PR
exec gh pr create --title 'Feature Title' --body 'Feature Body'
stdout2env PR_URL

# Remove the local branch so checkout has to create it from the remote
exec git checkout main
exec git branch -D feature-branch
stdout 'Deleted branch feature-branch'

# Checkout the PR into a fresh worktree
exec gh pr checkout ${PR_URL} --worktree ../wt
exists ../wt

# The worktree is on the PR branch
exec git -C ../wt rev-parse --abbrev-ref HEAD
stdout '(?m)^feature-branch$'

# The main working copy stays on the default branch
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'

# Checking out the same PR into the same worktree again reuses it
exec gh pr checkout ${PR_URL} --worktree ../wt
exec git -C ../wt rev-parse --abbrev-ref HEAD
stdout '(?m)^feature-branch$'

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'

# Checking out into the reused worktree with a new branch name creates that branch
exec gh pr checkout ${PR_URL} --worktree ../wt --branch renamed-branch
exec git -C ../wt rev-parse --abbrev-ref HEAD
stdout '(?m)^renamed-branch$'

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'

# Give the worktree's PR branch a local commit so it diverges from the PR head.
# A plain reuse would fast-forward-only merge and keep this commit, so --force is
# required to discard it with a hard reset.
exec git -C ../wt checkout feature-branch
exec git -C ../wt commit --allow-empty -m 'Diverging local commit'

# Force checking out the PR into the reused worktree hard resets it to the PR head
exec gh pr checkout ${PR_URL} --worktree ../wt --force
exec git -C ../wt rev-parse --abbrev-ref HEAD
stdout '(?m)^feature-branch$'

# The diverging local commit was discarded by the hard reset
exec git -C ../wt log -1 --format=%s
! stdout 'Diverging local commit'

# The worktree branch now matches the PR head
exec git -C ../wt rev-parse HEAD
stdout2env WT_HEAD
exec git rev-parse origin/feature-branch
stdout ${WT_HEAD}

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'

# With the local branch now present, removing the worktree and checking out into a
# fresh path adds a new worktree for the existing branch
exec git worktree remove ../wt
exec gh pr checkout ${PR_URL} --worktree ../wt2
exists ../wt2
exec git -C ../wt2 rev-parse --abbrev-ref HEAD
stdout '(?m)^feature-branch$'

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'

# Now create a two-sided divergence: advance the remote PR branch with a pushed
# commit the local branch will not have...
exec git -C ../wt2 commit --allow-empty -m 'Remote commit'
exec git -C ../wt2 push origin feature-branch

# ...then rewind the local branch and give it a different, local-only commit, so
# neither branch is an ancestor of the other
exec git -C ../wt2 reset --hard HEAD~1
exec git -C ../wt2 commit --allow-empty -m 'Local commit'

# A non-force checkout cannot fast-forward across the divergence and fails
! exec gh pr checkout ${PR_URL} --worktree ../wt2
stderr 'Not possible to fast-forward'

# The local-only commit is still there because the failed sync changed nothing
exec git -C ../wt2 log -1 --format=%s
stdout 'Local commit'

# Forcing the checkout hard resets the branch to the advanced PR head
exec gh pr checkout ${PR_URL} --worktree ../wt2 --force
exec git -C ../wt2 rev-parse --abbrev-ref HEAD
stdout '(?m)^feature-branch$'
exec git -C ../wt2 log -1 --format=%s
stdout 'Remote commit'
! stdout 'Local commit'

# The worktree branch now matches the advanced PR head
exec git -C ../wt2 rev-parse HEAD
stdout2env WT2_HEAD
exec git rev-parse origin/feature-branch
stdout ${WT2_HEAD}

# The main working copy is left untouched
exec git rev-parse --abbrev-ref HEAD
stdout '(?m)^main$'
Loading
Loading