repo.bash: replace with better version

From https://raw.github.com/aartamonau/repo.bash_completion/master/repo.bash_completion

Change-Id: I7ca5d7eba70244d875c48ce76c999c8a10b88934
diff --git a/bash_completion/repo-LICENSE b/bash_completion/repo-LICENSE
deleted file mode 100644
index d251496..0000000
--- a/bash_completion/repo-LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//    * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//    * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//    * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/bash_completion/repo.bash b/bash_completion/repo.bash
index e556d33..3b1d510 100644
--- a/bash_completion/repo.bash
+++ b/bash_completion/repo.bash
@@ -1,99 +1,654 @@
-# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the repo-LICENSE file.
+# -*- mode: sh; -*-
 
-# Programmable completion for some Chromium OS build scripts.
+declare -A CMD_HANDLERS
+CMD_HANDLERS=(
+    ["init"]=_repo_init
+    ["help"]=_repo_help
+    ["abandon"]=_repo_abandon
+    ["branch"]=_repo_branch
+    ["branches"]=_repo_branches
+    ["checkout"]=_repo_checkout
+    ["cherry-pick"]=_repo_cherry_pick
+    ["diff"]=_repo_diff
+    ["download"]=_repo_download
+    ["forall"]=_repo_forall
+    ["grep"]=_repo_grep
+    ["list"]=_repo_list
+    ["prune"]=_repo_prune
+    ["rebase"]=_repo_rebase
+    ["selfupdate"]=_repo_selfupdate
+    ["smartsync"]=_repo_smartsync
+    ["stage"]=_repo_stage
+    ["start"]=_repo_start
+    ["status"]=_repo_status
+    ["sync"]=_repo_sync
+    ["upload"]=_repo_upload
+    ["version"]=_repo_version
+)
 
