Skip to content

Conversation

@suft
Copy link
Contributor

@suft suft commented Nov 3, 2024

Check list

  • I have performed a self-review of my code
  • I have commented my code in hard-to-understand areas
  • I have made corresponding changes to the documentation

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:

  • main for looking at the pristine code
  • work for looking at my code
  • review for looking at someone else’s code
  • background for my computer to look at my code
    • operates in the detached head state
  • scratch for everything else
. (bare repo)
├── background/ (* worktree)
├── config
├── description
├── HEAD
├── hooks/
├── info/
├── logs/
├── main/ (* worktree)
├── objects/
├── packed-refs
├── refs/
├── review/ (* worktree)
├── rr-cache/
├── scratch/ (*worktree)
├── work/(* worktree)
└── worktrees/

Example of worktrees in a bare clone
Screenshot 2024-11-13 at 7 29 51 PM

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?)

  • locking a worktree - gwl
  • unlocking a worktree - gwu
  • removing a worktree - gwr
  • jumping to a worktree - gwj
    • similar to switching branches
    • involves a combination of selecting a result of git worktree list and cd into that result (not a native git operation)

Screenshots

bash (repo with short history) - gwj
FORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color'
Screenshot 2024-11-04 at 8 24 20 PM

fish (repo with mid-length history) - gwj
FORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color'
Screenshot 2024-11-10 at 11 41 59 AM

bash (repo with mid-length history) - gwj
FORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color --max-count=100'
Screenshot 2024-11-10 at 12 46 05 PM

Closes #399.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Breaking change
  • Documentation change

Test environment

  • Shell
    • bash
    • zsh
    • fish
  • OS
    • Linux
    • Mac OS X
    • Windows
    • Others:

@carlfriedrich
Copy link
Collaborator

@suft Thanks for your contribution! Is this ready for review or did you make it a draft on purpose?

@suft
Copy link
Contributor Author

suft commented Nov 4, 2024

@carlfriedrich I made it draft on purpose. Still have a few things to adjust.

