-
Notifications
You must be signed in to change notification settings - Fork 141
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
Feat: add interactive git worktree operations #402
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
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.
|
||
tree=$(git worktree list | awk '{print $1}' | FZF_DEFAULT_OPTS="$opts" fzf) | ||
[[ -z "$tree" ]] && return 1 | ||
echo "$tree" |
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 think it would be cleaner to cd
into the directory here instead of doing so in forgit.plugin.zsh and forgit.plugin.fish.
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.
- Using
cd
directly in the function only changes the current directory in the script child process, not in the terminal process where the script is called - Any preference between
cd
andpushd
?
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 is tricky, because the actual change of the directory is currently implemented in the shell plugin. Some users (e.g. me) do not use forgit with the plugin but in the form of a subcommand of git
. Calling git forgit worktree_jump
won't work at the moment, though. IMO we should find a way to make this work correctly in both usecases, or otherwise rename the function (because right now it actually does not jump).
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 could rename the function to worktree_select
- gws
(but also offer the gwj
alias which combines cd
and worktree_select
)
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.
Given that there is no way to manipulate the directory of the parent process, I think this is the best compromise we can achieve. @carlfriedrich WDYT?
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.
Seems resonable to me. We should add an explaining line on this to the documentation, though.
bin/git-forgit
Outdated
$FORGIT_WORKTREE_LOCK_FZF_OPTS | ||
" | ||
|
||
tree=$(git worktree list | awk '{print $1}' | grep -v "(bare)" | FZF_DEFAULT_OPTS="$opts" fzf) |
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.
What exactly is grep -v "(bare)"
filtering out? I'm new to the whole worktree thing, could you please elaborate?
There are also more things we need to filter out here:
- The root worktree (which can not be locked)
- Worktrees that are already locked
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.
When using worktrees with a bare clone, you automatically get the git-dir
as an entry in the
git worktree list
output (even though its not really a worktree).
So for certain operations we want to filter it out (like locking, unlocking, removing, ...).
Good catch on filtering already locked (totally slipped my mind).
bin/git-forgit
Outdated
|
||
opts=" | ||
$FORGIT_FZF_DEFAULT_OPTS | ||
+s -m --tiebreak=index --header-lines=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.
I'm not a fan of the header line here. I would prefer to have no header line and simply not display the root worktree with this command. This also applies to the other functions using --header-lines=1
. Would be interested to know what other maintainers think @cjappl @carlfriedrich @wfxr.
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 is the way we do it with all other forgit functions as well, so I would approve it like this. @sandr01d are you questioning this principle in general for all forgit functions?
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 is the way we do it with all other forgit functions as well
Weird, I've never noticed. In this case I'm fine with keeping it in this PR as well.
bin/git-forgit
Outdated
|
||
tree=$(git worktree list | awk '{print $1}' | grep -v "(bare)" | FZF_DEFAULT_OPTS="$opts" fzf) | ||
[[ -z "$tree" ]] && return 1 | ||
_forgit_git_worktree_lock "$tree" |
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.
It would be nice to allow passing in additional arguments as we do with other functions by propagating the function arguments, so that gwl --reason <STRING>
works as expected.
This also applies to the other functions.
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.
Agree.
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
(Feat: add interactive git worktree operations #402 (comment)) _forgit_worktree_jump
does not change the directory when forgit is used as a git subcommand (Feat: add interactive git worktree operations #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. (Feat: add interactive git worktree operations #402 (comment)) - We should add completions for the new commands (Feat: add interactive git worktree operations #402 (comment))
- We should allow passing additional arguments to the underlying git commands (Feat: add interactive git worktree operations #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.
(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 |
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 again for your work @suft. There are a few more things that need to be addressed, but I think we're heading in the right direction 🙂 I think there might have been a misunderstanding regarding argument parsing, sorry if I didn't make it clear enough what I meant previously. I provided an example this time. In case any questions come up, please don't hesitate to ping me.
local tree opts reason | ||
if [[ $# -ne 0 ]]; then | ||
if [[ "$1" == "--reason" ]]; then | ||
if [[ -z "$2" ]]; then | ||
_forgit_warn "Option \`--reason' requires a value" | ||
return 1 | ||
fi | ||
reason="$2" | ||
shift 2 | ||
fi | ||
|
||
if [[ $# -eq 0 ]] || _forgit_contains_non_flags "$@"; then | ||
git worktree lock "$@" --reason "$reason" | ||
return $? | ||
fi | ||
fi | ||
|
||
opts=" | ||
$FORGIT_FZF_DEFAULT_OPTS | ||
+s +m --tiebreak=index | ||
--preview=\"$FORGIT worktree_preview {1}\" | ||
$FORGIT_WORKTREE_LOCK_FZF_OPTS | ||
" | ||
|
||
tree=$(git worktree list | grep -vE "\(bare\)|locked" | awk '{print $1}' | FZF_DEFAULT_OPTS="$opts" fzf) | ||
[[ -z "$tree" ]] && return 1 | ||
|
||
if [[ -z "$reason" ]]; then | ||
_forgit_git_worktree_lock "$tree" | ||
else | ||
_forgit_git_worktree_lock "$tree" --reason "$reason" | ||
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 think we could simplify this by not parsing the arguments and instead simply propagating them. This also allows us to stay up-to-date without any changes in case git introduces new arguments in the future.
local tree opts reason | |
if [[ $# -ne 0 ]]; then | |
if [[ "$1" == "--reason" ]]; then | |
if [[ -z "$2" ]]; then | |
_forgit_warn "Option \`--reason' requires a value" | |
return 1 | |
fi | |
reason="$2" | |
shift 2 | |
fi | |
if [[ $# -eq 0 ]] || _forgit_contains_non_flags "$@"; then | |
git worktree lock "$@" --reason "$reason" | |
return $? | |
fi | |
fi | |
opts=" | |
$FORGIT_FZF_DEFAULT_OPTS | |
+s +m --tiebreak=index | |
--preview=\"$FORGIT worktree_preview {1}\" | |
$FORGIT_WORKTREE_LOCK_FZF_OPTS | |
" | |
tree=$(git worktree list | grep -vE "\(bare\)|locked" | awk '{print $1}' | FZF_DEFAULT_OPTS="$opts" fzf) | |
[[ -z "$tree" ]] && return 1 | |
if [[ -z "$reason" ]]; then | |
_forgit_git_worktree_lock "$tree" | |
else | |
_forgit_git_worktree_lock "$tree" --reason "$reason" | |
fi | |
local tree opts | |
if [[ $# -ne 0 ]] && _forgit_contains_non_flags "$@"; then | |
git worktree lock "$@" | |
return $? | |
fi | |
opts=" | |
$FORGIT_FZF_DEFAULT_OPTS | |
+s +m --tiebreak=index | |
--preview=\"$FORGIT worktree_preview {1}\" | |
$FORGIT_WORKTREE_LOCK_FZF_OPTS | |
" | |
tree=$(git worktree list | grep -vE "\(bare\)|locked" | awk '{print $1}' | FZF_DEFAULT_OPTS="$opts" fzf) | |
[[ -z "$tree" ]] && return 1 | |
_forgit_git_worktree_lock "$tree" "$@" |
_forgit_inside_work_tree || _forgit_inside_git_dir || return 1 | ||
local worktree_list tree opts force_flags | ||
|
||
force_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.
Same here, I would prefer simply propagating the arguments as we do everywhere else.
_git-worktrees() { | ||
_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.
This always suggests me refs/heads/worktrees
which is not a worktree. Also consider using -z
together with --porcelain
. From the git man page:
--porcelain
With list, output in an easy-to-parse format for scripts. This format will remain stable
across Git versions and regardless of user configuration. It is recommended to combine this
with -z. See below for
-z
Terminate each line with a NUL rather than a newline when --porcelain is specified with
list. This makes it possible to parse the output when a worktree path contains a newline
character.
The same applies to the bash completions.
@@ -42,6 +42,10 @@ complete -c git-forgit -n __fish_forgit_needs_subcommand -a reset_head -d 'git r | |||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a revert_commit -d 'git revert commit selector' | |||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a stash_show -d 'git stash viewer' | |||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a stash_push -d 'git stash push selector' | |||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a worktree_select -d 'git worktree selector' |
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 get error messages that look like this when trying to execute one of the commands in fish when inside a worktree:
forgit: 'worktree_lock' is not a valid forgit command.
The following commands are supported:
add
blame
branch_delete
checkout_branch
checkout_commit
checkout_file
checkout_tag
cherry_pick
cherry_pick_from_branch
clean
diff
fixup
ignore
log
rebase
reset_head
revert_commit
stash_show
stash_push
@cjappl is our fish expert in case you need some help with this.
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:
main
for looking at the pristine codework
for looking at my codereview
for looking at someone else’s codebackground
for my computer to look at my codescratch
for 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?)
gwl
gwu
gwr
gwj
git worktree list
andcd
into that result (not a native git operation)Screenshots
bash (repo with short history) -
gwj
FORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color'
fish (repo with mid-length history) -
gwj
FORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color'
bash (repo with mid-length history) -
gwj
FORGIT_WORKTREE_PREVIEW_GIT_OPTS='--oneline --graph --decorate --color --max-count=100'
Closes #399.
Type of change
Test environment