Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## New in git-machete 3.40.1

- fixed: ASCII-only mode is now applied separately for stdout and stderr so that stderr retains formatting when stdout is redirected (contributed by @tmchow)
- improved: bash, zsh and fish completion scripts now consistently filter out mutually exclusive options (contributed by @CodeLine9)

## New in git-machete 3.40.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fi

if grep -Pn 'not __fish_seen_subcommand_from .*?--([a-zA-Z0-9-]+)[^a-zA-Z0-9-].*? -x.*? -l \1 .*?-a ' $file; then
echo
echo "In fish completion ($file), flags with required parameter (-x) should not use \`not __fish_seen_subcommand_from <OPTION>\`."
echo 'If they do, as of fish v4.1.0, their parameter is not completed (-a) when the flag is used without `=` (`--foo <TAB>`).'
echo "In fish completion ($file), a flag \`--<FLAG>\` with required parameter (-x) should not use \`not __fish_seen_subcommand_from <FLAG>\`."
echo 'If it does, as of fish v4.1.0, its parameter is not completed (-a) when the flag is used without `=` (`--foo <TAB>`).'
exit 1
fi
112 changes: 106 additions & 6 deletions completion/git-machete.completion.bash
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
#!/usr/bin/env bash

# Check if any of the given options appear in the current command line words.
# Usage: _git_machete_seen_opt --opt1 -o --opt2 ...
_git_machete_seen_opt() {
local opt
for opt in "$@"; do
local word
for word in "${COMP_WORDS[@]}"; do
# Handle options with = (e.g. --onto= matches --onto=* and --onto)
if [[ "$opt" == *"=" ]]; then
if [[ "$word" == "${opt%=}" || "$word" == "${opt}"* ]]; then
return 0
fi
else
if [[ "$word" == "$opt" ]]; then
return 0
fi
fi
done
done
return 1
}

# Apply mutual exclusion to a space-separated options string.
# Each subsequent argument is a mutually-exclusive group of options (space-separated).
# If any option in a group is present on the current command line,
# all options from that group are removed from the resulting string.
# Usage: result=$(_git_machete_filter_mutex_groups "<opts>" "<group1>" "<group2>" ...)
_git_machete_filter_mutex_groups() {
local opts=" $1 "
shift
local group
for group in "$@"; do
if _git_machete_seen_opt $group; then
local opt
for opt in $group; do
opts="${opts// $opt / }"
done
fi
done
opts="${opts# }"
opts="${opts% }"
echo "$opts"
}

