From bb4712f9473974a51c9e89353863e3a2f7140b8d Mon Sep 17 00:00:00 2001 From: Djuuu Date: Mon, 6 Mar 2023 12:00:04 +0100 Subject: [PATCH 1/3] Introduce zsh completion function --- completion/_git-mr | 150 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 completion/_git-mr diff --git a/completion/_git-mr b/completion/_git-mr new file mode 100644 index 0000000..6ae80a7 --- /dev/null +++ b/completion/_git-mr @@ -0,0 +1,150 @@ +#compdef git-mr + +# zsh completion wrapper for git-mr +# +# Add this line to your .zshrc (before the call to compinit): +# +# fpath=("~/path/to/git-mr/completion" $fpath) +# +# or copy this file inside any directory in your fpath. +# + +__git-mr_commands() { + local -a commands=( + 'open:open merge request in browser' + 'status:show merge request status summary' + 'update:update merge request commit references in description' + 'menu:show menu of merge requests with same issue code' + + 'ip:transition merge request to "In Progress" status' + 'cr:transition merge request to "Code Review" status' + 'qa:transition merge request to "Quality Assurance" status' + 'ok:transition merge request to "Accepted" status' + + 'undraft:remove merge request "draft" status' + 'merge:merge merge request' + + 'base:show guessed base branch' + 'code:show guessed issue code' + 'hook:add prepare-commit-msg Git hook to repository' + 'help:show help page' + ) + _describe -V -t mr-commands 'mr commands' commands +} + +__git-mr_menu_commands() { + local -a commands=( + 'status:show menu merge requests status summary' + 'update:update menu in related merge request descriptions' + ) + _describe -V -t mr-menu-commands 'mr menu commands' commands +} + +__git-mr_branch_names() { + if ! declare -f __git_branch_names &> /dev/null; then + compadd "${(f)$(git branch --format='%(refname:short)')}" + return $? + fi + + __git_branch_names +} + +_git-mr() { + local context state state_descr line curcontext="$curcontext" + typeset -A opt_args + + # Parse current command words to get action context + local mr_action="default" w + for w in "${words[@]}"; do + case $w in + help|usage) mr_action="help" ;; + op|open) mr_action="open" ;; + st|status) [[ $mr_action == "menu" ]] && mr_action="menu-status" || mr_action="status" ;; + up|update) [[ $mr_action == "menu" ]] && mr_action="menu-update" || mr_action="update" ;; + mg|merge) mr_action="merge" ;; + menu) mr_action="menu" ;; + ip|cr|qa|ok) mr_action="transition" ;; + IP|CR|QA|OK) mr_action="transition" ;; + undraft) mr_action="transition" ;; + hook) mr_action="plumbing" ;; + base) mr_action="plumbing" ;; + code) mr_action="plumbing" ;; + esac + done + + [[ $mr_action == "plumbing" ]] && return 0 # No more argument or option + + # Build options depending on action context + local -a opts + case $mr_action in default|open) opts+=( + '(-c --code)'{-c,--code}'[force issue code]:issue code:->issue_code' + );; esac + case $mr_action in default|open|update) opts+=( + '(-t --target)'{-t,--target}'[force target branch]:merge request target branch:->target_branch' + '(-e --extended)'{-e,--extended}'[use full commit messages in description]' + );; esac + case $mr_action in default|open|status|menu-status) opts+=( + '(--no-color)'--no-color'[disable terminal colors]' + '(--no-links)'--no-links'[disable terminal hyperlinks]' + );; esac + case $mr_action in default|update|menu-update|transition|merge) opts+=( + '(-y --yes)'{-y,--yes}'[bypass confirmation prompts ("yes")]' + );; esac + opts+=( + '(-v --verbose)'{-v,--verbose}'[verbose output (displays called API URLs & other debugging info)]' + ) + + case $mr_action in default) opts+=( + '(-)'-h'[print help message]' + );; esac + + case $mr_action in update) opts+=( + '(-n --new-section -r --replace-commits)'{-n,--new-section}'[add new section in description for new commits]::new section title:->new_section_title' + '(-n --new-section -r --replace-commits)'{-r,--replace-commits}'[fully replace commit list in description with current commits]' + );; esac + case $mr_action in menu-update) opts+=( + '(--current)'--current'[update only current project/branch merge request]' + );; esac + case $mr_action in merge) opts+=( + '(-f --force)'{-f,--force}'[force merge even if there are unresolved threads]' + );; esac + + # Build arguments depending on action context + local -a args=('::git-mr command:->command') + case $mr_action in + help) ;; + menu*) args+=('::git-mr menu command:->menu_command' '::search term:->search_term') ;; + *) args+=('::source branch:->source_branch') ;; + esac + + # Main git-mr argument & option definition + _arguments -C $opts $args && return + + # Argument & option values + case $state in + source_branch) __git-mr_branch_names && ret=0 ;; + search_term) ret=0 ;; + + issue_code) ret=0 ;; + target_branch) __git-mr_branch_names && ret=0 ;; + + # "command" | "command source_branch" + command*) + __git-mr_commands && ret=0 + [[ -n ${words[CURRENT]} && $ret -gt 0 ]] && + __git-mr_branch_names && ret=0 # Complete branch only when trying word failing command completion + ;; + + # "menu_command" | "menu_command search_term" + menu_command*) __git-mr_menu_commands && ret=0 ;; + + new_section_title) ret=0 ;; + new_section_title*) # "new_section_title"| "new_section_title source_branch" + __git-mr_branch_names && ret=0 + ;; + esac + + return $ret +} + +_git-mr "$@" From 6dfaf7ca84320da94bff479728bf661c03feeb0b Mon Sep 17 00:00:00 2001 From: Djuuu Date: Mon, 6 Mar 2023 12:00:04 +0100 Subject: [PATCH 2/3] Refactor bash completion --- git-mr-completion.bash | 239 ++++++++++++++++++++++++++--------------- 1 file changed, 152 insertions(+), 87 deletions(-) diff --git a/git-mr-completion.bash b/git-mr-completion.bash index c1997b4..fee482a 100644 --- a/git-mr-completion.bash +++ b/git-mr-completion.bash @@ -1,106 +1,171 @@ # See https://github.com/git/git/blob/master/contrib/completion/git-completion.bash + +# bash completion support for git-mr +# +# Source this file in one of your shell startup scripts (e.g. .bashrc): +# +# . "/path/to/git-mr/git-mr-completion.bash" +# +# Git completion is required. +# + +__git-mr_commands() { + __gitcomp_nl_append "$( + cat <<-'ACTIONS' + open + status + update + menu + ip + cr + qa + ok + undraft + merge + base + code + hook + help + ACTIONS + )" +} + +__git-mr_menu_commands() { + __gitcomp_nl_append "$( + cat <<-'ACTIONS' + status + update + ACTIONS + )" +} + +__git-mr_branch_names() { + __git_complete_refs --mode="heads" +} + _git_mr() { - local isAnyAction - local isMenu - local isMenuStatus - local isMenuUpdate - local isMenuUpdateCurrent - local isMerge - local isUpdate - - # Parse current command words to get context + # Disable default bash completion (current directory file names, etc.) + compopt +o bashdefault +o default 2>/dev/null + + # Parse current command words to get action context + local mr_action="default" w for w in "${words[@]}"; do - case "$w" in - open|status|update|merge|menu|ip|cr|qa|ok|undraft|hook|base|code|help) isAnyAction=1 ;; + case $w in + help | usage) mr_action="help" ;; + open) mr_action="open" ;; + status) [[ $mr_action == "menu" ]] && mr_action="menu-status" || mr_action="status" ;; + update) [[ $mr_action == "menu" ]] && mr_action="menu-update" || mr_action="update" ;; + merge) mr_action="merge" ;; + menu) mr_action="menu" ;; + ip | cr | qa | ok) mr_action="transition" ;; + IP | CR | QA | OK) mr_action="transition" ;; + undraft) mr_action="transition" ;; + hook) mr_action="plumbing" ;; + base) mr_action="plumbing" ;; + code) mr_action="plumbing" ;; esac - [[ $w == "menu" ]] && isMenu=1 - if [[ -n "$isMenu" ]]; then - [[ "$w" == "update" ]] && isMenuUpdate=1 - [[ "$w" == "status" ]] && isMenuStatus=1 - if [[ -n "$isMenuUpdate" ]]; then - [[ "$w" == "--current" ]] && isMenuUpdateCurrent=1 - fi - else - [[ "$w" == "merge" ]] && isMerge=1 - [[ "$w" == "update" ]] && isUpdate=1 + + if [[ $w != "$cur" ]]; then + case $w in + op) mr_action="open" ;; + st) [[ $mr_action == "menu" ]] && mr_action="menu-status" || mr_action="status" ;; + up) [[ $mr_action == "menu" ]] && mr_action="menu-update" || mr_action="update" ;; + mg) mr_action="merge" ;; + esac fi done - case "$prev" in - # Options with values - -t|--target) __git_complete_refs --mode="heads"; return ;; - -c|--code) COMPREPLY=(); return ;; - # Actions without additional argument or option - hook|base|code) COMPREPLY=(); return ;; - esac + [[ $mr_action == "plumbing" ]] && return 0 # No more argument or option + + # Build options depending on action context + if [[ $cur = -* ]]; then + case $mr_action in default | open) + __gitcomp_nl_append '-c'; __gitcomp_nl_append '--code' + ;; esac + case $mr_action in default | open | update) + __gitcomp_nl_append '-t'; __gitcomp_nl_append '--target' + __gitcomp_nl_append '-e'; __gitcomp_nl_append '--extended' + ;; esac + case $mr_action in default | open | status | menu-status) + __gitcomp_nl_append '--no-color' + __gitcomp_nl_append '--no-links' + ;; esac + case $mr_action in default | update | menu-update | transition | merge) + __gitcomp_nl_append '-y'; __gitcomp_nl_append '--yes' + ;; esac + __gitcomp_nl_append '-v'; __gitcomp_nl_append '--verbose' - # Menu - if [[ -n $isMenu ]]; then - [[ -n $isMenuStatus ]] && return - [[ -n $isMenuUpdateCurrent ]] && return - [[ -n $isMenuUpdate ]] && __gitcomp "--current" && return - __gitcomp "status update" + case $mr_action in default) + __gitcomp_nl_append '-h' + ;; esac + + case $mr_action in update) + __gitcomp_nl_append '-n'; __gitcomp_nl_append '--new-section' + __gitcomp_nl_append '-r'; __gitcomp_nl_append '--replace-commits' + ;; esac + case $mr_action in menu-update) + __gitcomp_nl_append '--current' + ;; esac + case $mr_action in merge) + __gitcomp_nl_append '-f'; __gitcomp_nl_append '--force' + ;; esac return fi - case "$cur" in - --*) - __gitcomp "--code --target --extended --no-color --no-links --verbose --yes" - [[ -n $isMerge ]] && - __gitcomp_nl_append "--force" - [[ -n $isUpdate ]] && - __gitcomp_nl_append "--new-section" && - __gitcomp_nl_append "--replace-commits" - return - ;; - -*) - __gitcomp "-c --code -t --target -e --extended --no-color --no-links -v --verbose -y --yes" - [[ -n $isMerge ]] && - __gitcomp_nl_append "-f" && - __gitcomp_nl_append "--force" - [[ -n $isUpdate ]] && - __gitcomp_nl_append "-n" && - __gitcomp_nl_append "--new-section" && - __gitcomp_nl_append "-r" && - __gitcomp_nl_append "--replace-commits" - return - ;; - *) - __git_complete_refs --mode="heads" - [[ -z $isAnyAction ]] && __gitcomp_nl_append "$(cat <<-'ACTIONS' - open - status - update - merge - menu - ip - cr - qa - ok - undraft - hook - base - code - help - ACTIONS -)" - return + if [[ $cur != -* ]]; then + case $mr_action in + default) __git-mr_commands ;; + menu) __git-mr_menu_commands ;; + esac + fi + + # Options with values + case "$prev" in + -c | --code) return ;; # required argument + -t | --target) __git-mr_branch_names; return ;; + esac + + case $mr_action in + default) + [[ -n $cur ]] && case $cur in + o|op|ope|open |\ + s|st|sta|stat|statu|status |\ + u|up|upd|upda|updat|update |\ + m|me|men|menu |\ + i|ip|c|cr|q|qa|ok |\ + un|und|undr|undra|undraf|undraft |\ + mer|merg|merge |\ + b|ba|bas|base |\ + co|cod|code |\ + h|ho|hoo|hook |\ + he|hel|help) ;; + *) __git-mr_branch_names ;; + esac ;; + help | menu*) return ;; # no argument + *) __git-mr_branch_names ;; esac } +# Prevent classic sourcing on zsh +if [[ -n ${ZSH_VERSION-} && -z ${GIT_SOURCING_ZSH_COMPLETION-} ]]; then + echo "zsh: add 'git-mr/completion' to your fpath instead of sourcing git-mr-completion.bash" 1>&2 + return +fi -# Load git completion if not loaded yet and available at usual path -if ! declare -f __git_complete > /dev/null && [ -f /usr/share/bash-completion/completions/git ]; then - . /usr/share/bash-completion/completions/git +# Load git completion if not loaded yet and available at usual paths +if ! declare -f __git_complete &>/dev/null; then + if [[ -f "${HOME}/.local/share/bash-completion/completions/git" ]]; then + . "${HOME}/.local/share/bash-completion/completions/git" + elif [[ -f "/usr/share/bash-completion/completions/git" ]]; then + . "/usr/share/bash-completion/completions/git" + fi fi -if declare -f __git_complete > /dev/null; then - # Add completion for direct script usage - __git_complete "git-mr" _git_mr +# Add completion for direct script usage +__git_complete "git-mr" _git_mr - # Add completion for aliases - for a in $(alias -p | grep "git[- ]mr" | cut -d' ' -f2 | cut -d= -f1); do - __git_complete "$a" _git_mr - done -fi +# Add completion for aliases +for a in $(alias -p | grep "git[- ]mr" | cut -d' ' -f2 | cut -d= -f1); do + __git_complete "$a" _git_mr +done From 7f6535c89e3dd8467e6f7d7423f49330935707ec Mon Sep 17 00:00:00 2001 From: Djuuu Date: Mon, 6 Mar 2023 12:00:04 +0100 Subject: [PATCH 3/3] Documentation --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a00012b..182ca16 100644 --- a/README.md +++ b/README.md @@ -138,11 +138,23 @@ _OR_ #### Completion -Completion is available in `git-mr-completion.bash`. Source it in one of your shell startup scripts: -```bash -. "/path/to/git-mr/git-mr-completion.bash" -``` +Completion functions for Bash and Zsh are available: +* **Bash** + Source `git-mr-completion.bash` in one of your shell startup scripts (`.bashrc` / `.bash_profile`): + ```bash + . "/path/to/git-mr/git-mr-completion.bash" + ``` + +* **Zsh** + Add the `completion` directory to your `fpath` (in your `.zshrc`, before any call to `compinit` or `oh-my-zsh.sh`) + ```zsh + fpath=("~/path/to/git-mr/completion" $fpath) + ``` + You may have to force a rebuild of `zcompdump` by running: + ```zsh + rm -f ~/.zcompdump; compinit + ``` ### Configuration