[[ "$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[@]}" --
Copy link
Contributor Author

@suft suft Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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

@suft suft force-pushed the worktrees branch 3 times, most recently from cae816c to 525441f Compare November 10, 2024 19:44
@suft suft marked this pull request as ready for review November 10, 2024 19:45
Copy link
Collaborator

@sandr01d sandr01d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@suft suft requested a review from sandr01d November 14, 2024 00:32
@carlfriedrich
Copy link
Collaborator

@suft Great implementation so far, @sandr01d great review. This will be a good addition to forgit.

Copy link
Collaborator

@sandr01d sandr01d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_jump does 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=1 and 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_flags to do so.

@cjappl
Copy link
Collaborator

cjappl commented Nov 15, 2024

(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 )

@suft suft marked this pull request as draft November 18, 2024 00:09
@suft suft marked this pull request as ready for review December 18, 2024 19:08
@suft suft requested a review from sandr01d December 18, 2024 19:08
@suft suft marked this pull request as draft December 18, 2024 19:24
@suft suft marked this pull request as ready for review December 18, 2024 19:50
@suft
Copy link
Contributor Author

suft commented Dec 18, 2024

Thanks for the changes @suft, looks good to me so far. Let me summarize the things that still need to be addressed:

The root worktree doesn't show up as the first selection, so --header-lines=1 will affect a different worktree (which I'm guessing is not the behaviour we want), unless we reverse the fzf item list.

Screenshot 2024-12-18 at 3 02 14 PM

@suft suft changed the title feat: add interactive git worktree operations Feat: add interactive git worktree operations Dec 18, 2024
@suft suft marked this pull request as draft December 24, 2024 20:44
@stale
Copy link

stale bot commented Jun 26, 2025

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jun 26, 2025
@carlfriedrich
Copy link
Collaborator

@suft @sandr01d Are you planning to finish this PR to get it merged anytime?

@stale stale bot removed the stale label Jun 29, 2025
@suft
Copy link
Contributor Author

suft commented Jun 29, 2025

@suft @sandr01d Are you planning to finish this PR to get it merged anytime?

Sorry for the delay — things have been a bit hectic on my end. I’m hoping to take another look at it sometime this week, but totally understand if it makes sense to close the PR in the meantime.

@suft suft force-pushed the worktrees branch 2 times, most recently from 086ac51 to 80e5066 Compare July 1, 2025 15:11
@suft suft marked this pull request as ready for review July 1, 2025 16:10
@sandr01d
Copy link
Collaborator

Sorry for the late reply @suft. Just wanted to let you know that I saw your changes and will give you a review. Unfortunately I don't have much free time right now, so it might take a while until I get to it.

Copy link
Collaborator

@sandr01d sandr01d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few more things we need to resolve. I'll do another functional review once done. The issue with the main worktree being present in the list of gwl is also not resoved.
Please do not close my comments, just let me know once you're done and I will review and close them. Closing them from your side makes it difficult for me to know where we last left off and what needs to be addressed.

Comment on lines +1250 to +1256
local tree opts max_args
max_args=$(_forgit_worktree_lock_max_args)

if [[ $# -ge "$max_args" ]] || { [[ $# -ne 0 ]] && _forgit_all_non_flags "$@"; }; then
git worktree lock "$@"
return $?
fi
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the idea of checking for the number of allowed arguments for the command using --help. If the user provides invalid arguments it is fine to just pass them to git like we do everywhere else. Git will print a descriptive error message. Please try to keep things as simple as possible. Depending on the output of --help is not a good idea in general either, as this could easily change between different git version.

usage=$(git worktree lock --help 2>/dev/null | grep -E 'usage: git worktree lock' | head -n1)
usage=${usage#*:}
rest=$(echo "$usage" | awk '{for(i=4;i<=NF;++i)printf "%s ",$i; print ""}')
# shellcheck disable=SC2206
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should be removed anyway, but as a general note, please don't just disable shellchecks. If you're having issues with something, feel free to ask, but don't just turn them off.

Comment on lines +1304 to +1308
if [[ $# -ne 0 ]]; then
git worktree unlock "$@"
worktree_unlock_status=$?
return $worktree_unlock_status
fi
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified

Suggested change
if [[ $# -ne 0 ]]; then
git worktree unlock "$@"
worktree_unlock_status=$?
return $worktree_unlock_status
fi
[[ $# -ne 0 ]] && {
git worktree unlock "$@"
return $?
}


_forgit_worktree_select() {
_forgit_inside_work_tree || _forgit_inside_git_dir || return 1
local worktree_list count tree opts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep things consistent with the rest of the code, I would prefer to name this variable to worktrees. This applies to all functions.

Suggested change
local worktree_list count tree opts
local worktrees count tree opts

worktree_list=$(git worktree list | grep -vE "prunable$" | awk '{print $1}' | _forgit_filter_existing_paths)

count=$(echo "$worktree_list" | wc -l)
[[ $count -eq 1 ]] && return 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should let the user know why we refuse to run a function, e.g. echo "No worktrees to select" in this specific case. This applies to all your functions.

fi
}

_forgit_all_non_flags() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_forgit_all_non_flags() {
_forgit_only_non_flags() {

Comment on lines +22 to +23
# --porcelain usually paired with -z but not needed since we use awk
_alternative "worktrees:worktree:($(git worktree list --porcelain | awk '/worktree/ {print $2}'))"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use -z here, as this allows handling worktrees that contain a newline character.

Suggested change
# --porcelain usually paired with -z but not needed since we use awk
_alternative "worktrees:worktree:($(git worktree list --porcelain | awk '/worktree/ {print $2}'))"
__gitcomp_nl "$(git worktree list --porcelain -z | awk 'BEGIN {RS="\0"} /worktree/ {print $2}')"

The same applies to the zsh completions for this command.


- **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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
+ `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, no equivalent behavior using forgit as a git subcommand

Comment on lines +179 to +182
- **Interactive `git commit --squash && git rebase -i --autosquash` selector** (`gsq`)

- **Interactive `git commit --fixup=reword && git rebase -i --autosquash` selector** (`grw`)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not belong here


- **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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
+ `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.

Comment on lines +319 to +322
| `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` |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The *_PREVIEW_GIT_OPTS do not exist and don't belong here.

local worktree_list tree opts

worktree_list=$(git worktree list | grep -v "(bare)" | grep -E "locked$")
count=$(echo "$worktree_list" | wc -l)
Copy link
Collaborator

@sandr01d sandr01d Aug 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$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:

Suggested change
count=$(echo "$worktree_list" | wc -l)
count=$(echo "$worktree_list" | grep -c .)

This applies to multiple functions.

Comment on lines +1241 to +1245
_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[@]}" "$@"
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
_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[@]}" %
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add git worktree switcher like gcb

4 participants