├── .gitignore ├── screenshot-svn.png ├── screenshot-labels.png ├── screenshot-labels.xcf ├── screenshot-prompt-git.png ├── screenshot-prompt-basic.png ├── README ├── Makefile ├── demo-script ├── .old ├── git-demo └── demo-more-script ├── git-prompt.conf ├── index.txt └── git-prompt.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | 3 | # emacs backup and lock files 4 | *~ 5 | \#*\# 6 | -------------------------------------------------------------------------------- /screenshot-svn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cougar/git-prompt/master/screenshot-svn.png -------------------------------------------------------------------------------- /screenshot-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cougar/git-prompt/master/screenshot-labels.png -------------------------------------------------------------------------------- /screenshot-labels.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cougar/git-prompt/master/screenshot-labels.xcf -------------------------------------------------------------------------------- /screenshot-prompt-git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cougar/git-prompt/master/screenshot-prompt-git.png -------------------------------------------------------------------------------- /screenshot-prompt-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cougar/git-prompt/master/screenshot-prompt-basic.png -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | GIT Prompt for BASH 2 | 3 | Screenshots and docs are at: http://volnitsky.com/project/git-prompt 4 | 5 | 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(USER),lvv) 2 | HOMEDIR := /home/lvv/p/volnitsky.com/ 3 | INCLUDE := $(HOMEDIR)/include.mk 4 | else 5 | INCLUDE := /dev/null 6 | endif 7 | 8 | include $(INCLUDE) 9 | 10 | 11 | COPY_LIST = git-prompt.sh 12 | 13 | 14 | install: 15 | cp -v git-prompt.sh /etc/ 16 | [ -f /etc/git-prompt.conf ] || cp -v git-prompt.conf /etc/ 17 | 18 | tgit: 19 | xclip -i git-demo 20 | echo "ready to paste ..." 21 | 22 | -------------------------------------------------------------------------------- /demo-script: -------------------------------------------------------------------------------- 1 | cd p/git-prompt 2 | . ./git-prompt.sh 3 | cd 4 | test -d demo && rm -rf demo/ 5 | test -d ../demo && cd .. && rm -rf demo/ 6 | clear 7 | : ------------------------------------------------------------------------ 8 | clear 9 | mkdir demo 10 | cd demo 11 | git init 12 | echo "1st line" > FOO 13 | git add FOO 14 | touch untracked.o 15 | echo '*.o' >> .gitignore 16 | git commit -q -m "1st line" FOO 17 | git checkout -b test 18 | echo "added 2nd line in test" >> FOO 19 | git add FOO 20 | echo "2nd in BAR" > BAR 21 | git add BAR 22 | echo "added 3nd line FOO" >> FOO 23 | git add FOO 24 | git commit -q -m "FOO moded, BAR added" 25 | git checkout master 26 | cat FOO 27 | echo "now added 2nd line in master" >> FOO 28 | git add FOO 29 | git commit -q -m "2nd line" 30 | git merge test 31 | git cat-file -p test:FOO > FOO 32 | git add FOO 33 | git commit -q -m "merged" 34 | cat FOO 35 | git checkout HEAD^ 36 | cat FOO 37 | git checkout HEAD 38 | git clean -f 39 | : ------------------------------------------------------------------------ 40 | cd .. 41 | -------------------------------------------------------------------------------- /.old/git-demo: -------------------------------------------------------------------------------- 1 | set +xv 2 | . /t/prompt/prompt 3 | test -d demo && rm -rf demo/ 4 | test -d ../demo && cd .. && rm -rf demo/ 5 | clear 6 | : ------------------------------------------------------------------------ 7 | mkdir demo 8 | cd demo 9 | git init 10 | echo 'main() {puts("Hello World!");}' > hello.c 11 | make hello && ./hello 12 | git add hello.c 13 | git commit -q -m "1st hello" 14 | git checkout -b universe; git clean -f 15 | echo 'main() {puts("Hello Universe!");}' > hello.c 16 | git add hello.c 17 | echo "// FIXME: includes" >> hello.c 18 | git add hello.c 19 | git commit -q -m "notes to self" 20 | git checkout master 21 | cat hello.c 22 | echo 'main() {puts("Hello Universe!"); exit(0);}' > hello.c 23 | git add hello.c 24 | git commit -q -m "fixed exit code" 25 | git merge universe 26 | git cat-file -p universe:hello.c > hello.c 27 | git add hello.c 28 | git commit -q -m "merged" 29 | cat hello.c 30 | git checkout HEAD^ 31 | git checkout HEAD^ 32 | cat hello.c 33 | git checkout master 34 | sleep 2; echo "this emulates unsaved vim session" > .hello.c.swp 35 | git clean -f 36 | : ------------------------------------------------------------------------ 37 | cd .. 38 | -------------------------------------------------------------------------------- /.old/demo-more-script: -------------------------------------------------------------------------------- 1 | set +xv 2 | . /t/prompt/prompt 3 | test -d demo && rm -rf demo/ 4 | test -d ../demo && cd .. && rm -rf demo/ 5 | clear 6 | : ------------------------------------------------------------------------ 7 | mkdir demo 8 | cd demo 9 | git init 10 | echo 'main() {puts("Hello World!");}' > hello.c 11 | make hello && ./hello 12 | git add hello.c 13 | git commit -q -m "1st hello" 14 | git checkout -b universe; git clean -f 15 | echo 'main() {puts("Hello Universe!");}' > hello.c 16 | git add hello.c 17 | echo "// FIXME: includes" >> hello.c 18 | git add hello.c 19 | git commit -q -m "notes to self" 20 | git checkout master 21 | cat hello.c 22 | echo 'main() {puts("Hello Universe!"); exit(0);}' > hello.c 23 | git add hello.c 24 | git commit -q -m "fixed exit code" 25 | git merge universe 26 | git cat-file -p universe:hello.c > hello.c 27 | git add hello.c 28 | git commit -q -m "merged" 29 | cat hello.c 30 | git checkout HEAD^ 31 | git checkout HEAD^ 32 | cat hello.c 33 | git checkout master 34 | sleep 2; echo "this emulates unsaved vim session" > .hello.c.swp 35 | git clean -f 36 | : ------------------------------------------------------------------------ 37 | cd .. 38 | -------------------------------------------------------------------------------- /git-prompt.conf: -------------------------------------------------------------------------------- 1 | 2 | ### GIT-PROMPT.SH CONFIG 3 | ### 4 | ### lines commented-out with single '#' are default values 5 | ### lines commented-out with double '##' are examples 6 | ### 7 | ### NOTE: this is bash syntax - no spaces around "=" 8 | 9 | ########################################################### 10 | 11 | # error_bell=off # sound terminal bell when command return code is not zero. (use setterm to set pitch and duration) 12 | # max_file_list_length=100 # in characters 13 | # count_only=off # off - display file list; on - display file count 14 | # rawhex_len=5 # length of git rawhex revision id display (use 0 to hide it) 15 | 16 | ############################################################ MODULES 17 | 18 | # git_module=on 19 | # svn_module=off 20 | # hg_module=on 21 | # vim_module=on 22 | # virtualenv_module=on 23 | 24 | 25 | ########################################################### DEFAULT OBJECTS 26 | ### Default objects are not displayed. Example: 27 | 28 | ## default_user=lvv 29 | ## default_host="ahp" # remote host is always shown 30 | ## default_domain="lvvnet" 31 | 32 | ########################################################### Current Working Dir display 33 | # cwd_cmd='\w' # display full path 34 | ## cwd_cmd='\W' # display only last dir of path 35 | ## cwd_cmd='cwd_truncate 40' # display only last N chars of path 36 | 37 | ########################################################### ETC 38 | 39 | # Some don't like hostname in uppercase 40 | # upcase_hostname=on # =off 41 | # Some don't like long hostname 42 | # short_hostname=off # =on 43 | 44 | # Do not do VCS parsing for listed directories 45 | # useful for directories for which it is difficult to maintain .gitignore so 46 | # they are always dirty (ex: home, /etc) or directory with huge repo (ex: linux src) 47 | ## vcs_ignore_dir_list=" /etc $HOME /usr/src/linux.git " 48 | 49 | ########################################################### COLOR 50 | 51 | ### directory, exit code, root color 52 | 53 | # cols=`tput colors` 54 | # if [[ -n "$cols" && $cols -ge 8 ]]; then # if terminal supports colors 55 | # dir_color=CYAN 56 | # rc_color=red 57 | # virtualenv_color=green 58 | # user_id_color=blue 59 | # root_id_color=magenta 60 | # else # B/W terminal 61 | # dir_color=bw_bold 62 | # rc_color=bw_bold 63 | # fi 64 | 65 | ### prompt character for root/non-root, default '>' for both 66 | # prompt_char='>' 67 | # root_prompt_char='>' 68 | ## prompt_char='$' 69 | ## prompt_char='➔' 70 | ## root_prompt_char='#' 71 | 72 | ##### Per host color 73 | 74 | ### Per host color. If not set, color will be derived from hostname checksum). 75 | ### Variable name is uppercase-short-hostname with appended "_host_color" 76 | ### Example per-host-color config: 77 | 78 | ## TASHA_host_color=cyan 79 | ## AL_host_color=green 80 | ## AHP_host_color=white 81 | 82 | 83 | ##### VCS (version control system) state colors 84 | 85 | # init_vcs_color=WHITE # initial 86 | # clean_vcs_color=blue # nothing to commit (working directory clean) 87 | # modified_vcs_color=red # Changed but not updated: 88 | # added_vcs_color=green # Changes to be committed: 89 | # mixed_vcs_color=yellow # 90 | # untracked_vcs_color=BLUE # Untracked files: 91 | # op_vcs_color=MAGENTA 92 | # detached_vcs_color=RED 93 | # hex_vcs_color=BLACK # git revision id: bright black (makes gray) 94 | 95 | 96 | # :vim:ft=sh ts=8 sw=8 et: 97 | -------------------------------------------------------------------------------- /index.txt: -------------------------------------------------------------------------------- 1 | // This is raw asciidoc file. The HTML rendered page is at http://volnitsky.com/project/git-prompt 2 | 3 | = GIT Prompt 4 | 5 | * Repo: httpx://github.com/lvv/scc[GitHub], httpx://bitbucket.org/lvv/scc[BitBucket] + 6 | * License: httpx://www.gnu.org/licenses/gpl-3.0.html[GPL3] 7 | 8 | :v-p: http://volnitsky.com/project 9 | :compact-option: compact 10 | 11 | 12 | == Basic Usage 13 | 14 | image:screenshot-prompt-basic.png[basic usage] 15 | 16 | Digit [red]*1* on 3rd line is `false(1)` exit code. Also on non-zero exit code 17 | terminal bell is sounded. Bell is turned off by default (to set softer 18 | terminal bell use `setterm`). 19 | 20 | 21 | == GIT 22 | 23 | Branch and files are colored according to state. "M" stands for master. 24 | 25 | image:screenshot-prompt-git.png[git module screenshot] 26 | 27 | .Branch and Files Colors 28 | [cols="^3,^3,12",frame="topbot",options="header"] 29 | |================================================================ 30 | | *Branch* | *File* | *Meaning* 31 | | [darkblue]#dark blue# | | Clean repo 32 | | [green]#green# | [green]#green# | Modified or new file. Modifications are in index but not in repo yet. 33 | | [darkred]#dark red# | [darkred]#dark red# | Modified and tracked by repo, but modifications not added to index yet. 34 | | [lightblue]#light blue# | [lightblue]#light blue# | Untracked file. 35 | | [red]#light red# | | Detached Head 36 | | [magenta]#magenta# | | In middle of doing something 37 | |================================================================ 38 | 39 | 40 | == Subversion/SVN 41 | image:screenshot-svn.png[svn module screenshot] 42 | 43 | SVN module disabled by default because even on moderate sized working 44 | directories there is noticeable delay for prompt display. SVN is slower than 45 | GIT. Enable if needed in <> 46 | 47 | == Mercurial/HG 48 | HG module was developed by Lee Nussbaum ``. 49 | 50 | 51 | == Labels 52 | 53 | Labels are visual cues to help figure out what terminal is running what command. 54 | It is generalization of xterm-title but differ from xterm-title that it 55 | can be displayed in other places (on Screen(1) windows titles for example). 56 | Also label can display currently executed command (when bash prompt obviously 57 | is not displayed). Because labels have less space then prompt, instead of full path 58 | only last dir is shown. 59 | On screenshot below labels are in red ovals. 60 | 61 | image:screenshot-labels.png["labels screenshot", width="300", link="screenshot-labels.png"] 62 | 63 | The `screen(1)` status line at bottom of smaller gnome-terminal is displayed with 64 | following line in `~/.screenrc`: 65 | 66 | --------- 67 | caption always "%{= kw}%-w%{= bw}%n %t%{-}%+w %-= @%H - %LD %d %LM - %c" 68 | --------- 69 | 70 | 71 | == Simple AutoJump 72 | 73 | AutoJump is python script from Joel Schaerer providing shortcuts for jumping 74 | to directories you once visited. Git-prompt have built-in, simplified 75 | autojump. It is only about 10 lines of bash code (vs original 100+ python 76 | LOC), there is no database. It remembers only directories from current 77 | session. It selects not most frequent dir, but last visited. Matches are done 78 | from beginning of of dir name (not path name). 79 | 80 | ----------------- 81 | cd /tmp 82 | cd "~/long dir mp3" 83 | cd "~/long dir mp4" 84 | cd /tmp 85 | cd /var/tmp 86 | cd /etc 87 | cd 88 | j t # same as cd /var/tmp 89 | j .*3 # same as cd "~/long dir mp3" 90 | ------------- 91 | 92 | 93 | == Install 94 | Download link:git-prompt.sh[] or get it with GIT: 95 | 96 | ------------------ 97 | git clone git://github.com/lvv/git-prompt.git 98 | --------------- 99 | 100 | Put following command at the end of your profile (`~/.bash_profile` or `~/.profile`) 101 | 102 | -------------------- 103 | [[ $- == *i* ]] && . /path/to/git-prompt.sh 104 | --------------------- 105 | 106 | There might be your old prompt defined too. You can comment it out. 107 | Some distros also have `/etc/bashrc` or `/etc/bash/bashrc` with distro default 108 | prompt. 109 | 110 | 111 | 112 | == GIT config 113 | 114 | GIT-PROMPT requires following GIT's option to be set: 115 | 116 | ------------------------- 117 | git config [--global] core.quotepath off 118 | git config [--global] --unset svn.pathnameencoding 119 | git config [--global] --unset i18n.logoutputencoding 120 | ----------------------------- 121 | 122 | 123 | == GIT-PROMPT config 124 | [[config]] 125 | 126 | Is optional. If config file is not found then git-prompt uses defaults. 127 | Defaults are listed in example `git-prompt.conf`. Git-prompt looks (in listed order) 128 | for config file in following locations: 129 | 130 | * `/etc/git-prompt.conf` 131 | * `~/.git-prompt.conf` 132 | 133 | Copy example config `git-prompt.conf` 134 | to any of above locations and customize as needed. 135 | 136 | 137 | == Limitations 138 | 139 | * cd-ing into something like linux kernel git working directory for the 1st 140 | time (with cold cache) might take up to 10 seconds (that is how long `git status` executes). 141 | Use `vcs_ignore_dir_list` in config if you want to ingnore such dirs. 142 | * Because you will be always reminded about dirty repo (not checked-in files), 143 | you will maintain `.gitignore` and commit more often. 144 | * This prompt is most useful if your screen have enough width. 145 | If this is not the case, you might want to disable file list display (`max_file_list_length=0`). 146 | * When prompt is longer than screen-width it wraps to second line. This is always undesirable. 147 | Because of bug in `gnome-terminal` (or `readline` ?) some color escape codes can be visible on second line. 148 | I've reported gnome-terminal bug. Again, you can disable file list display or limit length (`max_file_list_length`). 149 | * By default some terminals display ascii color with maximum color saturation 150 | which makes colored text of different perceptual brightness. This makes it hard to read. 151 | If your terminal colors are configurable, try change it to softer (pastel) 152 | colors. 153 | 154 | 155 | == Dependencies 156 | 157 | Most probably you don't need to install anything because not optional 158 | dependencies are standard unix utils. 159 | 160 | * bash (tested with v3.2.33) 161 | * sed 162 | * tput (terminfo) 163 | * tty (core utils) 164 | * grep 165 | * locale (glibc) 166 | * id (core utils) 167 | * cksum (core utils) 168 | 169 | * git (optional) 170 | * svn (optional) 171 | * hg (optional) 172 | 173 | 174 | == Todo 175 | 176 | * httpx://jonisalonen.com/2012/your-bash-prompt-needs-this/[] 177 | * httpx://tldp.org/HOWTO/Bash-Prompt-HOWTO/x810.html[Beep after long running command] 178 | * httpx://briancarper.net/blog/248/[Sync bash history] 179 | * VIM module needs to be moved out of GIT module 180 | 181 | include::../volnitsky.com/project/howto-submit-patch.txt[] 182 | 183 | Nobody will use git-prompt if there will be delay in prompt display. 184 | Try to avoid use of external commands and 185 | subshells (backticks) in prompt_command_function. It is ok to use 186 | time consuming ops in postconfig which is executed only once. 187 | 188 | == Authors 189 | - Leonid Volnitsky (original author) , http://volnitsky.com 190 | - Niklas Hofer (CWD truncation) , httpx://github.com/niklas/[] 191 | - Lee Nussbaum (HG support) , httpx://github.com/wln[] 192 | - Albert Vernon http://xenoclub.wordpress.com[] 193 | - Amir Yalon httpx://github.com/amiryal[] 194 | - Martin httpx://github.com/jerrywho[] 195 | - Alexander Goldstein (emacs-shell, prompt chars) httpx://github.com/alexg0[] 196 | - Dmitry (bash completion) 197 | - Sergey Shepelev 198 | - Robert Wahler 199 | - Gustavo Delfino 200 | - Dan Bravender 201 | - Thomas Geffert 202 | - Tibor Simko 203 | 204 | 205 | [bibliography] 206 | .References 207 | - [[[1]]] 'Bash Prompt HOWTO', http://tldp.org/HOWTO/Bash-Prompt-HOWTO/index.html 208 | - [[[2]]] 'BASH Frequently Asked Questions', http://mywiki.wooledge.org/BashFAQ 209 | 210 | -------------------------------------------------------------------------------- /git-prompt.sh: -------------------------------------------------------------------------------- 1 | # don't set prompt if this is not interactive shell 2 | [[ $- != *i* ]] && return 3 | 4 | ################################################################### CONFIG 5 | 6 | ##### read config file if any. 7 | 8 | unset dir_color rc_color user_id_color root_id_color init_vcs_color clean_vcs_color 9 | unset modified_vcs_color added_vcs_color addmoded_vcs_color untracked_vcs_color op_vcs_color detached_vcs_color hex_vcs_color 10 | unset rawhex_len 11 | 12 | conf=git-prompt.conf; [[ -r $conf ]] && . $conf 13 | conf=/etc/git-prompt.conf; [[ -r $conf ]] && . $conf 14 | conf=~/.git-prompt.conf; [[ -r $conf ]] && . $conf 15 | conf=~/.config/git-prompt.conf; [[ -r $conf ]] && . $conf 16 | unset conf 17 | 18 | 19 | ##### set defaults if not set 20 | 21 | git_module=${git_module:-on} 22 | svn_module=${svn_module:-off} 23 | hg_module=${hg_module:-on} 24 | vim_module=${vim_module:-on} 25 | virtualenv_module=${virtualenv_module:-on} 26 | error_bell=${error_bell:-off} 27 | cwd_cmd=${cwd_cmd:-\\w} 28 | 29 | 30 | #### dir, rc, root color 31 | cols=`tput colors` # in emacs shell-mode tput colors returns -1 32 | if [[ -n "$cols" && $cols -ge 8 ]]; then # if terminal supports colors 33 | dir_color=${dir_color:-CYAN} 34 | rc_color=${rc_color:-red} 35 | virtualenv_color=${virtualenv_color:-green} 36 | user_id_color=${user_id_color:-blue} 37 | root_id_color=${root_id_color:-magenta} 38 | else # only B/W 39 | dir_color=${dir_color:-bw_bold} 40 | rc_color=${rc_color:-bw_bold} 41 | fi 42 | unset cols 43 | 44 | #### prompt character, for root/non-root 45 | prompt_char=${prompt_char:-'>'} 46 | root_prompt_char=${root_prompt_char:-'>'} 47 | 48 | #### vcs colors 49 | init_vcs_color=${init_vcs_color:-WHITE} # initial 50 | clean_vcs_color=${clean_vcs_color:-blue} # nothing to commit (working directory clean) 51 | modified_vcs_color=${modified_vcs_color:-red} # Changed but not updated: 52 | added_vcs_color=${added_vcs_color:-green} # Changes to be committed: 53 | addmoded_vcs_color=${addmoded_vcs_color:-yellow} 54 | untracked_vcs_color=${untracked_vcs_color:-BLUE} # Untracked files: 55 | op_vcs_color=${op_vcs_color:-MAGENTA} 56 | detached_vcs_color=${detached_vcs_color:-RED} 57 | 58 | hex_vcs_color=${hex_vcs_color:-BLACK} # gray 59 | 60 | 61 | max_file_list_length=${max_file_list_length:-100} 62 | short_hostname=${short_hostname:-off} 63 | upcase_hostname=${upcase_hostname:-on} 64 | count_only=${count_only:-off} 65 | rawhex_len=${rawhex_len:-5} 66 | 67 | aj_max=20 68 | 69 | 70 | ##################################################################### post config 71 | 72 | ################# make PARSE_VCS_STATUS 73 | unset PARSE_VCS_STATUS 74 | [[ $git_module = "on" ]] && type git >&/dev/null && PARSE_VCS_STATUS+="parse_git_status" 75 | [[ $svn_module = "on" ]] && type svn >&/dev/null && PARSE_VCS_STATUS+="${PARSE_VCS_STATUS+||}parse_svn_status" 76 | [[ $hg_module = "on" ]] && type hg >&/dev/null && PARSE_VCS_STATUS+="${PARSE_VCS_STATUS+||}parse_hg_status" 77 | PARSE_VCS_STATUS+="${PARSE_VCS_STATUS+||}return" 78 | ################# terminfo colors-16 79 | # 80 | # black? 0 8 81 | # red 1 9 82 | # green 2 10 83 | # yellow 3 11 84 | # blue 4 12 85 | # magenta 5 13 86 | # cyan 6 14 87 | # white 7 15 88 | # 89 | # terminfo setaf/setab - sets ansi foreground/background 90 | # terminfo sgr0 - resets all attributes 91 | # terminfo colors - number of colors 92 | # 93 | ################# Colors-256 94 | # To use foreground and background colors: 95 | # Set the foreground color to index N: \033[38;5;${N}m 96 | # Set the background color to index M: \033[48;5;${M}m 97 | # To make vim aware of a present 256 color extension, you can either set 98 | # the $TERM environment variable to xterm-256color or use vim's -T option 99 | # to set the terminal. I'm using an alias in my bashrc to do this. At the 100 | # moment I only know of two color schemes which is made for multi-color 101 | # terminals like urxvt (88 colors) or xterm: inkpot and desert256, 102 | 103 | ### if term support colors, then use color prompt, else bold 104 | 105 | black='\['`tput sgr0; tput setaf 0`'\]' 106 | red='\['`tput sgr0; tput setaf 1`'\]' 107 | green='\['`tput sgr0; tput setaf 2`'\]' 108 | yellow='\['`tput sgr0; tput setaf 3`'\]' 109 | blue='\['`tput sgr0; tput setaf 4`'\]' 110 | magenta='\['`tput sgr0; tput setaf 5`'\]' 111 | cyan='\['`tput sgr0; tput setaf 6`'\]' 112 | white='\['`tput sgr0; tput setaf 7`'\]' 113 | 114 | BLACK='\['`tput setaf 0; tput bold`'\]' 115 | RED='\['`tput setaf 1; tput bold`'\]' 116 | GREEN='\['`tput setaf 2; tput bold`'\]' 117 | YELLOW='\['`tput setaf 3; tput bold`'\]' 118 | BLUE='\['`tput setaf 4; tput bold`'\]' 119 | MAGENTA='\['`tput setaf 5; tput bold`'\]' 120 | CYAN='\['`tput setaf 6; tput bold`'\]' 121 | WHITE='\['`tput setaf 7; tput bold`'\]' 122 | 123 | dim='\['`tput sgr0; tput setaf p1`'\]' # half-bright 124 | 125 | bw_bold='\['`tput bold`'\]' 126 | 127 | on='' 128 | off=': ' 129 | bell="\[`eval ${!error_bell} tput bel`\]" 130 | colors_reset='\['`tput sgr0`'\]' 131 | 132 | # replace symbolic colors names to raw treminfo strings 133 | init_vcs_color=${!init_vcs_color} 134 | modified_vcs_color=${!modified_vcs_color} 135 | untracked_vcs_color=${!untracked_vcs_color} 136 | clean_vcs_color=${!clean_vcs_color} 137 | added_vcs_color=${!added_vcs_color} 138 | op_vcs_color=${!op_vcs_color} 139 | addmoded_vcs_color=${!addmoded_vcs_color} 140 | detached_vcs_color=${!detached_vcs_color} 141 | hex_vcs_color=${!hex_vcs_color} 142 | 143 | unset PROMPT_COMMAND 144 | 145 | ####### work around for MC bug. 146 | ####### specifically exclude emacs, want full when running inside emacs 147 | if [[ -z "$TERM" || ("$TERM" = "dumb" && -z "$INSIDE_EMACS") || -n "$MC_SID" ]]; then 148 | unset PROMPT_COMMAND 149 | PS1="\w$prompt_char " 150 | return 0 151 | fi 152 | 153 | #################################################################### MARKERS 154 | if [[ "$LC_CTYPE $LC_ALL" =~ "UTF" && $TERM != "linux" ]]; then 155 | elipses_marker="…" 156 | else 157 | elipses_marker="..." 158 | fi 159 | 160 | export who_where 161 | 162 | 163 | cwd_truncate() { 164 | # based on: https://www.blog.montgomerie.net/pwd-in-the-title-bar-or-a-regex-adventure-in-bash 165 | 166 | # arg1: max path lenght 167 | # returns abbrivated $PWD in public "cwd" var 168 | 169 | cwd=${PWD/$HOME/\~} # substitute "~" 170 | 171 | case $1 in 172 | full) 173 | return 174 | ;; 175 | last) 176 | cwd=${PWD##/*/} 177 | [[ $PWD == $HOME ]] && cwd="~" 178 | return 179 | ;; 180 | *) 181 | # if bash < v3.2 then don't truncate 182 | if [[ ${BASH_VERSINFO[0]} -eq 3 && ${BASH_VERSINFO[1]} -le 1 || ${BASH_VERSINFO[0]} -lt 3 ]] ; then 183 | return 184 | fi 185 | ;; 186 | esac 187 | 188 | # split path into: head='~/', truncateble middle, last_dir 189 | 190 | local cwd_max_length=$1 191 | # expression which bash-3.1 or older can not understand, so we wrap it in eval 192 | exp31='[[ "$cwd" =~ (~?/)(.*/)([^/]*)$ ]]' 193 | if eval $exp31 ; then # only valid if path have more then 1 dir 194 | local path_head=${BASH_REMATCH[1]} 195 | local path_middle=${BASH_REMATCH[2]} 196 | local path_last_dir=${BASH_REMATCH[3]} 197 | 198 | local cwd_middle_max=$(( $cwd_max_length - ${#path_last_dir} )) 199 | [[ $cwd_middle_max < 0 ]] && cwd_middle_max=0 200 | 201 | 202 | # trunc middle if over limit 203 | if [[ ${#path_middle} -gt $(( $cwd_middle_max + ${#elipses_marker} + 5 )) ]]; then 204 | 205 | # truncate 206 | middle_tail=${path_middle:${#path_middle}-${cwd_middle_max}} 207 | 208 | # trunc on dir boundary (trunc 1st, probably tuncated dir) 209 | exp31='[[ $middle_tail =~ [^/]*/(.*)$ ]]' 210 | eval $exp31 211 | middle_tail=${BASH_REMATCH[1]} 212 | 213 | # use truncated only if we cut at least 4 chars 214 | if [[ $(( ${#path_middle} - ${#middle_tail})) -gt 4 ]]; then 215 | cwd=$path_head$elipses_marker$middle_tail$path_last_dir 216 | fi 217 | fi 218 | fi 219 | return 220 | } 221 | 222 | 223 | set_shell_label() { 224 | 225 | xterm_label() { 226 | local args="$*" 227 | echo -n "]2;${args:0:200}" ; # FIXME: replace hardcodes with terminfo codes 228 | } 229 | 230 | screen_label() { 231 | # FIXME: run this only if screen is in xterm (how to test for this?) 232 | xterm_label "$plain_who_where $@" 233 | 234 | # FIXME $STY not inherited though "su -" 235 | [ "$STY" ] && screen -S $STY -X title "$*" 236 | } 237 | if [[ -n "$STY" ]]; then 238 | screen_label "$*" 239 | else 240 | case $TERM in 241 | 242 | screen*) 243 | screen_label "$*" 244 | ;; 245 | 246 | xterm* | rxvt* | gnome-* | konsole | eterm | wterm ) 247 | # is there a capability which we can to test 248 | # for "set term title-bar" and its escapes? 249 | xterm_label "$plain_who_where $@" 250 | ;; 251 | 252 | *) 253 | ;; 254 | esac 255 | fi 256 | } 257 | 258 | export -f set_shell_label 259 | 260 | ###################################################### ID (user name) 261 | id=`id -un` 262 | id=${id#$default_user} 263 | 264 | ########################################################### TTY 265 | tty=`tty` 266 | tty=`echo $tty | sed "s:/dev/pts/:p:; s:/dev/tty::" ` # RH tty devs 267 | tty=`echo $tty | sed "s:/dev/vc/:vc:" ` # gentoo tty devs 268 | 269 | if [[ "$TERM" = "screen" ]] ; then 270 | 271 | # [ "$WINDOW" = "" ] && WINDOW="?" 272 | # 273 | # # if under screen then make tty name look like s1-p2 274 | # # tty="${WINDOW:+s}$WINDOW${WINDOW:+-}$tty" 275 | # tty="${WINDOW:+s}$WINDOW" # replace tty name with screen number 276 | tty="$WINDOW" # replace tty name with screen number 277 | fi 278 | 279 | # we don't need tty name under X11 280 | case $TERM in 281 | xterm* | rxvt* | gnome-terminal | konsole | eterm* | wterm | cygwin) unset tty ;; 282 | *);; 283 | esac 284 | 285 | dir_color=${!dir_color} 286 | rc_color=${!rc_color} 287 | virtualenv_color=${!virtualenv_color} 288 | user_id_color=${!user_id_color} 289 | root_id_color=${!root_id_color} 290 | 291 | ########################################################### HOST 292 | ### we don't display home host/domain $SSH_* set by SSHD or keychain 293 | 294 | # How to find out if session is local or remote? Working with "su -", ssh-agent, and so on ? 295 | 296 | ## is sshd our parent? 297 | # if { for ((pid=$$; $pid != 1 ; pid=`ps h -o pid --ppid $pid`)); do ps h -o command -p $pid; done | grep -q sshd && echo == REMOTE ==; } 298 | #then 299 | 300 | host=${HOSTNAME} 301 | if [[ $short_hostname = "on" ]]; then 302 | if [[ "$(uname)" =~ "CYGWIN" ]]; then 303 | host=`hostname` 304 | else 305 | host=`hostname -s` 306 | fi 307 | fi 308 | host=${host#$default_host} 309 | uphost=`echo ${host} | tr a-z-. A-Z_` 310 | if [[ $upcase_hostname = "on" ]]; then 311 | host=${uphost} 312 | fi 313 | 314 | host_color=${uphost}_host_color 315 | host_color=${!host_color} 316 | if [[ -z $host_color && -x /usr/bin/cksum ]] ; then 317 | cksum_color_no=`echo $uphost | cksum | awk '{print $1%6}'` 318 | color_index=(green yellow blue magenta cyan white) # FIXME: bw, color-256 319 | host_color=${color_index[cksum_color_no]} 320 | fi 321 | 322 | host_color=${!host_color} 323 | 324 | # we might already have short host name 325 | host=${host%.$default_domain} 326 | 327 | #################################################################### WHO_WHERE 328 | # [[user@]host[-tty]] 329 | 330 | if [[ -n $id || -n $host ]] ; then 331 | [[ -n $id && -n $host ]] && at='@' || at='' 332 | color_who_where="${id}${host:+$host_color$at$host}${tty:+ $tty}" 333 | plain_who_where="${id}$at$host" 334 | 335 | # add trailing " " 336 | color_who_where="$color_who_where " 337 | plain_who_where="$plain_who_where " 338 | 339 | # if root then make it root_color 340 | if [ "$id" == "root" ] ; then 341 | user_id_color=$root_id_color 342 | prompt_char="$root_prompt_char" 343 | fi 344 | color_who_where="$user_id_color$color_who_where$colors_reset" 345 | else 346 | color_who_where='' 347 | fi 348 | 349 | 350 | parse_svn_status() { 351 | 352 | [[ -d .svn ]] || return 1 353 | 354 | vcs=svn 355 | 356 | ### get rev 357 | eval ` 358 | svn info | 359 | sed -n " 360 | s@^URL[^/]*//@repo_dir=@p 361 | s/^Revision: /rev=/p 362 | " 363 | ` 364 | ### get status 365 | 366 | unset status modified added clean init added mixed untracked op detached 367 | eval `svn status 2>/dev/null | 368 | sed -n ' 369 | s/^A... \([^.].*\)/modified=modified; modified_files[${#modified_files[@]}]=\"\1\";/p 370 | s/^M... \([^.].*\)/modified=modified; modified_files[${#modified_files[@]}]=\"\1\";/p 371 | s/^\?... \([^.].*\)/untracked=untracked; untracked_files[${#untracked_files[@]}]=\"\1\";/p 372 | ' 373 | ` 374 | # TODO branch detection if standard repo layout 375 | 376 | [[ -z $modified ]] && [[ -z $untracked ]] && clean=clean 377 | vcs_info=svn:r$rev 378 | } 379 | 380 | parse_hg_status() { 381 | 382 | # ☿ 383 | hg_root=`hg root 2>/dev/null` || return 1 384 | 385 | vcs=hg 386 | 387 | ### get status 388 | unset status modified added clean init added mixed untracked op detached 389 | 390 | eval `hg status 2>/dev/null | 391 | sed -n ' 392 | s/^M \([^.].*\)/modified=modified; modified_files[${#modified_files[@]}]=\"\1\";/p 393 | s/^A \([^.].*\)/added=added; added_files[${#added_files[@]}]=\"\1\";/p 394 | s/^R \([^.].*\)/added=added;/p 395 | s/^! \([^.].*\)/modified=modified;/p 396 | s/^? \([^.].*\)/untracked=untracked; untracked_files[${#untracked_files[@]}]=\\"\1\\";/p 397 | '` 398 | 399 | branch=`hg branch 2> /dev/null` 400 | 401 | [[ -f $hg_root/.hg/bookmarks.current ]] && bookmark=`cat "$hg_root/.hg/bookmarks.current"` 402 | 403 | [[ -z $modified ]] && [[ -z $untracked ]] && [[ -z $added ]] && clean=clean 404 | vcs_info=${branch/default/D} 405 | if [[ "$bookmark" ]] ; then 406 | vcs_info+=/$bookmark 407 | fi 408 | } 409 | 410 | 411 | 412 | parse_git_status() { 413 | 414 | # TODO add status: LOCKED (.git/index.lock) 415 | 416 | git_dir=`[[ $git_module = "on" ]] && git rev-parse --git-dir 2> /dev/null` 417 | #git_dir=`eval \$$git_module git rev-parse --git-dir 2> /dev/null` 418 | #git_dir=` git rev-parse --git-dir 2> /dev/null` 419 | 420 | [[ -n ${git_dir/./} ]] || return 1 421 | 422 | vcs=git 423 | 424 | ########################################################## GIT STATUS 425 | added_files=() 426 | modified_files=() 427 | untracked_files=() 428 | [[ $rawhex_len -gt 0 ]] && freshness="$dim=" 429 | 430 | unset branch status modified added clean init added mixed untracked op detached 431 | 432 | # info not in porcelain status 433 | eval " $( 434 | git status 2>/dev/null | 435 | sed -n ' 436 | s/^# On branch /branch=/p 437 | s/^nothing to commi.*/clean=clean/p 438 | s/^# Initial commi.*/init=init/p 439 | s/^# Your branch is ahead of \(.\).\+\1 by [[:digit:]]\+ commit.*/freshness=${WHITE}↑/p 440 | s/^# Your branch is behind \(.\).\+\1 by [[:digit:]]\+ commit.*/freshness=${YELLOW}↓/p 441 | s/^# Your branch and \(.\).\+\1 have diverged.*/freshness=${YELLOW}↕/p 442 | ' 443 | )" 444 | 445 | # porcelain file list 446 | # TODO: sed-less -- http://tldp.org/LDP/abs/html/arrays.html -- Example 27-5 447 | 448 | # git bug: (was reported to git@vger.kernel.org ) 449 | # echo 1 > "with space" 450 | # git status --porcelain 451 | # ?? with space <------------ NO QOUTES 452 | # git add with\ space 453 | # git status --porcelain 454 | # A "with space" <------------- WITH QOUTES 455 | 456 | eval " $( 457 | git status --porcelain 2>/dev/null | 458 | sed -n ' 459 | s,^[MARC]. \([^\"][^/]*/\?\).*, added=added; [[ \" ${added_files[@]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\",p 460 | s,^[MARC]. \"\([^/]\+/\?\).*\"$, added=added; [[ \" ${added_files[@]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\",p 461 | s,^.[MAU] \([^\"][^/]*/\?\).*, modified=modified; [[ \" ${modified_files[@]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\",p 462 | s,^.[MAU] \"\([^/]\+/\?\).*\"$, modified=modified; [[ \" ${modified_files[@]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\",p 463 | s,^?? \([^\"][^/]*/\?\).*, untracked=untracked; [[ \" ${untracked_files[@]} \" =~ \" \1 \" ]] || untracked_files[${#untracked_files[@]}]=\"\1\",p 464 | s,^?? \"\([^/]\+/\?\).*\"$, untracked=untracked; [[ \" ${untracked_files[@]} \" =~ \" \1 \" ]] || untracked_files[${#untracked_files[@]}]=\"\1\",p 465 | ' # |tee /dev/tty 466 | )" 467 | 468 | if ! grep -q "^ref:" "$git_dir/HEAD" 2>/dev/null; then 469 | detached=detached 470 | fi 471 | 472 | 473 | ################# GET GIT OP 474 | 475 | unset op 476 | 477 | if [[ -d "$git_dir/.dotest" ]] ; then 478 | 479 | if [[ -f "$git_dir/.dotest/rebasing" ]] ; then 480 | op="rebase" 481 | 482 | elif [[ -f "$git_dir/.dotest/applying" ]] ; then 483 | op="am" 484 | 485 | else 486 | op="am/rebase" 487 | 488 | fi 489 | 490 | elif [[ -f "$git_dir/.dotest-merge/interactive" ]] ; then 491 | op="rebase -i" 492 | # ??? branch="$(cat "$git_dir/.dotest-merge/head-name")" 493 | 494 | elif [[ -d "$git_dir/.dotest-merge" ]] ; then 495 | op="rebase -m" 496 | # ??? branch="$(cat "$git_dir/.dotest-merge/head-name")" 497 | 498 | # lvv: not always works. Should ./.dotest be used instead? 499 | elif [[ -f "$git_dir/MERGE_HEAD" ]] ; then 500 | op="merge" 501 | # ??? branch="$(git symbolic-ref HEAD 2>/dev/null)" 502 | 503 | elif [[ -f "$git_dir/index.lock" ]] ; then 504 | op="locked" 505 | 506 | else 507 | [[ -f "$git_dir/BISECT_LOG" ]] && op="bisect" 508 | # ??? branch="$(git symbolic-ref HEAD 2>/dev/null)" || \ 509 | # branch="$(git describe --exact-match HEAD 2>/dev/null)" || \ 510 | # branch="$(cut -c1-7 "$git_dir/HEAD")..." 511 | fi 512 | 513 | 514 | #### GET GIT HEX-REVISION 515 | if [[ $rawhex_len -gt 0 ]] ; then 516 | rawhex=`git rev-parse HEAD 2>/dev/null` 517 | rawhex=${rawhex/HEAD/} 518 | rawhex="$hex_vcs_color${rawhex:0:$rawhex_len}" 519 | else 520 | rawhex="" 521 | fi 522 | 523 | #### branch 524 | branch=${branch/#master/M} 525 | 526 | # another method of above: 527 | # branch=$(git symbolic-ref -q HEAD || { echo -n "detached:" ; git name-rev --name-only HEAD 2>/dev/null; } ) 528 | # branch=${branch#refs/heads/} 529 | 530 | ### compose vcs_info 531 | 532 | if [[ $init ]]; then 533 | vcs_info=${white}init 534 | 535 | else 536 | if [[ "$detached" ]] ; then 537 | branch="/dev/null`" 538 | 539 | 540 | elif [[ "$op" ]]; then 541 | branch="$op:$branch" 542 | if [[ "$op" == "merge" ]] ; then 543 | branch+="<--$(git name-rev --name-only $(<$git_dir/MERGE_HEAD))" 544 | fi 545 | #branch="<$branch>" 546 | fi 547 | vcs_info="$branch$freshness$rawhex" 548 | 549 | fi 550 | } 551 | 552 | 553 | parse_vcs_status() { 554 | 555 | unset file_list modified_files untracked_files added_files 556 | unset vcs vcs_info 557 | unset status modified untracked added init detached 558 | unset file_list modified_files untracked_files added_files 559 | 560 | [[ $vcs_ignore_dir_list =~ $PWD ]] && return 561 | 562 | eval $PARSE_VCS_STATUS 563 | 564 | 565 | ### status: choose primary (for branch color) 566 | unset status 567 | status=${op:+op} 568 | status=${status:-$detached} 569 | status=${status:-$clean} 570 | status=${status:-$modified} 571 | status=${status:-$added} 572 | status=${status:-$untracked} 573 | status=${status:-$init} 574 | # at least one should be set 575 | : ${status?prompt internal error: git status} 576 | eval vcs_color="\${${status}_vcs_color}" 577 | # no def: vcs_color=${vcs_color:-$WHITE} # default 578 | 579 | 580 | ### VIM 581 | 582 | if [[ $vim_module = "on" ]] ; then 583 | # equivalent to vim_glob=`ls .*.vim` but without running ls 584 | unset vim_glob vim_file vim_files 585 | old_nullglob=`shopt -p nullglob` 586 | shopt -s nullglob 587 | vim_glob=`echo .*.sw?` 588 | eval $old_nullglob 589 | 590 | if [[ $vim_glob ]]; then 591 | set $vim_glob 592 | #vim_file=${vim_glob#.} 593 | if [[ $# > 1 ]] ; then 594 | vim_files="*" 595 | else 596 | vim_file=${1#.} 597 | vim_file=${vim_file/.sw?/} 598 | [[ .${vim_file}.swp -nt $vim_file ]] && vim_files=$vim_file 599 | fi 600 | # if swap is newer, then this is unsaved vim session 601 | # [temoto custom] if swap is older, then it must be deleted, so show all swaps. 602 | fi 603 | fi 604 | 605 | 606 | ### file list 607 | unset file_list 608 | if [[ $count_only = "on" ]] ; then 609 | [[ ${added_files[0]} ]] && file_list+=" "${added_vcs_color}+${#added_files[@]} 610 | [[ ${modified_files[0]} ]] && file_list+=" "${modified_vcs_color}*${#modified_files[@]} 611 | [[ ${untracked_files[0]} ]] && file_list+=" "${untracked_vcs_color}?${#untracked_files[@]} 612 | else 613 | [[ ${added_files[0]} ]] && file_list+=" "$added_vcs_color${added_files[@]} 614 | [[ ${modified_files[0]} ]] && file_list+=" "$modified_vcs_color${modified_files[@]} 615 | [[ ${untracked_files[0]} ]] && file_list+=" "$untracked_vcs_color${untracked_files[@]} 616 | fi 617 | [[ ${vim_files} ]] && file_list+=" "${MAGENTA}vim:${vim_files} 618 | 619 | if [[ ${#file_list} -gt $max_file_list_length ]] ; then 620 | file_list=${file_list:0:$max_file_list_length} 621 | if [[ $max_file_list_length -gt 0 ]] ; then 622 | file_list="${file_list% *} $elipses_marker" 623 | fi 624 | fi 625 | 626 | 627 | head_local="$vcs_color(${vcs_info}$vcs_color${file_list}$vcs_color)" 628 | 629 | ### fringes 630 | head_local="${head_local+$vcs_color$head_local }" 631 | #above_local="${head_local+$vcs_color$head_local\n}" 632 | #tail_local="${tail_local+$vcs_color $tail_local}${dir_color}" 633 | } 634 | 635 | parse_virtualenv_status() { 636 | unset virtualenv 637 | 638 | [[ $virtualenv_module = "on" ]] || return 1 639 | 640 | if [[ -n "$VIRTUAL_ENV" ]] ; then 641 | virtualenv=`basename $VIRTUAL_ENV` 642 | rc="$rc $virtualenv_color<$virtualenv> " 643 | fi 644 | } 645 | 646 | disable_set_shell_label() { 647 | trap - DEBUG >& /dev/null 648 | } 649 | 650 | # show currently executed command in label 651 | enable_set_shell_label() { 652 | disable_set_shell_label 653 | # check for BASH_SOURCE being empty, no point running set_shell_label on every line of .bashrc 654 | trap '[[ -z "$BASH_SOURCE" && ($BASH_COMMAND != prompt_command_function) ]] && 655 | set_shell_label $BASH_COMMAND' DEBUG >& /dev/null 656 | } 657 | 658 | declare -ft disable_set_shell_label 659 | declare -ft enable_set_shell_label 660 | 661 | # autojump (see http://wiki.github.com/joelthelion/autojump) 662 | 663 | # TODO reverse the line order of a file 664 | #awk ' { line[NR] = $0 } 665 | # END { for (i=NR;i>0;i--) 666 | # print line[i] }' listlogs 667 | 668 | j (){ 669 | : ${1? usage: j dir-beginning} 670 | # go in ring buffer starting from current index. cd to first matching dir 671 | for (( i=(aj_idx-1)%aj_max; i != aj_idx%aj_max; i=(--i+aj_max)%aj_max )) ; do 672 | if [[ ${aj_dir_list[$i]} =~ ^.*/$1[^/]*$ ]] ; then 673 | cd "${aj_dir_list[$i]}" 674 | return 675 | fi 676 | done 677 | echo '?' 678 | } 679 | 680 | alias jumpstart='echo ${aj_dir_list[@]}' 681 | 682 | ###################################################################### PROMPT_COMMAND 683 | 684 | prompt_command_function() { 685 | rc="$?" 686 | 687 | if [[ "$rc" == "0" ]]; then 688 | rc="" 689 | else 690 | rc="$rc_color$rc$colors_reset$bell " 691 | fi 692 | 693 | cwd=${PWD/$HOME/\~} # substitute "~" 694 | set_shell_label "${cwd##[/~]*/}/" # default label - path last dir 695 | 696 | parse_virtualenv_status 697 | parse_vcs_status 698 | 699 | # autojump 700 | if [[ ${aj_dir_list[aj_idx%aj_max]} != $PWD ]] ; then 701 | aj_dir_list[++aj_idx%aj_max]="$PWD" 702 | fi 703 | 704 | # if cwd_cmd have back-slash, then assign it value to cwd 705 | # else eval cwd_cmd, cwd should have path after exection 706 | eval "${cwd_cmd/\\/cwd=\\\\}" 707 | 708 | PS1="$colors_reset$rc$head_local$color_who_where$dir_color$cwd$tail_local$dir_color$prompt_char $colors_reset" 709 | 710 | unset head_local tail_local pwd 711 | } 712 | 713 | PROMPT_COMMAND=prompt_command_function 714 | 715 | enable_set_shell_label 716 | 717 | unset rc id tty modified_files file_list 718 | 719 | # vim: set ft=sh ts=8 sw=8 et: 720 | --------------------------------------------------------------------------------