-
Notifications
You must be signed in to change notification settings - Fork 150
Feat: add interactive git worktree operations #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
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. |
| [[ "$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.
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
cae816c to
525441f
Compare
sandr01d
left a comment
There was a problem hiding this 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.
sandr01d
left a comment
There was a problem hiding this 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_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
|
|
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. |
086ac51 to
80e5066
Compare
|
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. |
sandr01d
left a comment
There was a problem hiding this 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.
| 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| if [[ $# -ne 0 ]]; then | ||
| git worktree unlock "$@" | ||
| worktree_unlock_status=$? | ||
| return $worktree_unlock_status | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| _forgit_all_non_flags() { | |
| _forgit_only_non_flags() { |
| # --porcelain usually paired with -z but not needed since we use awk | ||
| _alternative "worktrees:worktree:($(git worktree list --porcelain | awk '/worktree/ {print $2}'))" |
There was a problem hiding this comment.
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.
| # --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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| + `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 |
| - **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.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| + `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. |
| | `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.
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) |
There was a problem hiding this comment.
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:
| count=$(echo "$worktree_list" | wc -l) | |
| count=$(echo "$worktree_list" | grep -c .) |
This applies to multiple functions.
| _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.
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:
| _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[@]}" % | |
| } |

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