Feat: add interactive git worktree operations#402
Conversation
|
@suft Thanks for your contribution! Is this ready for review or did you make it a draft on purpose? |
|
@carlfriedrich I made it draft on purpose. Still have a few things to adjust. |
bin/git-forgit
Outdated
| [[ "$sha" == "(bare)" ]] && return | ||
| # the trailing '--' ensures that this works for branches that have a name | ||
| # that is identical to a file | ||
| git log "$sha" "${_forgit_log_preview_options[@]}" -- |
There was a problem hiding this comment.
- I should probably add preview options
FORGIT_WORKTREE_PREVIEW_GIT_OPTS(like Add *_PREVIEW_GIT_OPTS variables #396) - I've noticed this can run a bit slow (on some computers) if you have a repo with a long history because it attempts to get the entire history for the branch checked out in that worktree
- It would be good to see what others think when they test this out
- Would be quicker if I limit the log entries in the worktree preview options
cae816c to
525441f
Compare
sandr01d
left a comment
There was a problem hiding this comment.
Thanks for you work @suft! I think this is going to be a nice improvement. There are a few things that need to be adjusted. In some of my comments I've pinged the other maintainers for their opinions. Please hold back on implementing those, as those comments are opinionated and I'd like to have feedback from the others first before deciding in which direction to go.
sandr01d
left a comment
There was a problem hiding this comment.
Thanks for the changes @suft, looks good to me so far. Let me summarize the things that still need to be addressed:
- The default preview should look the same as for other commands that use
git log(#402 (comment)) _forgit_worktree_jumpdoes not change the directory when forgit is used as a git subcommand (#402 (comment))- The root worktree should not be selectable with
gwl. We can make it non-interactable with--header-lines=1and also bring this back in the other functions that you used this with. Don't mind my earlier comment regarding this. (#402 (comment)) - We should add completions for the new commands (#402 (comment))
- We should allow passing additional arguments to the underlying git commands (#402 (comment)). The logic for detecting whether the git command should be executed immediately without using fzf needs to be adjusted to do so. Instead of testing if any arguments were passed, we need to test whether non flag arguments were passed. You can use
_forgit_contains_non_flagsto do so.
|
(it looks like there is very thorough reviewing going on here, please ping me if you need another pair of eyes, otherwise I completely defer to @sandr01d and @carlfriedrich ) |
The root worktree doesn't show up as the first selection, so
|
README.md
Outdated
| - **Interactive `git commit --squash && git rebase -i --autosquash` selector** (`gsq`) | ||
|
|
||
| - **Interactive `git commit --fixup=reword && git rebase -i --autosquash` selector** (`grw`) | ||
|
|
There was a problem hiding this comment.
This does not belong here
README.md
Outdated
|
|
||
| - **Interactive `git worktree list` selector** (`gws`/`gwj`) | ||
|
|
||
| + `gwj` jumps to the worktree using `cd` and can only be used via the alias, no equivalent behavior using forgit as a git subcommand |
There was a problem hiding this comment.
| + `gwj` jumps to the worktree using `cd` and can only be used via the alias, no equivalent behavior using forgit as a git subcommand | |
| + `gwj` jumps to the worktree using `cd` and can only be used via the alias, there is no equivalent behavior when using forgit as a git subcommand. |
README.md
Outdated
| | `gws`/`gwj` | `FORGIT_WORKTREE_PREVIEW_GIT_OPTS` | | ||
| | `gwl` | `FORGIT_WORKTREE_LOCK_GIT_OPTS`, `FORGIT_WORKTREE_PREVIEW_GIT_OPTS` | | ||
| | `gwr` | `FORGIT_WORKTREE_REMOVE_GIT_OPTS`, `FORGIT_WORKTREE_PREVIEW_GIT_OPTS` | | ||
| | `gwu` | `FORGIT_WORKTREE_PREVIEW_GIT_OPTS` | |
There was a problem hiding this comment.
The *_PREVIEW_GIT_OPTS do not exist and don't belong here.
bin/git-forgit
Outdated
| local worktree_list tree opts | ||
|
|
||
| worktree_list=$(git worktree list | grep -v "(bare)" | grep -E "locked$") | ||
| count=$(echo "$worktree_list" | wc -l) |
There was a problem hiding this comment.
$count is 1 when there are actually no locked workspaces, because echo adds a newline (which is counted by wc). We can work around it using grep instead:
| count=$(echo "$worktree_list" | wc -l) | |
| count=$(echo "$worktree_list" | grep -c .) |
This applies to multiple functions.
bin/git-forgit
Outdated
| _forgit_git_worktree_lock() { | ||
| _forgit_worktree_lock_git_opts=() | ||
| _forgit_parse_array _forgit_worktree_lock_git_opts "$FORGIT_WORKTREE_LOCK_GIT_OPTS" | ||
| git worktree lock "${_forgit_worktree_lock_git_opts[@]}" "$@" | ||
| } |
There was a problem hiding this comment.
Would be nice to allow operating on multiple worktrees at the same time. Should be easy to implement by allowing multiselect in fzf and modifiying the _forgit_git_worktree_* functions like this:
| _forgit_git_worktree_lock() { | |
| _forgit_worktree_lock_git_opts=() | |
| _forgit_parse_array _forgit_worktree_lock_git_opts "$FORGIT_WORKTREE_LOCK_GIT_OPTS" | |
| git worktree lock "${_forgit_worktree_lock_git_opts[@]}" "$@" | |
| } | |
| _forgit_git_worktree_lock() { | |
| local trees | |
| trees=$1 | |
| shift | |
| _forgit_worktree_lock_git_opts=() | |
| _forgit_parse_array _forgit_worktree_lock_git_opts "$FORGIT_WORKTREE_LOCK_GIT_OPTS" | |
| echo "$trees" | xargs -I% git worktree lock "${_forgit_worktree_lock_git_opts[@]}" % | |
| } |
|
Hey @suft. Are you going to continue working on this? Otherwise somebody else might adopt this PR in order to make it merge-ready. |
|
Hey @suft, thank you so much for all the work you've put into this PR — the worktree integration is a feature more and more users have been looking forward to. I noticed it's been a while since the last update. Just wanted to check in: are you still interested in continuing to work on this? No pressure at all — I completely understand that life gets busy! If you'd prefer to hand it off or if I don't hear back by this weekend, I'd be happy to pick it up from here and get it across the finish line. Either way, your contribution is greatly appreciated and will be properly credited. Thanks again! |
Thanks for checking in! Life has indeed gotten busy on my end, so I'd really appreciate you taking this over. Feel free to use whatever parts are helpful from my work. Looking forward to seeing this feature land! |
|
https://mikebian.co/using-git-worktrees-for-parallel-ai-agent-development/ this zsh script is very forgit-like and has been working well for me. |
Add six new git worktree commands with fzf integration: - worktree (gw): Browse/list worktrees with preview - worktree_jump (gwj): Select and cd into worktree - worktree_delete (gwd): Interactive multi-select deletion - worktree_move (gwm): Interactive move with path input - worktree_lock (gwl): Interactive multi-select locking - worktree_unlock (gwu): Interactive multi-select unlocking Features: - Support for bare repositories - Preview showing git status and recent commits - Ctrl-Y to copy worktree path to clipboard - Multi-select support for delete/lock/unlock - Dirty worktree warning in delete preview - Shell integration for zsh, bash, and fish - Tab completions for all three shells - Full README documentation Co-Authored-By: Sufien Tout <sufientout@gmail.com>
- Add _forgit_branch_list to list branches with current branch first
- Add _forgit_extract_branch_name to remove 2-char prefix from git branch output
- Replace 5 occurrences of `cut -c3-` and `awk '{print $1}'` with the helper
- Fix preview commands to use {} instead of {1} for full line matching
- Display header line (current branch) in gray for visual distinction
The previous `LC_ALL=C sort -k1.1,1.1 -rs` approach didn't work reliably
because git branch has three prefixes: '*' (current), '+' (worktree), ' '
(other), and their ASCII order conflicts with the desired display order.
Redesign worktree list format to show colored branch name (cyan), relative commit time (gray), and lock status (yellow). Simplify preview to show only colorized git status and commit log.
Deduplicate the ANSI-stripping sed pattern into _forgit_strip_ansi and adopt the [XY] bracket prefix style (consistent with ga) for worktree list output. The Y position now encodes both lock (L) and prunable (P) states, with L taking precedence when both present.
The separate `gw` (browse) and `gwj` (jump) worktree commands were redundant — `gwj` was a strict superset that called `gw` internally then `cd`'d into the result. Users almost always want to jump into the selected worktree, not just print its path. Merge both into a single `gwt` alias that selects a worktree and `cd`s into it. The raw path output remains available via `git forgit worktree` for scripting use cases.
The interactive worktree move adds little value over plain `git worktree move` — unlike browse/delete, there is no multi-select or preview benefit that justifies the wrapper.
- Use `_forgit_branch_list` and `_forgit_extract_branch_name` in `_forgit_switch_branch` so it stays consistent with `_forgit_checkout_branch` and other branch selectors - Remove duplicate `_forgit_worktree_delete_preview` since it is identical to `_forgit_worktree_preview` - Add `ctrl-y` (yank path) binding to worktree delete selector for parity with the worktree browser
- Fix detached HEAD causing fzf to swallow first branch candidate: always emit a header line, showing the detached SHA when no current branch exists - Fix `_forgit_strip_ansi` breaking on macOS: replace GNU-only `\x1b` with portable ANSI-C quoting - Fix `gwt` wrapper returning error when arguments are passed (e.g. `gwt add`): short-circuit to passthrough in both zsh/bash and fish plugins - Fix `_forgit_extract_branch_name` failing on ANSI-coded and symbolic ref (`->`) lines from `git branch --all` - Filter out symbolic ref lines (`remotes/origin/HEAD ->`) from branch list to prevent them as fzf candidates - Add bashunit tests for `_forgit_extract_branch_name`, `_forgit_strip_ansi`, and `_forgit_extract_worktree_path`
Deduplicate the ANSI-stripping sed pattern into _forgit_strip_ansi and adopt the [XY] bracket prefix style (consistent with ga) for worktree list output. The Y position now encodes both lock (L) and prunable (P) states, with L taking precedence when both present.
|
Thanks again to @suft for the original work and idea on this PR! I've taken over and done a reimplementation based on the original proposal and the review feedback from @sandr01d and @carlfriedrich. Also thanks to @iloveitaly for providing the link, which helped improve the display format of the worktree list. First — my sincere apologies, @suft. I accidentally force-pushed to your branch, which overwrote your original commits. I'm really sorry about that. Hopefully you still have a local copy of your original implementation. Again, very sorry for the mishap. Summary of the current implementationTwo new commands:
Worktree browser features
Also included
Differences from the original proposal
The main design shift: instead of multiple standalone commands (lock/unlock/jump/remove), the current approach uses a single browser with keybindings for in-place operations + a separate delete command, keeping the alias namespace smaller. I've done a thorough self-review already, but given the scope of this PR — new feature + refactoring of existing branch-related code — I'd appreciate another round of review. @cjappl @carlfriedrich @sandr01d Would you mind taking a look when you have time? And @suft, your input would also be very valuable given all the thought you put into the original design! |


Check list
Description
I recently adopted using multiple fixed worktrees as part of my workflow to help with productivity.
Each worktree is used for a different type of concurrent activity:
mainfor looking at the pristine codeworkfor looking at my codereviewfor looking at someone else’s codebackgroundfor my computer to look at my codescratchfor everything elseExample of worktrees in a bare clone

Another approach is to use worktrees as a replacement of, or a supplement to git branches. Instead of switching branches, you just change directories. So that would involve creating a new worktree and branch, then delete the worktree upon merging.
Worktree Operations (should we stick with these abbreviations?)
gwlgwugwrgwjgit worktree listandcdinto that result (not a native git operation)Screenshots
bash (repo with short history) -

gwjFORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color'fish (repo with mid-length history) -

gwjFORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color'bash (repo with mid-length history) -

gwjFORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color --max-count=100'Closes #399.
Type of change
Test environment