]> gitweb.pimeys.fr Git - config-20-100.git/commitdiff
[.bashrc] Modular and expressive VCS functions
authorPierre-Elliott Bécue <becue@crans.org>
Mon, 7 Apr 2014 16:48:46 +0000 (18:48 +0200)
committerPierre-Elliott Bécue <becue@crans.org>
Mon, 7 Apr 2014 16:49:25 +0000 (18:49 +0200)
.bashrc

diff --git a/.bashrc b/.bashrc
index 80fcf713d2d746c80139c94301a494ef28d2b372..120e75fa3cb8e52a45d2695cd3e45a2b3c356f44 100644 (file)
--- a/.bashrc
+++ b/.bashrc
@@ -5,7 +5,7 @@
 # Licence : WTFPL
 
 # Les sections commentées par #~# sont des features qui ne sont pas activées
-# par défaut. Sentez-vous libre de les décommenter pour les utiliser. 
+# par défaut. Sentez-vous libre de les décommenter pour les utiliser.
 
 #------------------------------------------------------------------------------
 
@@ -36,7 +36,6 @@ fi
 # ils seront chargés par la ligne suivante
 [ -d ~/.bash_completion.d/ ] && for f in ~/.bash_completion.d/*; do source $f; done
 
-
 # +-----+
 # | VCS |
 # +-----+
@@ -44,57 +43,422 @@ fi
 # Définition de fonction pour pouvoir afficher dans le prompt
 # des infos quand on est dans un dépôt versionné
 
-find_up () {
-    local path normalized_path normalized_ret
-    path="$1"
-    shift 1
-    normalized_path=`readlink -f -- "$path"`
-    normalized_ret=$?
-    while [[ "$normalized_path" != "/" ]] && [ $normalized_ret -eq 0 ];
-    do
-        find "$path"  -maxdepth 1 -mindepth 1 "$@"
-        path=${path}/..
-        normalized_path=`readlink -f -- "$path"`
-        normalized_ret=$?
-    done
+# Checks if the command provided is in the commands list and is
+# executable
+check_command(){
+    [[ -n ${commands[$1]} ]] && [ -x ${commands[$1]} ] && return 0
+    return 1
 }
 
-get_vcs_info () {
-    # Donne les infos sur le dépôt VCS courant.
-    local LBRANCH LTYPE BRANCH TYPE DIR
-    declare -a DIR
-    declare -A TYPE
-    declare -A BRANCH
-    DIR[0]=".git"
-    DIR[1]=".hg"
-    DIR[2]="_darcs"
-    DIR[3]=".svn"
-    TYPE[.git]="git"
-    TYPE[.hg]="mercurial"
-    TYPE[_darcs]="darcs"
-    TYPE[.svn]="svn"
-    BRANCH[.git]='git branch 2>/dev/null | sed -r "s/^[^*].*$//" | paste -s -d "" | sed -r "s/^[*] //"'
-    BRANCH[.hg]='hg branch 2>/dev/null'
-    BRANCH[_darcs]="darcs show repo 2>/dev/null| egrep '^ *Cache' | sed 's@.*/\([^/]*\),.*@\1@'"
-    BRANCH[.svn]="svn info 2>/dev/null | head -n 6 | tail -n 1"
-
-    DIR=$(eval "find_up \"$PWD\" -name \"\"$(printf -- ' -o -name "%s"' "${DIR[@]}") | head -n 1")
-    if [ -n "$DIR" ]; then
-        DIR=$(basename "$DIR")
-        LBRANCH=$(eval "${BRANCH[$DIR]}")
-        LTYPE="${TYPE[$DIR]}"
-        if [ "$color_prompt" = yes ]; then
-            VCS_info="${nocolor_prompt}${vcs_symbols_color}(${vcs_type_color}$LTYPE${vcs_symbols_color})-${vcs_symbols_color}[${vcs_branch_color}$LBRANCH${vcs_symbols_color}]${nocolor_prompt}"
+# If name should be overwritten (eg for git-svn), do it.
+vcs_adjust(){
+    [[ -n ${vcs_comm[overwrite_name]} ]] && vcs=${vcs_comm[overwrite_name]}
+    return 0
+}
+
+# Formats two VCS_info messages, one with colors, and one without
+vcs_formats(){
+    local action=$1 branch=$2 base=$3 rev=$4
+    local msg
+    local -i i
+
+    # printf is for readability (it's easier to find %s)
+    msg="(%s)-[%s/%s"
+    msg=$(printf $msg $vcs ${base/*\/} $branch)
+
+    # If there is a revnumber, print it
+    if [ ! -z ${rev} ]; then
+        msg="${msg}:%s"
+        msg=$(printf $msg $rev)
+    fi
+
+    # Print the current cvs action state
+    if [ ! -z ${action} ] ; then
+        msg="${msg}|%s"
+        msg=$(printf $msg $action)
+    fi
+    msg="${msg}]-"
+
+    msgs[1]=$msg
+
+    # Same shit with colors
+    msg="${nocolor_prompt}${vcs_symbols_color}(${vcs_type_color}%s${vcs_symbols_color})${vcs_sep_color}-${vcs_symbols_color}[${vcs_repo_color}%s${vcs_sep_color}/${vcs_branch_color}%s"
+    msg=$(printf $msg $vcs ${base/*\/} $branch)
+    if [ ! -z ${rev} ]; then
+        msg="${msg}${vcs_colon_color}:${vcs_rev_color}%s"
+        msg=$(printf $msg $rev)
+    fi
+    if [[ ! -z ${action} ]] ; then
+        msg="${msg}${nocolor_prompt}|${vcs_action_color}%s"
+        msg=$(printf $msg $action)
+    fi
+    msg="${msg}${vcs_symbols_color}]${nocolor_prompt}-"
+    msgs[0]=$msg
+
+    return 0
+}
+
+# Uses -P option for cd in order to resolve symlinks
+vcs_realpath(){
+    (
+        cd -P $1 2>/dev/null && pwd
+    )
+}
+
+# Feature to detect a special dir, at the top of
+# the current repo
+detect_by_dir(){
+    local dirname=$1
+    local basedir="." realbasedir
+
+    realbasedir="$(vcs_realpath ${basedir})"
+    while [[ ${realbasedir} != '/' ]]; do
+        [[ -r ${realbasedir} ]] || return 1
+
+        # Tries to find detect_need_file (eg formats) in the dir
+        if [[ -n ${vcs_comm[detect_need_file]} ]] ; then
+            [[ -d ${basedir}/${dirname} ]] && \
+            [[ -e ${basedir}/${dirname}/${vcs_comm[detect_need_file]} ]] && \
+                break
         else
-            VCS_info="($LTYPE)-[$LBRANCH]"
+            [[ -d ${basedir}/${dirname} ]] && break
+        fi
+
+        basedir=${basedir}/..
+        realbasedir="$(vcs_realpath ${basedir})"
+    done
+
+    [[ ${realbasedir} == "/" ]] && return 1
+    vcs_comm[basedir]=${realbasedir}
+    return 0
+}
+
+# Git is powerfull
+git_detect(){
+    if check_command git && git rev-parse --is-inside-work-tree &> /dev/null; then
+        vcs_comm[gitdir]="$(git rev-parse --git-dir 2> /dev/null)" || return 1
+        if   [[ -d ${vcs_comm[gitdir]}/svn ]]             ; then vcs_comm[overwrite_name]='git-svn'
+        elif [[ -d ${vcs_comm[gitdir]}/refs/remotes/p4 ]] ; then vcs_comm[overwrite_name]='git-p4' ; fi
+        return 0
+    fi
+    return 1
+}
+
+# Mercurial isn't
+hg_detect(){
+    check_command hg || return 1
+    vcs_comm[detect_need_file]=store
+    detect_by_dir '.hg'
+    return $?
+}
+
+# Neither is svk
+# TODO - Not working : imported from zsh but not post treated
+svk_detect(){
+    local -a info
+    local -i fhash
+    fhash=0
+
+    check_command svk || return 1
+    [[ -f ~/.svk/config ]] || return 1
+
+    # This detection function is a bit different from the others.
+    # We need to read svk's config file to detect a svk repository
+    # in the first place. Therefore, we'll just proceed and read
+    # the other information, too. This is more then any of the
+    # other detections do but this takes only one file open for
+    # svk at most. VCS_INFO_svk_get_data() get simpler, too. :-)
+    while IFS= read -r line ; do
+        if [[ -n ${vcs_comm[basedir]} ]] ; then
+            line=${line## ##}
+            [[ ${line} == depotpath:* ]] && vcs_comm[branch]=${line##*/}
+            [[ ${line} == revision:* ]] && vcs_comm[revision]=${line##*[[:space:]]##}
+            [[ -n ${vcs_comm[branch]} ]] && [[ -n ${vcs_comm[revision]} ]] && break
+            continue
+        fi
+        (( fhash > 0 )) && [[ ${line} == '  '[^[:space:]]*:* ]] && break
+        [[ ${line} == '  hash:'* ]] && fhash=1 && continue
+        (( fhash == 0 )) && continue
+        [[ ${PWD}/ == ${${line## ##}%:*}/* ]] && vcs_comm[basedir]=${${line## ##}%:*}
+    done < ~/.svk/config
+
+    [[ -n ${vcs_comm[basedir]} ]]  && \
+    [[ -n ${vcs_comm[branch]} ]]   && \
+    [[ -n ${vcs_comm[revision]} ]] && return 0
+    return 1
+}
+
+# .svn in each directories
+svn_detect() {
+    check_command svn || return 1
+    [[ -d ".svn" ]] && return 0
+    return 1
+}
+
+bzr_detect(){
+    check_command bzr || return 1
+    vcs_comm[detect_need_file]=branch/format
+    detect_by_dir '.bzr'
+    return $?
+}
+
+cdv_detect(){
+    check_command cdv || return 1
+    vcs_comm[detect_need_file]=format
+    detect_by_dir '.cdv'
+    return $?
+}
+
+cvs_detect(){
+    check_command svn || return 1
+    [[ -d "./CVS" ]] && [[ -r "./CVS/Repository" ]] && return 0
+    return 1
+}
+
+darcs_detect(){
+    check_command darcs || return 1
+    vcs_comm[detect_need_file]=format
+    detect_by_dir '_darcs'
+    return $?
+}
+
+# Find git's branch
+git_getbranch (){
+    local gitbranch gitdir=$1 tmp actiondir
+    local gitsymref='git symbolic-ref HEAD'
+
+    # In certain circumstances, we have to take into account
+    # actions
+    actiondir=''
+    for tmp in "${gitdir}/rebase-apply" \
+               "${gitdir}/rebase"       \
+               "${gitdir}/../.dotest"; do
+        if [[ -d ${tmp} ]]; then
+            actiondir=${tmp}
+            break
+        fi
+    done
+    if [[ -n ${actiondir} ]]; then
+        gitbranch="$(${gitsymref} 2> /dev/null)"
+        [[ -z ${gitbranch} ]] && [[ -r ${actiondir}/head-name ]] \
+            && gitbranch="$(< ${actiondir}/head-name)"
+
+    # MERGE_HEAD state
+    elif [[ -f "${gitdir}/MERGE_HEAD" ]] ; then
+        gitbranch="$(eval $gitsymref 2> /dev/null)"
+        [[ -z ${gitbranch} ]] && gitbranch="$(< ${gitdir}/MERGE_HEAD)"
+
+    # rebase
+    elif [[ -d "${gitdir}/rebase-merge" ]] ; then
+        gitbranch="$(< ${gitdir}/rebase-merge/head-name)"
+
+    # dotest
+    elif [[ -d "${gitdir}/.dotest-merge" ]] ; then
+        gitbranch="$(< ${gitdir}/.dotest-merge/head-name)"
+
+    # Normal case
+    else
+        gitbranch="$(eval $gitsymref 2> /dev/null)"
+
+        # shit happens
+        if [[ $? -ne 0 ]] ; then
+            gitbranch="refs/tags/$(git describe --exact-match HEAD 2>/dev/null)"
+
+            # big shit happens
+            if [[ $? -ne 0 ]] ; then
+                gitbranch=$(< $gitdir/HEAD)
+                gitbranch="${gitbranch:0:7}..."
+            fi
+        fi
+    fi
+
+    # keep only the last part of gitbranch
+    printf '%s' "${gitbranch#refs/[^/]*/}"
+    return 0
+}
+
+git_getaction(){
+    local gitaction='' gitdir=$1
+    local tmp
+
+    for tmp in "${gitdir}/rebase-apply" \
+               "${gitdir}/rebase"       \
+               "${gitdir}/../.dotest" ; do
+        if [[ -d ${tmp} ]] ; then
+            if   [[ -f "${tmp}/rebasing" ]] ; then
+                gitaction="rebase"
+            elif [[ -f "${tmp}/applying" ]] ; then
+                gitaction="am"
+            else
+                gitaction="am/rebase"
+            fi
+            printf '%s' ${gitaction}
+            return 0
         fi
-        VCS_size=$((${#LTYPE}+${#LBRANCH}+5))
+    done
+
+    for tmp in "${gitdir}/rebase-merge/interactive" \
+               "${gitdir}/.dotest-merge/interactive" ; do
+        if [[ -f "${tmp}" ]] ; then
+            printf '%s' "rebase-i"
+            return 0
+        fi
+    done
+
+    for tmp in "${gitdir}/rebase-merge" \
+               "${gitdir}/.dotest-merge" ; do
+        if [[ -d "${tmp}" ]] ; then
+            printf '%s' "rebase-m"
+            return 0
+        fi
+    done
+
+    if [[ -f "${gitdir}/MERGE_HEAD" ]] ; then
+        printf '%s' "merge"
+        return 0
+    fi
+
+    if [[ -f "${gitdir}/BISECT_LOG" ]] ; then
+        printf '%s' "bisect"
+        return 0
+    fi
+    return 1
+}
+
+git_get_data(){
+    local gitdir gitbase gitbranch gitaction
+
+    gitdir=${vcs_comm[gitdir]}
+    gitbranch="$(git_getbranch ${gitdir})"
+
+    if [[ -z ${gitdir} ]] || [[ -z ${gitbranch} ]] ; then
+        return 1
+    fi
+
+    vcs_adjust
+    gitaction="$(git_getaction ${gitdir})"
+    gitprefix=$(git rev-parse --show-prefix)
+    gitbase=${PWD%/${gitprefix%/}}
+    vcs_formats "${gitaction}" "${gitbranch}" "${gitbase}" ''
+    return 0
+}
+
+hg_get_data(){
+    local hgbranch hgbase file
+
+    hgbase=${vcs_comm[basedir]}
+
+    file="${hgbase}/.hg/branch"
+    if [[ -r ${file} ]] ; then
+        hgbranch=$(< ${file})
     else
-        VCS_info=""
-        VCS_size=0
+        hgbranch='default'
     fi
+
+    vcs_formats '' "${hgbranch}" "${hgbase}" ''
+    return 0
+}
+
+svk_get_data(){
+    local svkbranch svkbase
+
+    svkbase=${vcs_comm[basedir]}
+    svkbranch=${vcs_comm[branch]}
+    svkrevision=${vcs_comm[revision]}
+    vcs_formats '' "${svkbranch}" "${svkbase}" "${svkrevision}"
+    return 0
+}
+
+svn_get_data(){
+    local svnbase svnbranch
+    local -a svninfo
+
+    svnbase="."
+    while [[ -d "${svnbase}/../.svn" ]]; do
+        svnbase="${svnbase}/.."
+    done
+    svnbase="$(vcs_realpath ${svnbase})"
+    svnrev=$(svn info | awk '{if($1 == "Révision :") print $2}')
+    svnbranch=$(svn info | awk '{if($1 == "URL :") print $2}'|awk -F "/" '{ print $NF }')
+
+    vcs_formats '' "${svnbranch}" "${svnbase}" "${svnrev}"
+    return 0
+}
+
+bzr_get_data(){
+    local bzrbase bzrbr
+    local -a bzrinfo
+
+    bzrbase=$(bzr info|awk '{if ($1 == "branch" && $2 == "root:") print $3}')
+    bzrbranch=$(bzr version-info|awk '{if ($1 == "branch-nick:") print $2}')
+    bzrrev=$(bzr version-info|awk '{if ($1 == "revno:") print $2}')
+    bzrbase="$(vcs_realpath ${bzrbase})"
+    bzrbr="${bzrbranch}"
+
+    vcs_formats '' "${bzrbr}" "${bzrbase}" "${bzrrev}"
+    return 0
+}
+
+cdv_get_data(){
+    local cdvbase
+
+    cdvbase=${vcs_comm[basedir]}
+    vcs_formats '' "${cdvbase/*\/}" "${cdvbase}" ''
+    return 0
+}
+
+cvs_get_data(){
+    local cvsbranch cvsbase basename
+
+    cvsbase="."
+    while [[ -d "${cvsbase}/../CVS" ]]; do
+        cvsbase="${cvsbase}/.."
+    done
+    cvsbase="$(vcs_realpath ${cvsbase})"
+    cvsbranch=$(< ./CVS/Repository)
+    basename=${cvsbase/*\/}
+    cvsbranch=${cvsbranch#${basename}/}
+
+    [[ -z ${cvsbranch} ]] && cvsbranch=${basename}
+    vcs_formats '' "${cvsbranch}" "${cvsbase}" ''
+    return 0
 }
 
+darcs_get_data(){
+    local darcsbase
+
+    darcsbase=${vcs_comm[basedir]}
+    vcs_formats '' "${darcsbase/*\/}" "${darcsbase}" ''
+    return 0
+}
+
+vcs_info(){
+    local -i found
+    local -ax msgs
+    local -Ax vcs_comm commands
+    local -x vcs
+    local -a vcss
+
+    vcs="init"
+    vcss=(git hg bzr darcs svk svn cvs cdv)
+    for i in $(seq 0 $(( ${#vcss[*]} - 1 ))); do
+        commands[${vcss[$i]}]=$(which ${vcss[$i]});
+    done;
+
+    found=0
+    for vcs in ${vcss[*]}; do
+        ${vcs}_detect && found=1 && break
+    done
+
+    (( found == 1 )) && ${vcs}_get_data
+
+    if [ ${color_prompt} = "yes" ]; then
+        VCS_info=${msgs[0]}
+    else
+        VCS_info=${msgs[1]}
+    fi
+    VCS_size=${#msgs[1]}
+}
 
 # Pour avoir le bon umask en fonction du dossier où on se trouve
 # L'umask définit avec quel droits un fichier est créé.
@@ -151,30 +515,6 @@ blanc_prompt="\[${blanc}\]"
 blanc_thin_prompt="\[${blanc_thin}\]"
 nocolor_prompt="\[${nocolor}\]"
 
-if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
-    # support de la couleur
-    color_prompt=yes
-    # Couleurs dans "user@host $"
-    username_color=${rouge_prompt}
-    host_color=${bleu_prompt}
-    symbols_color=${vert_prompt}
-    # Couleur de la ligne séparatrice de prompt
-    line_color=${cyan}
-    line_color_prompt=${cyan_prompt}
-    # Couleur du path actuel
-    pwd_color=${jaune_prompt}
-    # Couleur de la date (à chaque affichage du prompt)
-    date_color=${violet_prompt}
-    # Couleur de la date au premier affichage (à l'ouverture du terminal)
-    announce_date_color=${blanc}
-    # Couleur d'affichage de vcs_info
-    vcs_symbols_color=${violet_thin_prompt}
-    vcs_type_color=${jaune_thin_prompt}
-    vcs_branch_color=${vert_thin_prompt}
-else
-    # pas de support de la couleur
-    color_prompt=no
-fi
 
 # Est-ce qu'on veut que le prompt affiche les information sur l'éventuel dépôt
 # versionné dans lequel on se trouve
@@ -215,7 +555,7 @@ function prompt_command
     # À décommenter si on veut afficher des infos
     # quand on se trouve dans un dépôt versionné
     if [ "$display_vcs_info" = yes ]; then
-        get_vcs_info
+        vcs_info
     fi
     
     # Chemin courant, en faisant attention à la largeur de la fenêtre
@@ -257,6 +597,36 @@ function prompt_command
     fi
 }
 
+if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
+    # support de la couleur
+    color_prompt=yes
+    # Couleurs dans "user@host $"
+    username_color=${rouge_prompt}
+    host_color=${bleu_prompt}
+    symbols_color=${vert_prompt}
+    # Couleur de la ligne séparatrice de prompt
+    line_color=${cyan}
+    line_color_prompt=${cyan_prompt}
+    # Couleur du path actuel
+    pwd_color=${jaune_prompt}
+    # Couleur de la date (à chaque affichage du prompt)
+    date_color=${violet_prompt}
+    # Couleur de la date au premier affichage (à l'ouverture du terminal)
+    announce_date_color=${blanc}
+    # Couleur d'affichage de vcs_info
+    vcs_symbols_color=${violet_thin_prompt}
+    vcs_type_color=${jaune_thin_prompt}
+    vcs_branch_color=${vert_thin_prompt}
+    vcs_repo_color=${vert_thin_prompt}
+    vcs_action_color=${rouge_thin_prompt}
+    vcs_sep_color=${jaune_thin_prompt}
+    vcs_rev_color=${jaune_thin_prompt}
+    vcs_colon_color=${rouge_thin_prompt}
+else
+    # pas de support de la couleur
+    color_prompt=no
+fi
+
 # On change le titre de la fenêtre dynamiquement si on est sous X
 if [[ $TERM = "xterm" ]]; then
     TITLE='\[\e];\u@\h:\w\a\]'
@@ -264,7 +634,7 @@ else
     TITLE=''
 fi
 
-# On régénère le prompt après chaque commande
+# On regénére le prompt après chaque commande
 PROMPT_COMMAND=prompt_command
 
 # +-------------------+