_git_machete() {
local cmds="add advance anno completion delete-unmanaged diff discover edit file fork-point github gitlab go help is-managed list log reapply show slide-out squash status traverse update version"
local help_topics="$cmds config format hooks"
Expand Down Expand Up @@ -47,13 +91,29 @@ _git_machete() {
--stop-after=*) __gitcomp "$(__git_heads)" "" "${cur##--stop-after=}" ;;
-*)
case ${COMP_WORDS[2]} in
add) __gitcomp "$common_opts $add_opts" ;;
add)
local filtered_add_opts
filtered_add_opts=$(_git_machete_filter_mutex_groups "$add_opts" \
"-R --as-root -f --as-first-child" \
"-R --as-root -o --onto=")
__gitcomp "$common_opts $filtered_add_opts"
;;
advance) __gitcomp "$common_opts $advance_opts" ;;
anno) __gitcomp "$common_opts $anno_opts" ;;
anno)
local filtered_anno_opts
filtered_anno_opts=$(_git_machete_filter_mutex_groups "$anno_opts" \
"-H --sync-github-prs -L --sync-gitlab-mrs")
__gitcomp "$common_opts $filtered_anno_opts"
;;
d|diff) __gitcomp "$common_opts $diff_opts" ;;
delete-unmanaged) __gitcomp "$common_opts $delete_unmanaged_opts" ;;
discover) __gitcomp "$common_opts $discover_opts" ;;
fork-point) __gitcomp "$common_opts $fork_point_opts" ;;
fork-point)
local filtered_fork_point_opts
filtered_fork_point_opts=$(_git_machete_filter_mutex_groups "$fork_point_opts" \
"--inferred --override-to= --override-to-inferred --override-to-parent --unset-override")
__gitcomp "$common_opts $filtered_fork_point_opts"
;;
github)
case ${COMP_WORDS[3]} in
"anno-prs") __gitcomp "$common_opts $githublab_anno_opts" ;;
Expand All @@ -75,11 +135,51 @@ _git_machete() {
*) __gitcomp "$common_opts" ;;
esac ;;
reapply) __gitcomp "$common_opts $reapply_opts" ;;
slide-out) __gitcomp "$common_opts $slide_out_opts" ;;
slide-out)
local filtered_slide_out_opts
filtered_slide_out_opts=$(_git_machete_filter_mutex_groups "$slide_out_opts" \
"--removed-from-remote -d --down-fork-point=" \
"--removed-from-remote -M --merge" \
"--removed-from-remote -n" \
"--removed-from-remote --no-edit-merge" \
"--removed-from-remote --no-interactive-rebase" \
"--removed-from-remote --no-rebase" \
"-M --merge -d --down-fork-point=" \
"-M --merge --no-interactive-rebase" \
"-M --merge --no-rebase" \
"-n --no-edit-merge" \
"-n --no-interactive-rebase" \
"--no-rebase -d --down-fork-point=" \
"--no-rebase --no-edit-merge" \
"--no-rebase --no-interactive-rebase")
__gitcomp "$common_opts $filtered_slide_out_opts"
;;
squash) __gitcomp "$common_opts $squash_opts" ;;
s|status) __gitcomp "$common_opts $status_opts" ;;
t|traverse) __gitcomp "$common_opts $traverse_opts" ;;
update) __gitcomp "$common_opts $update_opts" ;;
t|traverse)
local filtered_traverse_opts
filtered_traverse_opts=$(_git_machete_filter_mutex_groups "$traverse_opts" \
"-W -F --fetch" \
"-W -l --list-commits" \
"-W -w --whole" \
"-H --sync-github-prs -L --sync-gitlab-mrs" \
"-M --merge --no-interactive-rebase" \
"-n --no-edit-merge" \
"-n --no-interactive-rebase" \
"-n -y --yes" \
"--push --no-push" \
"--push-untracked --no-push-untracked")
__gitcomp "$common_opts $filtered_traverse_opts"
;;
update)
local filtered_update_opts
filtered_update_opts=$(_git_machete_filter_mutex_groups "$update_opts" \
"-M --merge -f --fork-point=" \
"-M --merge --no-interactive-rebase" \
"-n --no-edit-merge" \
"-n --no-interactive-rebase")
__gitcomp "$common_opts $filtered_update_opts"
;;
*)
if [[ $COMP_CWORD -eq 2 ]]; then
__gitcomp "$common_opts --version"
Expand Down
74 changes: 37 additions & 37 deletions completion/git-machete.completion.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ _git-machete() {
(add)
_arguments \
'1:: :__git_machete_list_addable' \
'(-f --as-first-child)'{-f,--as-first-child}'[Add the given branch as the first (instead of last) child of its parent]' \
'(-R --as-root)'{-R,--as-root}'[Add the given branch as a new root]' \
'(-o --onto)'{-o,--onto=}'[Specify the target parent branch to add the given branch onto]: :__git_machete_list_managed' \
'(-R --as-root -f --as-first-child)'{-f,--as-first-child}'[Add the given branch as the first (instead of last) child of its parent]' \
'(-R --as-root -f --as-first-child -o --onto)'{-R,--as-root}'[Add the given branch as a new root]' \
'(-R --as-root -o --onto)'{-o,--onto=}'[Specify the target parent branch to add the given branch onto]: :__git_machete_list_managed' \
'(-y --yes)'{-y,--yes}'[Do not ask for confirmation whether to create the branch or whether to add onto the inferred upstream]' \
"${common_flags[@]}"
;;
Expand All @@ -33,8 +33,8 @@ _git-machete() {
(anno)
_arguments \
'(-b --branch)'{-b,--branch=}'[Branch to set the annotation for]: :__git_machete_list_managed' \
'(-H --sync-github-prs)'{-H,--sync-github-prs}'[Annotate with GitHub PR numbers and author logins where applicable]' \
'(-L --sync-gitlab-mrs)'{-L,--sync-gitlab-mrs}'[Annotate with GitLab MR numbers and author logins where applicable]' \
'(-L --sync-gitlab-mrs -H --sync-github-prs)'{-H,--sync-github-prs}'[Annotate with GitHub PR numbers and author logins where applicable]' \
'(-H --sync-github-prs -L --sync-gitlab-mrs)'{-L,--sync-gitlab-mrs}'[Annotate with GitLab MR numbers and author logins where applicable]' \
"${common_flags[@]}"
;;
(completion)
Expand Down Expand Up @@ -67,11 +67,11 @@ _git-machete() {
;;
(fork-point)
_arguments '1:: :__git_branch_names' \
'(--inferred)'--inferred'[Display the fork point ignoring any potential override]' \
'(--override-to)'--override-to='[Override fork point to the given revision]: :__git_references' \
'(--override-to-inferred)'--override-to-inferred'[Override fork point to the inferred location]' \
'(--override-to-parent)'--override-to-parent'[Override fork point to the upstream (parent) branch]' \
'(--unset-override)'--unset-override='[Unset fork point override by removing machete.overrideForkPoint.<branch>.* configs]: :__git_machete_list_with_overridden_fork_point' \
'(--inferred --override-to --override-to-inferred --override-to-parent --unset-override)'--inferred'[Display the fork point ignoring any potential override]' \
'(--inferred --override-to --override-to-inferred --override-to-parent --unset-override)'--override-to='[Override fork point to the given revision]: :__git_references' \
'(--inferred --override-to --override-to-inferred --override-to-parent --unset-override)'--override-to-inferred'[Override fork point to the inferred location]' \
'(--inferred --override-to --override-to-inferred --override-to-parent --unset-override)'--override-to-parent'[Override fork point to the upstream (parent) branch]' \
'(--inferred --override-to --override-to-inferred --override-to-parent --unset-override)'--unset-override='[Unset fork point override by removing machete.overrideForkPoint.<branch>.* configs]: :__git_machete_list_with_overridden_fork_point' \
"${common_flags[@]}"
;;
(g|go)
Expand Down Expand Up @@ -115,14 +115,14 @@ _git-machete() {
# TODO (#113): suggest further branches based on the previous specified branch (like in Bash completion script)
_arguments \
'*:: :__git_machete_list_slidable' \
'(-d --down-fork-point)'{-d,--down-fork-point=}'[If updating by rebase, specify fork point commit after which the rebased part of history of the downstream branch is meant to start]: :__git_references' \
'(--removed-from-remote -M --merge --no-rebase -d --down-fork-point)'{-d,--down-fork-point=}'[If updating by rebase, specify fork point commit after which the rebased part of history of the downstream branch is meant to start]: :__git_references' \
'(--delete)'--delete'[Delete branches after sliding them out]' \
'(-M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \
'(-n)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \
'(--no-edit-merge)'--no-edit-merge'[If updating by merge, pass --no-edit flag to underlying git merge]' \
'(--no-interactive-rebase)'--no-interactive-rebase'[If updating by rebase, do NOT pass --interactive flag to underlying git rebase]' \
'(--no-rebase)'--no-rebase'[Skip rebase of downstream branches after sliding out]' \
'(--removed-from-remote)'--removed-from-remote'[Slide out all branches removed from the remote]' \
'(--removed-from-remote -d --down-fork-point --no-interactive-rebase --no-rebase -M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \
'(--removed-from-remote -n --no-edit-merge --no-interactive-rebase)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \
'(--removed-from-remote -n --no-edit-merge --no-interactive-rebase --no-rebase)'--no-edit-merge'[If updating by merge, pass --no-edit flag to underlying git merge]' \
'(--removed-from-remote -M --merge -n --no-edit-merge --no-interactive-rebase --no-rebase)'--no-interactive-rebase'[If updating by rebase, do NOT pass --interactive flag to underlying git rebase]' \
'(--removed-from-remote -d --down-fork-point -M --merge --no-interactive-rebase --no-edit-merge --no-rebase)'--no-rebase'[Skip rebase of downstream branches after sliding out]' \
'(--removed-from-remote -d --down-fork-point -M --merge -n --no-edit-merge --no-interactive-rebase --no-rebase)'--removed-from-remote'[Slide out all branches removed from the remote]' \
"${common_flags[@]}"
;;
(squash)
Expand All @@ -140,34 +140,34 @@ _git-machete() {
;;
(t|traverse)
_arguments \
'(-F --fetch)'{-F,--fetch}'[Fetch the remotes of all managed branches at the beginning of traversal]' \
'(-H --sync-github-prs)'{-H,--sync-github-prs}'[Create and retarget GitHub PRs while traversing]' \
'(-L --sync-gitlab-mrs)'{-L,--sync-gitlab-mrs}'[Create and retarget GitLab MRs while traversing]' \
'(-l --list-commits)'{-l,--list-commits}'[List the messages of commits introduced on each branch]' \
'(-M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \
'(-n)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \
'(-W -F --fetch)'{-F,--fetch}'[Fetch the remotes of all managed branches at the beginning of traversal]' \
'(-L --sync-gitlab-mrs -H --sync-github-prs)'{-H,--sync-github-prs}'[Create and retarget GitHub PRs while traversing]' \
'(-H --sync-github-prs -L --sync-gitlab-mrs)'{-L,--sync-gitlab-mrs}'[Create and retarget GitLab MRs while traversing]' \
'(-W -l --list-commits)'{-l,--list-commits}'[List the messages of commits introduced on each branch]' \
'(--no-interactive-rebase -M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \
'(-n --no-edit-merge --no-interactive-rebase -y --yes)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \
'(--no-detect-squash-merges)'--no-detect-squash-merges'[Only consider "strict" (fast-forward or 2-parent) merges, rather than rebase/squash merges, when detecting if a branch is merged into its upstream]' \
'(--no-edit-merge)'--no-edit-merge'[If updating by merge, pass --no-edit flag to underlying git merge]' \
'(--no-interactive-rebase)'--no-interactive-rebase'[If updating by rebase, do NOT pass --interactive flag to underlying git rebase]' \
'(--no-push)'--no-push'[Do not push any (neither tracked nor untracked) branches to remote]' \
'(--no-push-untracked)'--no-push-untracked'[Do not push untracked branches to remote]' \
'(--push)'--push'[Push all (both tracked and untracked) branches to remote (default behavior)]' \
'(--push-untracked)'--push-untracked'[Push untracked branches to remote (default behavior)]' \
'(-n --no-edit-merge)'--no-edit-merge'[If updating by merge, pass --no-edit flag to underlying git merge]' \
'(-n -M --merge --no-interactive-rebase)'--no-interactive-rebase'[If updating by rebase, do NOT pass --interactive flag to underlying git rebase]' \
'(--push --no-push)'--no-push'[Do not push any (neither tracked nor untracked) branches to remote]' \
'(--push-untracked --no-push-untracked)'--no-push-untracked'[Do not push untracked branches to remote]' \
'(--no-push --push)'--push'[Push all (both tracked and untracked) branches to remote (default behavior)]' \
'(--no-push-untracked --push-untracked)'--push-untracked'[Push untracked branches to remote (default behavior)]' \
'(--return-to)'--return-to='[The branch to return after traversal is successfully completed; argument can be "here", "nearest-remaining", or "stay"]: :__git_machete_opt_return_to_args' \
'(--start-from)'--start-from='[The branch to start the traversal from; argument can be "here", "root", "first-root", or any branch name]: :__git_machete_opt_start_from_args_or_branches' \
'(--stop-after)'--stop-after='[The branch to stop the traversal after]: :__git_branch_names' \
'(-w --whole)'{-w,--whole}'[Equivalent to -n --start-from=first-root --return-to=nearest-remaining]' \
'(-W)'-W'[Equivalent to --fetch --whole]' \
'(-y --yes)'{-y,--yes}'[Do not ask for any interactive input; implicates -n]' \
'(-W -w --whole)'{-w,--whole}'[Equivalent to -n --start-from=first-root --return-to=nearest-remaining]' \
'(-F --fetch -l --list-commits -w --whole -W)'-W'[Equivalent to --fetch --whole]' \
'(-n -y --yes)'{-y,--yes}'[Do not ask for any interactive input; implicates -n]' \
"${common_flags[@]}"
;;
(update)
_arguments \
'(-f --fork-point)'{-f,--fork-point=}'[If updating by rebase, specify fork point commit after which the rebased part of history is meant to start]: :__git_references' \
'(-M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \
'(-n)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \
'(--no-edit-merge)'--no-edit-merge'[If updating by merge, pass --no-edit flag to underlying git merge]' \
'(--no-interactive-rebase)'--no-interactive-rebase'[If updating by rebase, do NOT pass --interactive flag to underlying git rebase]' \
'(-M --merge -f --fork-point)'{-f,--fork-point=}'[If updating by rebase, specify fork point commit after which the rebased part of history is meant to start]: :__git_references' \
'(-f --fork-point --no-interactive-rebase -M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \
'(-n --no-edit-merge --no-interactive-rebase)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \
'(-n --no-edit-merge)'--no-edit-merge'[If updating by merge, pass --no-edit flag to underlying git merge]' \
'(-n -M --merge --no-interactive-rebase)'--no-interactive-rebase'[If updating by rebase, do NOT pass --interactive flag to underlying git rebase]' \
"${common_flags[@]}"
;;
esac
Expand Down
Loading