├── .gitignore ├── bash_hosts ├── bash_profile ├── codespaces-post-start ├── host-boulder.local └── rcrc ├── host-coalface.local └── rcrc ├── host-shiro.local └── rcrc ├── inputrc ├── install.sh ├── ssh └── config ├── tag-development ├── bash_profile.development ├── bin │ ├── cssh │ ├── git-completion.sh │ ├── git-pingu │ ├── rails-schema-resolve │ ├── start │ ├── tmuxsend │ └── tmuxwidth ├── config │ └── nvim │ │ └── init.vim ├── ctags ├── gitconfig ├── gitignore ├── gitshrc ├── tmux.conf ├── vim │ └── ftplugin │ │ └── typescript.vim └── vimrc ├── tag-email ├── bin │ ├── mutt-notmuch-py │ └── sync-email ├── mbsyncrc ├── msmtprc ├── mutt │ ├── Equifax_Secure_CA.cert │ ├── config │ │ ├── account.georgebrock │ │ ├── account.gmail │ │ ├── mutt-colors-solarized-dark-256.muttrc │ │ └── pgp.muttrc │ └── mailcap ├── muttrc └── notmuch-config ├── tag-github ├── bash_profile.github └── vim │ └── local │ └── github.vimrc ├── tag-gpg ├── bash_profile.gpg ├── bin │ └── passp ├── gitconfig-gpg └── gnupg │ ├── gpg-agent.conf │ └── gpg.conf ├── tag-linux └── tmux.conf.local └── tag-macos ├── bash_profile.local ├── bin ├── battery ├── iterm ├── log ├── socks ├── switch └── wifi ├── gitconfig-macos └── tmux.conf.local /.gitignore: -------------------------------------------------------------------------------- 1 | offlineimap/Account-* 2 | offlineimap/Repository-* 3 | offlineimap/pid 4 | tags.temp 5 | -------------------------------------------------------------------------------- /bash_hosts: -------------------------------------------------------------------------------- 1 | $include /etc/hosts 2 | 3 | pingu 4 | yeti 5 | manfred.local 6 | -------------------------------------------------------------------------------- /bash_profile: -------------------------------------------------------------------------------- 1 | export PATH="$HOME/.bin:$PATH" 2 | 3 | short_codespace_name="$(echo "$CODESPACE_NAME" | sed -Ee 's/^georgebrock-(.+)-[^-]+$/\1/')" 4 | export PS1="\[\033[00;33m\]${short_codespace_name:-\h} \[\033[00;36m\]\W\[\033[31m\]\$ \[\033[0m\]" 5 | 6 | alias vim=nvim 7 | export EDITOR=nvim 8 | export VISUAL=nvim 9 | 10 | alias psg="ps auxwww | head -n 1 ; ps auxwww | grep -Ei" 11 | alias ll="ls -l" 12 | 13 | export HISTFILESIZE=10000 14 | export CLICOLOR=1 15 | export LSCOLORS=gxFxCxDxBxegedabagacad 16 | 17 | # Completion 18 | export hostname_completion_file=~/.bash_hosts 19 | complete -A hostname ssh 20 | 21 | for path in $(ls -a ~/.bash_profile.*); do 22 | source $path 23 | done 24 | -------------------------------------------------------------------------------- /codespaces-post-start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Not general to all Codespaces: must be configured in the repo's .devcontainer 4 | # setup. 5 | 6 | if [[ -x /workspaces/github/bin/build-ctags ]]; then 7 | /workspaces/github/bin/build-ctags 8 | fi 9 | -------------------------------------------------------------------------------- /host-boulder.local/rcrc: -------------------------------------------------------------------------------- 1 | TAGS="macos development email gpg" 2 | -------------------------------------------------------------------------------- /host-coalface.local/rcrc: -------------------------------------------------------------------------------- 1 | TAGS="macos development gpg github" 2 | DOTFILES_DIRS="/Users/georgebrock/.dotfiles /Users/georgebrock/.dotfiles-private" 3 | -------------------------------------------------------------------------------- /host-shiro.local/rcrc: -------------------------------------------------------------------------------- 1 | TAGS="macos development email gpg" 2 | -------------------------------------------------------------------------------- /inputrc: -------------------------------------------------------------------------------- 1 | set expand-tilde on 2 | set bell-style visible 3 | 4 | "\C-n": menu-complete 5 | "\e3": "#" 6 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec > >(tee -i $HOME/dotfiles_install.log) 4 | exec 2>&1 5 | set -x 6 | 7 | 8 | if [[ "$CODESPACES" = "true" ]]; then 9 | rm ~/.bashrc 10 | sudo apt-get install -y rcm tmux universal-ctags 11 | rcup -f -v -d . -t linux -t development -t github 12 | 13 | if [[ -d /etc/ssh ]]; then 14 | echo 'AcceptEnv TZ LC_*' >> /etc/ssh/sshd_config 15 | fi 16 | 17 | elif [[ "$(uname)" = "Darwin" ]]; then 18 | brew install rcm 19 | rcup -v -t macos 20 | rcup -v 21 | else 22 | >&2 echo "error: Unknown system" 23 | exit 1 24 | fi 25 | 26 | if [[ ! -d "$HOME/.vim/bundle/Vundle.vim" ]]; then 27 | git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim 28 | fi 29 | 30 | vim +PluginInstall +qa 31 | 32 | if [[ "$CODESPACES" = "true" ]]; then 33 | git config --global url.https://github.com/.insteadOf git@github.com: 34 | git config --global gpg.program /.codespaces/bin/gh-gpgsign 35 | fi 36 | -------------------------------------------------------------------------------- /ssh/config: -------------------------------------------------------------------------------- 1 | SendEnv TZ LC_* 2 | Include config.* 3 | 4 | Host github.com 5 | Hostname ssh.github.com 6 | Port 443 7 | User git 8 | 9 | Host *.dev 10 | ForwardAgent yes 11 | 12 | Host yeti yeti.georgebrock.com 13 | User georgebrock 14 | Hostname yeti.georgebrock.com 15 | 16 | Host pippin pippin.brock.network 17 | User pi 18 | Hostname pippin.brock.network 19 | ForwardAgent yes 20 | 21 | Host merry merry.brock.network 22 | User pi 23 | Hostname merry.brock.network 24 | ForwardAgent yes 25 | -------------------------------------------------------------------------------- /tag-development/bash_profile.development: -------------------------------------------------------------------------------- 1 | source ~/.bin/git-completion.sh 2 | 3 | alias g=git 4 | alias gc="git commit -v" 5 | alias gca="git commit --amend -v" 6 | alias gs="git status" 7 | alias gd="git diff" 8 | alias gds="git diff --stat" 9 | alias gdc="git diff --cached" 10 | alias ga="git add" 11 | alias gf="git fetch" 12 | alias gb="git branch" 13 | alias gg="git grep -En" 14 | -------------------------------------------------------------------------------- /tag-development/bin/cssh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | codespace="$1" 4 | 5 | if ! ssh-add -l 2>&1 > /dev/null; then 6 | echo >&2 "$(basename "$0"): need to add private keys to the SSH agent" 7 | ssh-add 8 | fi 9 | 10 | if [[ $EUID -ne 0 ]]; then 11 | echo >&2 "$(basename "$0"): must run as root to forward port 80, re-running with sudo" 12 | exec sudo /bin/sh "$0" "$@" 13 | fi 14 | 15 | gh cs ports forward 80:80 3012:3012 -c "$codespace" & 16 | pid=$! 17 | 18 | sleep 5 19 | gh cs ssh -c "$codespace" 20 | 21 | kill -s TERM "$pid" 22 | echo "Done." 23 | -------------------------------------------------------------------------------- /tag-development/bin/git-completion.sh: -------------------------------------------------------------------------------- 1 | # bash/zsh completion support for core Git. 2 | # 3 | # Copyright (C) 2006,2007 Shawn O. Pearce 4 | # Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). 5 | # Distributed under the GNU General Public License, version 2.0. 6 | # 7 | # The contained completion routines provide support for completing: 8 | # 9 | # *) local and remote branch names 10 | # *) local and remote tag names 11 | # *) .git/remotes file names 12 | # *) git 'subcommands' 13 | # *) git email aliases for git-send-email 14 | # *) tree paths within 'ref:path/to/file' expressions 15 | # *) file paths within current working directory and index 16 | # *) common --long-options 17 | # 18 | # To use these routines: 19 | # 20 | # 1) Copy this file to somewhere (e.g. ~/.git-completion.bash). 21 | # 2) Add the following line to your .bashrc/.zshrc: 22 | # source ~/.git-completion.bash 23 | # 3) Consider changing your PS1 to also show the current branch, 24 | # see git-prompt.sh for details. 25 | # 26 | # If you use complex aliases of form '!f() { ... }; f', you can use the null 27 | # command ':' as the first command in the function body to declare the desired 28 | # completion style. For example '!f() { : git commit ; ... }; f' will 29 | # tell the completion to use commit completion. This also works with aliases 30 | # of form "!sh -c '...'". For example, "!sh -c ': git commit ; ... '". 31 | # 32 | # Compatible with bash 3.2.57. 33 | # 34 | # You can set the following environment variables to influence the behavior of 35 | # the completion routines: 36 | # 37 | # GIT_COMPLETION_CHECKOUT_NO_GUESS 38 | # 39 | # When set to "1", do not include "DWIM" suggestions in git-checkout 40 | # and git-switch completion (e.g., completing "foo" when "origin/foo" 41 | # exists). 42 | # 43 | # GIT_COMPLETION_SHOW_ALL 44 | # 45 | # When set to "1" suggest all options, including options which are 46 | # typically hidden (e.g. '--allow-empty' for 'git commit'). 47 | 48 | case "$COMP_WORDBREAKS" in 49 | *:*) : great ;; 50 | *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" 51 | esac 52 | 53 | # Discovers the path to the git repository taking any '--git-dir=' and 54 | # '-C ' options into account and stores it in the $__git_repo_path 55 | # variable. 56 | __git_find_repo_path () 57 | { 58 | if [ -n "${__git_repo_path-}" ]; then 59 | # we already know where it is 60 | return 61 | fi 62 | 63 | if [ -n "${__git_C_args-}" ]; then 64 | __git_repo_path="$(git "${__git_C_args[@]}" \ 65 | ${__git_dir:+--git-dir="$__git_dir"} \ 66 | rev-parse --absolute-git-dir 2>/dev/null)" 67 | elif [ -n "${__git_dir-}" ]; then 68 | test -d "$__git_dir" && 69 | __git_repo_path="$__git_dir" 70 | elif [ -n "${GIT_DIR-}" ]; then 71 | test -d "${GIT_DIR-}" && 72 | __git_repo_path="$GIT_DIR" 73 | elif [ -d .git ]; then 74 | __git_repo_path=.git 75 | else 76 | __git_repo_path="$(git rev-parse --git-dir 2>/dev/null)" 77 | fi 78 | } 79 | 80 | # Deprecated: use __git_find_repo_path() and $__git_repo_path instead 81 | # __gitdir accepts 0 or 1 arguments (i.e., location) 82 | # returns location of .git repo 83 | __gitdir () 84 | { 85 | if [ -z "${1-}" ]; then 86 | __git_find_repo_path || return 1 87 | echo "$__git_repo_path" 88 | elif [ -d "$1/.git" ]; then 89 | echo "$1/.git" 90 | else 91 | echo "$1" 92 | fi 93 | } 94 | 95 | # Runs git with all the options given as argument, respecting any 96 | # '--git-dir=' and '-C ' options present on the command line 97 | __git () 98 | { 99 | git ${__git_C_args:+"${__git_C_args[@]}"} \ 100 | ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null 101 | } 102 | 103 | # Removes backslash escaping, single quotes and double quotes from a word, 104 | # stores the result in the variable $dequoted_word. 105 | # 1: The word to dequote. 106 | __git_dequote () 107 | { 108 | local rest="$1" len ch 109 | 110 | dequoted_word="" 111 | 112 | while test -n "$rest"; do 113 | len=${#dequoted_word} 114 | dequoted_word="$dequoted_word${rest%%[\\\'\"]*}" 115 | rest="${rest:$((${#dequoted_word}-$len))}" 116 | 117 | case "${rest:0:1}" in 118 | \\) 119 | ch="${rest:1:1}" 120 | case "$ch" in 121 | $'\n') 122 | ;; 123 | *) 124 | dequoted_word="$dequoted_word$ch" 125 | ;; 126 | esac 127 | rest="${rest:2}" 128 | ;; 129 | \') 130 | rest="${rest:1}" 131 | len=${#dequoted_word} 132 | dequoted_word="$dequoted_word${rest%%\'*}" 133 | rest="${rest:$((${#dequoted_word}-$len+1))}" 134 | ;; 135 | \") 136 | rest="${rest:1}" 137 | while test -n "$rest" ; do 138 | len=${#dequoted_word} 139 | dequoted_word="$dequoted_word${rest%%[\\\"]*}" 140 | rest="${rest:$((${#dequoted_word}-$len))}" 141 | case "${rest:0:1}" in 142 | \\) 143 | ch="${rest:1:1}" 144 | case "$ch" in 145 | \"|\\|\$|\`) 146 | dequoted_word="$dequoted_word$ch" 147 | ;; 148 | $'\n') 149 | ;; 150 | *) 151 | dequoted_word="$dequoted_word\\$ch" 152 | ;; 153 | esac 154 | rest="${rest:2}" 155 | ;; 156 | \") 157 | rest="${rest:1}" 158 | break 159 | ;; 160 | esac 161 | done 162 | ;; 163 | esac 164 | done 165 | } 166 | 167 | # The following function is based on code from: 168 | # 169 | # bash_completion - programmable completion functions for bash 3.2+ 170 | # 171 | # Copyright © 2006-2008, Ian Macdonald 172 | # © 2009-2010, Bash Completion Maintainers 173 | # 174 | # 175 | # This program is free software; you can redistribute it and/or modify 176 | # it under the terms of the GNU General Public License as published by 177 | # the Free Software Foundation; either version 2, or (at your option) 178 | # any later version. 179 | # 180 | # This program is distributed in the hope that it will be useful, 181 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 182 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 183 | # GNU General Public License for more details. 184 | # 185 | # You should have received a copy of the GNU General Public License 186 | # along with this program; if not, see . 187 | # 188 | # The latest version of this software can be obtained here: 189 | # 190 | # http://bash-completion.alioth.debian.org/ 191 | # 192 | # RELEASE: 2.x 193 | 194 | # This function can be used to access a tokenized list of words 195 | # on the command line: 196 | # 197 | # __git_reassemble_comp_words_by_ref '=:' 198 | # if test "${words_[cword_-1]}" = -w 199 | # then 200 | # ... 201 | # fi 202 | # 203 | # The argument should be a collection of characters from the list of 204 | # word completion separators (COMP_WORDBREAKS) to treat as ordinary 205 | # characters. 206 | # 207 | # This is roughly equivalent to going back in time and setting 208 | # COMP_WORDBREAKS to exclude those characters. The intent is to 209 | # make option types like --date= and : easy to 210 | # recognize by treating each shell word as a single token. 211 | # 212 | # It is best not to set COMP_WORDBREAKS directly because the value is 213 | # shared with other completion scripts. By the time the completion 214 | # function gets called, COMP_WORDS has already been populated so local 215 | # changes to COMP_WORDBREAKS have no effect. 216 | # 217 | # Output: words_, cword_, cur_. 218 | 219 | __git_reassemble_comp_words_by_ref() 220 | { 221 | local exclude i j first 222 | # Which word separators to exclude? 223 | exclude="${1//[^$COMP_WORDBREAKS]}" 224 | cword_=$COMP_CWORD 225 | if [ -z "$exclude" ]; then 226 | words_=("${COMP_WORDS[@]}") 227 | return 228 | fi 229 | # List of word completion separators has shrunk; 230 | # re-assemble words to complete. 231 | for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do 232 | # Append each nonempty word consisting of just 233 | # word separator characters to the current word. 234 | first=t 235 | while 236 | [ $i -gt 0 ] && 237 | [ -n "${COMP_WORDS[$i]}" ] && 238 | # word consists of excluded word separators 239 | [ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ] 240 | do 241 | # Attach to the previous token, 242 | # unless the previous token is the command name. 243 | if [ $j -ge 2 ] && [ -n "$first" ]; then 244 | ((j--)) 245 | fi 246 | first= 247 | words_[$j]=${words_[j]}${COMP_WORDS[i]} 248 | if [ $i = $COMP_CWORD ]; then 249 | cword_=$j 250 | fi 251 | if (($i < ${#COMP_WORDS[@]} - 1)); then 252 | ((i++)) 253 | else 254 | # Done. 255 | return 256 | fi 257 | done 258 | words_[$j]=${words_[j]}${COMP_WORDS[i]} 259 | if [ $i = $COMP_CWORD ]; then 260 | cword_=$j 261 | fi 262 | done 263 | } 264 | 265 | if ! type _get_comp_words_by_ref >/dev/null 2>&1; then 266 | _get_comp_words_by_ref () 267 | { 268 | local exclude cur_ words_ cword_ 269 | if [ "$1" = "-n" ]; then 270 | exclude=$2 271 | shift 2 272 | fi 273 | __git_reassemble_comp_words_by_ref "$exclude" 274 | cur_=${words_[cword_]} 275 | while [ $# -gt 0 ]; do 276 | case "$1" in 277 | cur) 278 | cur=$cur_ 279 | ;; 280 | prev) 281 | prev=${words_[$cword_-1]} 282 | ;; 283 | words) 284 | words=("${words_[@]}") 285 | ;; 286 | cword) 287 | cword=$cword_ 288 | ;; 289 | esac 290 | shift 291 | done 292 | } 293 | fi 294 | 295 | # Fills the COMPREPLY array with prefiltered words without any additional 296 | # processing. 297 | # Callers must take care of providing only words that match the current word 298 | # to be completed and adding any prefix and/or suffix (trailing space!), if 299 | # necessary. 300 | # 1: List of newline-separated matching completion words, complete with 301 | # prefix and suffix. 302 | __gitcomp_direct () 303 | { 304 | local IFS=$'\n' 305 | 306 | COMPREPLY=($1) 307 | } 308 | 309 | # Similar to __gitcomp_direct, but appends to COMPREPLY instead. 310 | # Callers must take care of providing only words that match the current word 311 | # to be completed and adding any prefix and/or suffix (trailing space!), if 312 | # necessary. 313 | # 1: List of newline-separated matching completion words, complete with 314 | # prefix and suffix. 315 | __gitcomp_direct_append () 316 | { 317 | local IFS=$'\n' 318 | 319 | COMPREPLY+=($1) 320 | } 321 | 322 | __gitcompappend () 323 | { 324 | local x i=${#COMPREPLY[@]} 325 | for x in $1; do 326 | if [[ "$x" == "$3"* ]]; then 327 | COMPREPLY[i++]="$2$x$4" 328 | fi 329 | done 330 | } 331 | 332 | __gitcompadd () 333 | { 334 | COMPREPLY=() 335 | __gitcompappend "$@" 336 | } 337 | 338 | # Generates completion reply, appending a space to possible completion words, 339 | # if necessary. 340 | # It accepts 1 to 4 arguments: 341 | # 1: List of possible completion words. 342 | # 2: A prefix to be added to each possible completion word (optional). 343 | # 3: Generate possible completion matches for this word (optional). 344 | # 4: A suffix to be appended to each possible completion word (optional). 345 | __gitcomp () 346 | { 347 | local cur_="${3-$cur}" 348 | 349 | case "$cur_" in 350 | --*=) 351 | ;; 352 | --no-*) 353 | local c i=0 IFS=$' \t\n' 354 | for c in $1; do 355 | if [[ $c == "--" ]]; then 356 | continue 357 | fi 358 | c="$c${4-}" 359 | if [[ $c == "$cur_"* ]]; then 360 | case $c in 361 | --*=|*.) ;; 362 | *) c="$c " ;; 363 | esac 364 | COMPREPLY[i++]="${2-}$c" 365 | fi 366 | done 367 | ;; 368 | *) 369 | local c i=0 IFS=$' \t\n' 370 | for c in $1; do 371 | if [[ $c == "--" ]]; then 372 | c="--no-...${4-}" 373 | if [[ $c == "$cur_"* ]]; then 374 | COMPREPLY[i++]="${2-}$c " 375 | fi 376 | break 377 | fi 378 | c="$c${4-}" 379 | if [[ $c == "$cur_"* ]]; then 380 | case $c in 381 | *=|*.) ;; 382 | *) c="$c " ;; 383 | esac 384 | COMPREPLY[i++]="${2-}$c" 385 | fi 386 | done 387 | ;; 388 | esac 389 | } 390 | 391 | # Clear the variables caching builtins' options when (re-)sourcing 392 | # the completion script. 393 | if [[ -n ${ZSH_VERSION-} ]]; then 394 | unset ${(M)${(k)parameters[@]}:#__gitcomp_builtin_*} 2>/dev/null 395 | else 396 | unset $(compgen -v __gitcomp_builtin_) 397 | fi 398 | 399 | # This function is equivalent to 400 | # 401 | # __gitcomp "$(git xxx --git-completion-helper) ..." 402 | # 403 | # except that the output is cached. Accept 1-3 arguments: 404 | # 1: the git command to execute, this is also the cache key 405 | # 2: extra options to be added on top (e.g. negative forms) 406 | # 3: options to be excluded 407 | __gitcomp_builtin () 408 | { 409 | # spaces must be replaced with underscore for multi-word 410 | # commands, e.g. "git remote add" becomes remote_add. 411 | local cmd="$1" 412 | local incl="${2-}" 413 | local excl="${3-}" 414 | 415 | local var=__gitcomp_builtin_"${cmd/-/_}" 416 | local options 417 | eval "options=\${$var-}" 418 | 419 | if [ -z "$options" ]; then 420 | local completion_helper 421 | if [ "$GIT_COMPLETION_SHOW_ALL" = "1" ]; then 422 | completion_helper="--git-completion-helper-all" 423 | else 424 | completion_helper="--git-completion-helper" 425 | fi 426 | # leading and trailing spaces are significant to make 427 | # option removal work correctly. 428 | options=" $incl $(__git ${cmd/_/ } $completion_helper) " || return 429 | 430 | for i in $excl; do 431 | options="${options/ $i / }" 432 | done 433 | eval "$var=\"$options\"" 434 | fi 435 | 436 | __gitcomp "$options" 437 | } 438 | 439 | # Variation of __gitcomp_nl () that appends to the existing list of 440 | # completion candidates, COMPREPLY. 441 | __gitcomp_nl_append () 442 | { 443 | local IFS=$'\n' 444 | __gitcompappend "$1" "${2-}" "${3-$cur}" "${4- }" 445 | } 446 | 447 | # Generates completion reply from newline-separated possible completion words 448 | # by appending a space to all of them. 449 | # It accepts 1 to 4 arguments: 450 | # 1: List of possible completion words, separated by a single newline. 451 | # 2: A prefix to be added to each possible completion word (optional). 452 | # 3: Generate possible completion matches for this word (optional). 453 | # 4: A suffix to be appended to each possible completion word instead of 454 | # the default space (optional). If specified but empty, nothing is 455 | # appended. 456 | __gitcomp_nl () 457 | { 458 | COMPREPLY=() 459 | __gitcomp_nl_append "$@" 460 | } 461 | 462 | # Fills the COMPREPLY array with prefiltered paths without any additional 463 | # processing. 464 | # Callers must take care of providing only paths that match the current path 465 | # to be completed and adding any prefix path components, if necessary. 466 | # 1: List of newline-separated matching paths, complete with all prefix 467 | # path components. 468 | __gitcomp_file_direct () 469 | { 470 | local IFS=$'\n' 471 | 472 | COMPREPLY=($1) 473 | 474 | # use a hack to enable file mode in bash < 4 475 | compopt -o filenames +o nospace 2>/dev/null || 476 | compgen -f /non-existing-dir/ >/dev/null || 477 | true 478 | } 479 | 480 | # Generates completion reply with compgen from newline-separated possible 481 | # completion filenames. 482 | # It accepts 1 to 3 arguments: 483 | # 1: List of possible completion filenames, separated by a single newline. 484 | # 2: A directory prefix to be added to each possible completion filename 485 | # (optional). 486 | # 3: Generate possible completion matches for this word (optional). 487 | __gitcomp_file () 488 | { 489 | local IFS=$'\n' 490 | 491 | # XXX does not work when the directory prefix contains a tilde, 492 | # since tilde expansion is not applied. 493 | # This means that COMPREPLY will be empty and Bash default 494 | # completion will be used. 495 | __gitcompadd "$1" "${2-}" "${3-$cur}" "" 496 | 497 | # use a hack to enable file mode in bash < 4 498 | compopt -o filenames +o nospace 2>/dev/null || 499 | compgen -f /non-existing-dir/ >/dev/null || 500 | true 501 | } 502 | 503 | # Execute 'git ls-files', unless the --committable option is specified, in 504 | # which case it runs 'git diff-index' to find out the files that can be 505 | # committed. It return paths relative to the directory specified in the first 506 | # argument, and using the options specified in the second argument. 507 | __git_ls_files_helper () 508 | { 509 | if [ "$2" == "--committable" ]; then 510 | __git -C "$1" -c core.quotePath=false diff-index \ 511 | --name-only --relative HEAD -- "${3//\\/\\\\}*" 512 | else 513 | # NOTE: $2 is not quoted in order to support multiple options 514 | __git -C "$1" -c core.quotePath=false ls-files \ 515 | --exclude-standard $2 -- "${3//\\/\\\\}*" 516 | fi 517 | } 518 | 519 | 520 | # __git_index_files accepts 1 or 2 arguments: 521 | # 1: Options to pass to ls-files (required). 522 | # 2: A directory path (optional). 523 | # If provided, only files within the specified directory are listed. 524 | # Sub directories are never recursed. Path must have a trailing 525 | # slash. 526 | # 3: List only paths matching this path component (optional). 527 | __git_index_files () 528 | { 529 | local root="$2" match="$3" 530 | 531 | __git_ls_files_helper "$root" "$1" "${match:-?}" | 532 | awk -F / -v pfx="${2//\\/\\\\}" '{ 533 | paths[$1] = 1 534 | } 535 | END { 536 | for (p in paths) { 537 | if (substr(p, 1, 1) != "\"") { 538 | # No special characters, easy! 539 | print pfx p 540 | continue 541 | } 542 | 543 | # The path is quoted. 544 | p = dequote(p) 545 | if (p == "") 546 | continue 547 | 548 | # Even when a directory name itself does not contain 549 | # any special characters, it will still be quoted if 550 | # any of its (stripped) trailing path components do. 551 | # Because of this we may have seen the same directory 552 | # both quoted and unquoted. 553 | if (p in paths) 554 | # We have seen the same directory unquoted, 555 | # skip it. 556 | continue 557 | else 558 | print pfx p 559 | } 560 | } 561 | function dequote(p, bs_idx, out, esc, esc_idx, dec) { 562 | # Skip opening double quote. 563 | p = substr(p, 2) 564 | 565 | # Interpret backslash escape sequences. 566 | while ((bs_idx = index(p, "\\")) != 0) { 567 | out = out substr(p, 1, bs_idx - 1) 568 | esc = substr(p, bs_idx + 1, 1) 569 | p = substr(p, bs_idx + 2) 570 | 571 | if ((esc_idx = index("abtvfr\"\\", esc)) != 0) { 572 | # C-style one-character escape sequence. 573 | out = out substr("\a\b\t\v\f\r\"\\", 574 | esc_idx, 1) 575 | } else if (esc == "n") { 576 | # Uh-oh, a newline character. 577 | # We cannot reliably put a pathname 578 | # containing a newline into COMPREPLY, 579 | # and the newline would create a mess. 580 | # Skip this path. 581 | return "" 582 | } else { 583 | # Must be a \nnn octal value, then. 584 | dec = esc * 64 + \ 585 | substr(p, 1, 1) * 8 + \ 586 | substr(p, 2, 1) 587 | out = out sprintf("%c", dec) 588 | p = substr(p, 3) 589 | } 590 | } 591 | # Drop closing double quote, if there is one. 592 | # (There is not any if this is a directory, as it was 593 | # already stripped with the trailing path components.) 594 | if (substr(p, length(p), 1) == "\"") 595 | out = out substr(p, 1, length(p) - 1) 596 | else 597 | out = out p 598 | 599 | return out 600 | }' 601 | } 602 | 603 | # __git_complete_index_file requires 1 argument: 604 | # 1: the options to pass to ls-file 605 | # 606 | # The exception is --committable, which finds the files appropriate commit. 607 | __git_complete_index_file () 608 | { 609 | local dequoted_word pfx="" cur_ 610 | 611 | __git_dequote "$cur" 612 | 613 | case "$dequoted_word" in 614 | ?*/*) 615 | pfx="${dequoted_word%/*}/" 616 | cur_="${dequoted_word##*/}" 617 | ;; 618 | *) 619 | cur_="$dequoted_word" 620 | esac 621 | 622 | __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")" 623 | } 624 | 625 | # Lists branches from the local repository. 626 | # 1: A prefix to be added to each listed branch (optional). 627 | # 2: List only branches matching this word (optional; list all branches if 628 | # unset or empty). 629 | # 3: A suffix to be appended to each listed branch (optional). 630 | __git_heads () 631 | { 632 | local pfx="${1-}" cur_="${2-}" sfx="${3-}" 633 | 634 | __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ 635 | "refs/heads/$cur_*" "refs/heads/$cur_*/**" 636 | } 637 | 638 | # Lists branches from remote repositories. 639 | # 1: A prefix to be added to each listed branch (optional). 640 | # 2: List only branches matching this word (optional; list all branches if 641 | # unset or empty). 642 | # 3: A suffix to be appended to each listed branch (optional). 643 | __git_remote_heads () 644 | { 645 | local pfx="${1-}" cur_="${2-}" sfx="${3-}" 646 | 647 | __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ 648 | "refs/remotes/$cur_*" "refs/remotes/$cur_*/**" 649 | } 650 | 651 | # Lists tags from the local repository. 652 | # Accepts the same positional parameters as __git_heads() above. 653 | __git_tags () 654 | { 655 | local pfx="${1-}" cur_="${2-}" sfx="${3-}" 656 | 657 | __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ 658 | "refs/tags/$cur_*" "refs/tags/$cur_*/**" 659 | } 660 | 661 | # List unique branches from refs/remotes used for 'git checkout' and 'git 662 | # switch' tracking DWIMery. 663 | # 1: A prefix to be added to each listed branch (optional) 664 | # 2: List only branches matching this word (optional; list all branches if 665 | # unset or empty). 666 | # 3: A suffix to be appended to each listed branch (optional). 667 | __git_dwim_remote_heads () 668 | { 669 | local pfx="${1-}" cur_="${2-}" sfx="${3-}" 670 | local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers 671 | 672 | # employ the heuristic used by git checkout and git switch 673 | # Try to find a remote branch that cur_es the completion word 674 | # but only output if the branch name is unique 675 | __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ 676 | --sort="refname:strip=3" \ 677 | "refs/remotes/*/$cur_*" "refs/remotes/*/$cur_*/**" | \ 678 | uniq -u 679 | } 680 | 681 | # Lists refs from the local (by default) or from a remote repository. 682 | # It accepts 0, 1 or 2 arguments: 683 | # 1: The remote to list refs from (optional; ignored, if set but empty). 684 | # Can be the name of a configured remote, a path, or a URL. 685 | # 2: In addition to local refs, list unique branches from refs/remotes/ for 686 | # 'git checkout's tracking DWIMery (optional; ignored, if set but empty). 687 | # 3: A prefix to be added to each listed ref (optional). 688 | # 4: List only refs matching this word (optional; list all refs if unset or 689 | # empty). 690 | # 5: A suffix to be appended to each listed ref (optional; ignored, if set 691 | # but empty). 692 | # 693 | # Use __git_complete_refs() instead. 694 | __git_refs () 695 | { 696 | local i hash dir track="${2-}" 697 | local list_refs_from=path remote="${1-}" 698 | local format refs 699 | local pfx="${3-}" cur_="${4-$cur}" sfx="${5-}" 700 | local match="${4-}" 701 | local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers 702 | 703 | __git_find_repo_path 704 | dir="$__git_repo_path" 705 | 706 | if [ -z "$remote" ]; then 707 | if [ -z "$dir" ]; then 708 | return 709 | fi 710 | else 711 | if __git_is_configured_remote "$remote"; then 712 | # configured remote takes precedence over a 713 | # local directory with the same name 714 | list_refs_from=remote 715 | elif [ -d "$remote/.git" ]; then 716 | dir="$remote/.git" 717 | elif [ -d "$remote" ]; then 718 | dir="$remote" 719 | else 720 | list_refs_from=url 721 | fi 722 | fi 723 | 724 | if [ "$list_refs_from" = path ]; then 725 | if [[ "$cur_" == ^* ]]; then 726 | pfx="$pfx^" 727 | fer_pfx="$fer_pfx^" 728 | cur_=${cur_#^} 729 | match=${match#^} 730 | fi 731 | case "$cur_" in 732 | refs|refs/*) 733 | format="refname" 734 | refs=("$match*" "$match*/**") 735 | track="" 736 | ;; 737 | *) 738 | for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD; do 739 | case "$i" in 740 | $match*) 741 | if [ -e "$dir/$i" ]; then 742 | echo "$pfx$i$sfx" 743 | fi 744 | ;; 745 | esac 746 | done 747 | format="refname:strip=2" 748 | refs=("refs/tags/$match*" "refs/tags/$match*/**" 749 | "refs/heads/$match*" "refs/heads/$match*/**" 750 | "refs/remotes/$match*" "refs/remotes/$match*/**") 751 | ;; 752 | esac 753 | __git_dir="$dir" __git for-each-ref --format="$fer_pfx%($format)$sfx" \ 754 | "${refs[@]}" 755 | if [ -n "$track" ]; then 756 | __git_dwim_remote_heads "$pfx" "$match" "$sfx" 757 | fi 758 | return 759 | fi 760 | case "$cur_" in 761 | refs|refs/*) 762 | __git ls-remote "$remote" "$match*" | \ 763 | while read -r hash i; do 764 | case "$i" in 765 | *^{}) ;; 766 | *) echo "$pfx$i$sfx" ;; 767 | esac 768 | done 769 | ;; 770 | *) 771 | if [ "$list_refs_from" = remote ]; then 772 | case "HEAD" in 773 | $match*) echo "${pfx}HEAD$sfx" ;; 774 | esac 775 | __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ 776 | "refs/remotes/$remote/$match*" \ 777 | "refs/remotes/$remote/$match*/**" 778 | else 779 | local query_symref 780 | case "HEAD" in 781 | $match*) query_symref="HEAD" ;; 782 | esac 783 | __git ls-remote "$remote" $query_symref \ 784 | "refs/tags/$match*" "refs/heads/$match*" \ 785 | "refs/remotes/$match*" | 786 | while read -r hash i; do 787 | case "$i" in 788 | *^{}) ;; 789 | refs/*) echo "$pfx${i#refs/*/}$sfx" ;; 790 | *) echo "$pfx$i$sfx" ;; # symbolic refs 791 | esac 792 | done 793 | fi 794 | ;; 795 | esac 796 | } 797 | 798 | # Completes refs, short and long, local and remote, symbolic and pseudo. 799 | # 800 | # Usage: __git_complete_refs [