-_list_repo_commands() {
-  local repo=${COMP_WORDS[0]}
-  "${repo}" help --all | grep -E '^  ' | sed 's/  \([^ ]\+\) .\+/\1/'
+# To be populated by command handlers.
+declare -a OPTIONS
+declare -A ARG_OPTIONS
+
+declare cur
+declare prev
+
+_init_cur_prev() {
+    cur=$(_get_cword "=")
+    prev=$(_get_cword "=" 1)
+
+    _split_longopt
 }
 
-_list_repo_branches() {
-  local repo=${COMP_WORDS[0]}
-  "${repo}" branches 2>&1 | grep \| | sed 's/[ *][Pp ] *\([^ ]\+\) .*/\1/'
-}
+_find_repo() {
+    local dir=$(pwd)
+    local found=1
 
-_list_repo_projects() {
-  local repo=${COMP_WORDS[0]}
-  local manifest=$(mktemp)
-  "${repo}" manifest -o "${manifest}" >& /dev/null
-  grep 'project name=' "${manifest}" | sed 's/.\+name="\([^"]\+\)".\+/\1/'
-  rm -f "${manifest}" >& /dev/null
-}
+    while [ "${dir}" != / ]
+    do
+        if [ -f "${dir}/.repo/repo/main.py" ]
+        then
+            found=0
+            break
+        fi
 
-# Complete the repo <command> argument.
-_complete_repo_command() {
-  [ ${COMP_CWORD} -eq 1 ] || return 1
-  local command=${COMP_WORDS[1]}
-  COMPREPLY=($(compgen -W "$(_list_repo_commands)" -- "${command}"))
-  return 0
-}
+        dir=$(cd "${dir}/.." && pwd)
+    done
 
-_complete_repo_arg() {
-  [ ${COMP_CWORD} -gt 1 ] || return 1
-  local command=${COMP_WORDS[1]}
-  local current=${COMP_WORDS[COMP_CWORD]}
-  if [[ ${command} == "abandon" ]]; then
-    if [[ ${COMP_CWORD} -eq 2 ]]; then
-      COMPREPLY=($(compgen -W "$(_list_repo_branches)" -- "${current}"))
-    else
-      COMPREPLY=($(compgen -W "$(_list_repo_projects)" -- "${current}"))
+    if [ ${found} -eq 0 ]
+    then
+        echo "${dir}"
     fi
+}
+
+_is_repo_dir() {
+    local repo_root=$(_find_repo)
+
+    [ -n "${repo_root}" ]
+}
+
+_gen_comps() {
+    local completions="$1"
+    local suffix="${2:- }"
+
+    local -i i
+    local -a tmp=( $(compgen -W "${completions}" -- ${cur}) )
+
+    for (( i=0; i < ${#tmp[*]}; i++ ))
+    do
+        tmp[$i]="${tmp[$i]}${suffix}"
+    done
+
+    COMPREPLY=(
+        "${COMPREPLY[@]}"
+        "${tmp[@]}"
+    )
+}
+
+_strip_colors () {
+    # taken from http://goo.gl/7KlLZ
+    sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g"
+}
+
+_no_completion() {
+    true
+}
+
+_command_completion() {
+    local cmds
+
+    if _is_repo_dir
+    then
+        cmds=("abandon" "branch" "branches" "checkout" "cherry-pick" "diff"
+            "download" "forall" "grep" "help" "init" "list" "prune" "rebase"
+            "selfupdate" "smartsync" "stage" "start" "status" "sync"
+            "upload" "version")
+    else
+        cmds=("help" "init")
+    fi
+
+    _gen_comps "${cmds[*]}"
+}
+
+_branch_completion() {
+    local raw_branches
+
+    # separate statement required to be able to access exit code
+    raw_branches=$(repo branches 2>/dev/null)
+
+    if [ $? -eq 0 ]
+    then
+        local branches=$(
+            echo "${raw_branches}" |
+            _strip_colors | awk 'BEGIN { FS="|" } { print $1 }' | cut -c 3-
+        )
+
+        _gen_comps "${branches}"
+    fi
+}
+
+_dir_completion() {
+    _filedir -d
+}
+
+_project_completion() {
+    local repo_root=$(_find_repo)
+
+    if [ -n "${repo_root}" -a -f "${repo_root}/.repo/project.list" ]
+    then
+        local projects=$(cat "${repo_root}/.repo/project.list")
+        _gen_comps "${projects}"
+    fi
+}
+
+_manifest_completion() {
+    local repo_root=$(_find_repo)
+
+    if [ -n "${repo_root}" ]
+    then
+        local manifests_dir="${repo_root}/.repo/manifests"
+        local git_dir="${manifests_dir}/.git"
+        local candidates
+
+        manifests=$(
+            git --git-dir "${git_dir}" ls-files "*.xml" 2>/dev/null)
+
+        if [ $? -eq 0 ]
+        then
+            _gen_comps "${manifests}"
+        fi
+    fi
+}
+
+_path_cmd_completion() {
+    _gen_comps "$(compgen -c ${cur})"
+}
+
+_is_option() {
+    local opt="$1"
+
+    [[ "${opt}" == -* ]]
+}
+
+_is_long_option() {
+    local opt="$1"
+
+    [[ "${opt}" == --* ]]
+}
+
+_expects_arg() {
+    local opt="$1"
+
+    if [[ ${ARG_OPTIONS[$opt]} ]]
+    then
+        return 0
+    else
+        return 1
+    fi
+}
+
+_handle_options() {
+    if _expects_arg "${prev}"
+    then
+        local handler=${ARG_OPTIONS[$prev]}
+        eval ${handler} "${cur}"
+    elif _is_option "${cur}"
+    then
+        _gen_comps "${OPTIONS[*]}"
+
+        local arg_short
+        local arg_long
+
+        for opt in "${!ARG_OPTIONS[@]}"
+        do
+            if _is_long_option "${opt}"
+            then
+                arg_long="${arg_long} ${opt}"
+            else
+                arg_short="${arg_short} ${opt}"
+            fi
+        done
+
+        _gen_comps "${arg_short}"
+        _gen_comps "${arg_long}" "="
+    else
+        return 1
+    fi
+
     return 0
-  fi
-  if [[ ${command} == "help" ]]; then
-    [ ${COMP_CWORD} -eq 2 ] && \
-      COMPREPLY=($(compgen -W "$(_list_repo_commands)" -- "${current}"))
+}
+
+_is_known_shortopt() {
+    local needle="$1"
+
+    for opt in ${OPTIONS[@]}
+    do
+        if [ "${opt}" = "${needle}" ]
+        then
+            return 0
+        fi
+    done
+
+    return 1
+}
+
+_is_known_longopt() {
+    local needle="$1"
+
+    [[ ${ARG_OPTIONS[$1]} ]]
+}
+
+_arg_index() {
+    local -i i=2               # skip repo and command
+    local -i ix=0
+
+    while [ ${i} -lt ${COMP_CWORD} ]
+    do
+        if _is_known_shortopt "${COMP_WORDS[i]}"
+        then
+            i+=1
+        elif _is_known_longopt "${COMP_WORDS[i]}"
+        then
+            i+=2
+        elif _is_option "${COMP_WORDS[i]}"
+        then
+            i+=1
+        else
+            i+=1
+            ix+=1
+        fi
+    done
+
+    eval $1="${ix}"
+}
+
+_when_ix() {
+    local ix="$1"
+    local completion="$2"
+
+    _arg_index arg_ix
+
+    if [ ${arg_ix} -eq ${ix} ]
+    then
+        ${completion}
+        return 0
+    else
+        return 1
+    fi
+}
+
+_when_first() {
+    _when_ix 0 "$1"
+}
+
+_when_even() {
+    local completion="$1"
+
+    _arg_index arg_ix
+
+    if [ $(( ${arg_ix} % 2 )) -eq 0 ]
+    then
+        ${completion}
+        return 0
+    else
+        return 1
+    fi
+}
+
+_cmp_opts() {
+    local opt="$1"
+    local word="$2"
+
+    if _is_option "${opt}" && ! _is_long_option "${opt}"
+    then
+        [ "${word}" == "${opt}" ]
+    else
+        [[ "${word}" == "${opt}"=* || "${word}" == "${opt}" ]]
+    fi
+}
+
+_before() {
+    local completion="$1"
+    local words
+
+    shift
+
+    _get_comp_words_by_ref -n = words
+
+    for word in "${words[@]}"
+    do
+        for needle in "$@"
+        do
+            if _cmp_opts "${needle}" "${word}"
+            then
+                return 1
+            fi
+        done
+    done
+
+    ${completion}
     return 0
-  fi
-  if [[ ${command} == "start" ]]; then
-    [ ${COMP_CWORD} -gt 2 ] && \
-      COMPREPLY=($(compgen -W "$(_list_repo_projects)" -- "${current}"))
+}
+
+_repo_init() {
+    OPTIONS=(
+        "-h" "--help"
+        "-q" "--quite"
+        "--mirror"
+        "--no-repo-verify"
+    )
+
+    ARG_OPTIONS=(
+        ["-u"]=_no_completion
+        ["--manifest-url"]=_no_completion
+        ["-b"]=_no_completion
+        ["--manifest-branch"]=_no_completion
+        ["-m"]=_manifest_completion
+        ["--manifest-name"]=_manifest_completion
+        ["--reference"]=_dir_completion
+        ["--repo-url"]=_no_completion
+        ["--repo-branch"]=_no_completion
+    )
+
+    _handle_options
+}
+
+_repo_help() {
+    OPTIONS=(
+        "-a" "--all"
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _when_first _command_completion
+}
+
+_repo_abandon() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _when_first _branch_completion || _project_completion
+}
+
+_repo_branch() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options
+}
+
+_repo_branches() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options
+}
+
+_repo_checkout() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _when_first _branch_completion || _project_completion
+}
+
+_repo_cherry_pick() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options
+}
+
+_repo_diff() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _project_completion
+}
+
+_repo_download() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _when_even _project_completion
+}
+
+_repo_forall() {
+    OPTIONS=(
+        "-h" "--help"
+        "-p"
+        "-v" "--verbose"
+    )
+
+    ARG_OPTIONS=(
+        ["-c"]=_path_cmd_completion
+        ["--command"]=_path_cmd_completion
+    )
+
+    _handle_options || _before _project_completion -c --command || _filedir
+}
+
+_repo_grep() {
+    OPTIONS=(
+        "-h" "--help"
+        "--cached"
+        "-r" "--revision"
+        "-i" "--ignore-case"
+        "-a" "--text"
+        "-I"
+        "-w" "--word-regexp"
+        "-v" "--invert-match"
+        "-G" "--basic-regexp"
+        "-E" "--extended-regexp"
+        "-F" "--fixed-strings"
+        "--all-match"
+        "--and" "--or" "--not"
+        "-(" "-)"
+        "-n"
+        "-l" "--name-only" "--files-with-matches"
+        "-L" "--files-without-match"
+    )
+
+    ARG_OPTIONS=(
+        ["-e"]=_no_completion
+        ["-C"]=_no_completion
+        ["-B"]=_no_completion
+        ["-A"]=_no_completion
+    )
+
+    _handle_options || _project_completion
+}
+
+_repo_list() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _project_completion
+}
+
+_repo_prune() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _project_completion
+}
+
+_repo_rebase() {
+    OPTIONS=(
+        "-h" "--help"
+        "-i" "--interactive"
+        "-f" "--force-rebase"
+        "--no-ff"
+        "-q" "--quiet"
+        "--autosquash"
+    )
+
+    ARG_OPTIONS=(
+        ["--whitespace"]=_no_completion
+    )
+
+    _handle_options || _project_completion
+}
+
+_repo_selfupdate() {
+    OPTIONS=(
+        "-h" "--help"
+        "--no-repo-verify"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options
+}
+
+_repo_smartsync() {
+    OPTIONS=(
+        "-h" "--help"
+        "-f" "--force-broken"
+        "-l" "--local-only"
+        "-n" "--network-only"
+        "-d" "--detach"
+        "-q" "--quiet"
+        "--no-repo-verify"
+    )
+
+    ARG_OPTIONS=(
+        ["-j"]=_no_completion
+        ["--jobs"]=_no_completion
+
+    )
+
+    _handle_options || _project_completion
+}
+
+_repo_stage() {
+    OPTIONS=(
+        "-h" "--help"
+        "-i" "--interactive"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _project_completion
+}
+
+_repo_start() {
+    OPTIONS=(
+        "-h" "--help"
+        "--all"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options || _when_first _branch_completion || _project_completion
+}
+
+_repo_status() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=(
+        ["-j"]=_no_completion
+        ["--jobs"]=_no_completion
+    )
+
+    _handle_options || _project_completion
+}
+
+_repo_sync() {
+    OPTIONS=(
+        "-h" "--help"
+        "-f" "--force-broken"
+        "-l" "--local-only"
+        "-n" "--network-only"
+        "-d" "--detach"
+        "-q" "--quiet"
+        "-s" "--smart-sync"
+        "--no-repo-verify"
+    )
+
+    ARG_OPTIONS=(
+        ["-j"]=_no_completion
+        ["--jobs"]=_no_completion
+    )
+
+    _handle_options || _project_completion
+}
+
+_repo_upload() {
+    OPTIONS=(
+        "-h" "--help"
+        "-t"
+        "--no-verify"
+        "--verify"
+    )
+
+    ARG_OPTIONS=(
+        ["--re"]=_no_completion
+        ["--reviewers"]=_no_completion
+        ["--cc"]=_no_completion
+        ["--br"]=_branch_completion
+    )
+
+    _handle_options || _project_completion
+}
+
+_repo_version() {
+    OPTIONS=(
+        "-h" "--help"
+    )
+
+    ARG_OPTIONS=()
+
+    _handle_options
+}
+
+_repo() {
+    COMPREPLY=()
+
+    _init_cur_prev
+
+    if [ ${COMP_CWORD} -eq 1 ]
+    then
+        _command_completion
+    else
+        local cmd=${COMP_WORDS[1]}
+        local handler=${CMD_HANDLERS["${cmd}"]}
+        if [ -n ${handler} ]
+        then
+            eval ${handler}
+        fi
+    fi
+
     return 0
-  fi
-  return 1
 }
 
-# Complete the repo arguments.
-_complete_repo() {
-  COMPREPLY=()
-  _complete_repo_command && return 0
-  _complete_repo_arg && return 0
-  return 0
-}
-
-complete -F _complete_repo repo
-
-# Add a way to get the "m" branch from repo easily; used by __git_branch_ps1()
-#
-# Repo seems to maintain a phony 'm/' remote and it always seems to be the name
-# of the manifest branch.  This will retrieve it.
-__git_m_branch() {
-  local git_dir=$(git rev-parse --git-dir 2> /dev/null)
-  if [ -n "${git_dir}" ]; then
-    echo $(cd ${git_dir}/refs/remotes/m 2> /dev/null && ls)
-  fi
-}
-
-# A "subclass" of __git_ps1 that adds the manifest branch name into the prompt.
-# ...if you're on manifest branch "0.11.257.B" and local branch "lo" and
-# pass " (%s)", we'll output " (0.11.257.B/lo)".  Note that we'll never show
-# the manifest branch 'master', since it's so common.
-__git_branch_ps1() {
-  local format_str="${1:- (%s)}"
-  local m_branch=$(__git_m_branch)
-  if [ "${m_branch}" != "master" -a -n "${m_branch}" ]; then
-    format_str=$(printf "${format_str}" "${m_branch}/%s")
-  fi
-  # for subshells, prefix the prompt with the shell nesting level
-  local lshlvl=""
-  [ ! -z "${SHLVL##*[!0-9]*}" ] && [ ${SHLVL} -gt 1 ] && lshlvl="${SHLVL} "
-  __git_ps1 "${lshlvl}${format_str}"
-}
-
-# Prompt functions should not error when in subshells
-export -f __gitdir
-export -f __git_ps1
-export -f __git_m_branch
-export -f __git_branch_ps1
+complete -o nospace -F _repo repo