diff options
-rw-r--r-- | README.md | 12 | ||||
-rwxr-xr-x | functions/vcs.zsh | 64 | ||||
-rwxr-xr-x | powerlevel9k.zsh-theme | 4 | ||||
-rwxr-xr-x | test/segments/vcs-git.spec | 130 | ||||
-rwxr-xr-x | test/segments/vcs-hg.spec | 15 |
5 files changed, 187 insertions, 38 deletions
@@ -410,10 +410,10 @@ Customizations available are: |Default|Truncate whole directories from left. How many is defined by `POWERLEVEL9K_SHORTEN_DIR_LENGTH`| |`truncate_absolute_chars`|Truncates an absolute number of characters from the left such that the number of characters that your path displays (with or without `POWERLEVEL9K_SHORTEN_DELIMITER`) is no more than `POWERLEVEL9K_SHORTEN_DIR_LENGTH` + the length of `POWERLEVEL9K_SHORTEN_DELIMITER` | |`truncate_middle`|Truncates the middle part of a folder. E.g. you are in a folder named `~/MySuperProjects/AwesomeFiles/BoringOffice`, then it will truncated to `~/MyS..cts/Awe..les/BoringOffice`, if `POWERLEVEL9K_SHORTEN_DIR_LENGTH=3` is also set (controls the amount of characters to be left).| -|`truncate_from_right`|Just leaves the beginning of a folder name untouched. E.g. your folders will be truncated like so: "/ro../Pr../office". How many characters will be untouched is controlled by `POWERLEVEL9K_SHORTEN_DIR_LENGTH`.| -|`truncate_absolute`|Truncates everything exept the last few characters in the path. E.g. if you are in a folder named "~/Projects/powerlevel9k" and you have set `POWERLEVEL9K_SHORTEN_DIR_LENGTH=3`, you will get "..l9k".| +|`truncate_from_right`|Just leaves the beginning of a folder name untouched. E.g. your folders will be truncated like so: `/ro../Pr../office`. How many characters will be untouched is controlled by `POWERLEVEL9K_SHORTEN_DIR_LENGTH`.| +|`truncate_absolute`|Truncates everything exept the last few characters in the path. E.g. if you are in a folder named `~/Projects/powerlevel9k` and you have set `POWERLEVEL9K_SHORTEN_DIR_LENGTH=3`, you will get `..l9k`.| |`truncate_to_last`|Truncates everything before the last folder in the path.| -|`truncate_to_first_and_last`|Truncate middle directories from the path. How many directories will be untouched is controlled by POWERLEVEL9K_SHORTER_DIR_LENGTH. E.g. if you are in a folder named "~/Projects/powerlevel9k" and you have set `POWERLEVEL9K_SHORTEN_DIR_LENGTH=1`, you will get "~/../powerlevel9k".|| +|`truncate_to_first_and_last`|Truncate middle directories from the path. How many directories will be untouched is controlled by `POWERLEVEL9K_SHORTEN_DIR_LENGTH`. E.g. if you are in a folder named `~/Projects/powerlevel9k` and you have set `POWERLEVEL9K_SHORTEN_DIR_LENGTH=1`, you will get `~/../powerlevel9k`.|| |`truncate_to_unique`|Parse all parent path components and truncate them to the shortest unique length. If you copy & paste the result to a shell, after hitting `TAB` it should expand to the original path unambiguously.| |`truncate_with_package_name`|Search for a `package.json` or `composer.json` and prints the `name` field to abbreviate the directory path. The precedence and/or files could be set by `POWERLEVEL9K_DIR_PACKAGE_FILES=(package.json composer.json)`. If you have [jq](https://stedolan.github.io/jq/) installed, it will dramatically improve the speed of this strategy.| |`truncate_with_folder_marker`|Search for a file that is specified by `POWERLEVEL9K_SHORTEN_FOLDER_MARKER` and truncate everything before that (if found, otherwise stop on $HOME and ROOT).| @@ -468,9 +468,9 @@ The `disk_usage` segment will show the usage level of the partition that your cu | Variable | Default Value | Description | |----------|---------------|-------------| -|POWERLEVEL9K_DISK_USAGE_ONLY_WARNING|false|Hide the segment except when usage levels have hit warning or critical levels.| -|POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL|90|The usage level that triggers a warning state.| -|POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL|95|The usage level that triggers a critical state.| +|`POWERLEVEL9K_DISK_USAGE_ONLY_WARNING`|false|Hide the segment except when usage levels have hit warning or critical levels.| +|`POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL`|90|The usage level that triggers a warning state.| +|`POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL`|95|The usage level that triggers a critical state.| ##### host diff --git a/functions/vcs.zsh b/functions/vcs.zsh index b99e7c86..6c9bd055 100755 --- a/functions/vcs.zsh +++ b/functions/vcs.zsh @@ -6,18 +6,30 @@ # https://github.com/bhilburn/powerlevel9k ################################################################ -set_default POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY true +set_default POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY false function +vi-git-untracked() { - # TODO: check git >= 1.7.2 - see function git_compare_version() - local FLAGS - FLAGS=('--porcelain') - - if [[ "$POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY" == "false" ]]; then - FLAGS+='--ignore-submodules=dirty' - fi - - if [[ $(command git rev-parse --is-inside-work-tree 2> /dev/null) == 'true' && \ - -n $(command git status ${FLAGS} | \grep -E '^\?\?' 2> /dev/null | tail -n1) ]]; then + [[ -z "${vcs_comm[gitdir]}" || "${vcs_comm[gitdir]}" == "." ]] && return + # If we are in a .git folder, do not check for untracked files. + [[ "${PWD:A}" =~ "\.git/" ]] && return + + # If we are in a repos root folder, vcs_comm[gitdir] yields ".git". + # Inside the .git dir itself (and not a subdir of it) the variable + # yields ".". In any other case (either a subdirectory of .git or + # the repo itself), the value of vcs_comm[gitdir] is the absolute + # path to the .git directory. + # Therefore we can step up a directory, if we are inside the .git + # folder. And in any other case, use the parent directory of the + # gitdir. + local repoDir="." + # Getting the parent dir of the current dir "." is still ".", so + # is is safe to do this always. + [[ "${vcs_comm[gitdir]}" != ".git" ]] && repoDir="${vcs_comm[gitdir]:A:h}" + [[ "${vcs_comm[gitdir]}" == "." ]] && repoDir="${PWD:A:h}" + + if [[ "$POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY" == "true" && "$(command git submodule foreach --quiet --recursive 'command git ls-files --others --exclude-standard')" != "" ]]; then + hook_com[unstaged]+=" $(print_icon 'VCS_UNTRACKED_ICON')" + VCS_WORKDIR_HALF_DIRTY=true + elif [[ "$(command git ls-files --others --exclude-standard "${repoDir}")" != "" ]]; then hook_com[unstaged]+=" $(print_icon 'VCS_UNTRACKED_ICON')" VCS_WORKDIR_HALF_DIRTY=true else @@ -26,41 +38,39 @@ function +vi-git-untracked() { } function +vi-git-aheadbehind() { - local ahead behind branch_name + local ahead behind local -a gitstatus - branch_name=$(command git symbolic-ref --short HEAD 2>/dev/null) - # for git prior to 1.7 - # ahead=$(command git rev-list origin/${branch_name}..HEAD | wc -l) - ahead=$(command git rev-list "${branch_name}"@{upstream}..HEAD 2>/dev/null | wc -l) + # ahead=$(command git rev-list origin/${hook_com[branch]}..HEAD | wc -l) + ahead=$(command git rev-list --count "${hook_com[branch]}"@{upstream}..HEAD 2>/dev/null) (( ahead )) && gitstatus+=( " $(print_icon 'VCS_OUTGOING_CHANGES_ICON')${ahead// /}" ) # for git prior to 1.7 - # behind=$(command git rev-list HEAD..origin/${branch_name} | wc -l) - behind=$(command git rev-list HEAD.."${branch_name}"@{upstream} 2>/dev/null | wc -l) + # behind=$(command git rev-list HEAD..origin/${hook_com[branch]} | wc -l) + behind=$(command git rev-list --count HEAD.."${hook_com[branch]}"@{upstream} 2>/dev/null) (( behind )) && gitstatus+=( " $(print_icon 'VCS_INCOMING_CHANGES_ICON')${behind// /}" ) hook_com[misc]+=${(j::)gitstatus} } function +vi-git-remotebranch() { - local remote branch_name + local remote + local branch_name="${hook_com[branch]}" # Are we on a remote-tracking branch? remote=${$(command git rev-parse --verify HEAD@{upstream} --symbolic-full-name 2>/dev/null)/refs\/(remotes|heads)\/} - branch_name=$(command git symbolic-ref --short HEAD 2>/dev/null) if [[ -n "$POWERLEVEL9K_VCS_SHORTEN_LENGTH" ]] && [[ -n "$POWERLEVEL9K_VCS_SHORTEN_MIN_LENGTH" ]]; then set_default POWERLEVEL9K_VCS_SHORTEN_DELIMITER $'\U2026' - if [ ${#hook_com[branch]} -gt $POWERLEVEL9K_VCS_SHORTEN_MIN_LENGTH ] && [ ${#hook_com[branch]} -gt $POWERLEVEL9K_VCS_SHORTEN_LENGTH ]; then + if [ ${#hook_com[branch]} -gt ${POWERLEVEL9K_VCS_SHORTEN_MIN_LENGTH} ] && [ ${#hook_com[branch]} -gt ${POWERLEVEL9K_VCS_SHORTEN_LENGTH} ]; then case "$POWERLEVEL9K_VCS_SHORTEN_STRATEGY" in truncate_middle) - hook_com[branch]="$(echo "${branch_name:0:$POWERLEVEL9K_VCS_SHORTEN_LENGTH}")$POWERLEVEL9K_VCS_SHORTEN_DELIMITER$(echo "${branch_name: -$POWERLEVEL9K_VCS_SHORTEN_LENGTH}")" + hook_com[branch]="${branch_name:0:$POWERLEVEL9K_VCS_SHORTEN_LENGTH}${POWERLEVEL9K_VCS_SHORTEN_DELIMITER}${branch_name: -$POWERLEVEL9K_VCS_SHORTEN_LENGTH}" ;; truncate_from_right) - hook_com[branch]="$(echo "${branch_name:0:$POWERLEVEL9K_VCS_SHORTEN_LENGTH}")$POWERLEVEL9K_VCS_SHORTEN_DELIMITER" + hook_com[branch]="${branch_name:0:$POWERLEVEL9K_VCS_SHORTEN_LENGTH}${POWERLEVEL9K_VCS_SHORTEN_DELIMITER}" ;; esac fi @@ -104,11 +114,9 @@ function +vi-git-tagname() { # Show count of stashed changes # Port from https://github.com/whiteinge/dotfiles/blob/5dfd08d30f7f2749cfc60bc55564c6ea239624d9/.zsh_shouse_prompt#L268 function +vi-git-stash() { - local -a stashes - - if [[ -s $(command git rev-parse --git-dir)/refs/stash ]] ; then - stashes=$(command git stash list 2>/dev/null | wc -l) - hook_com[misc]+=" $(print_icon 'VCS_STASH_ICON')${stashes// /}" + if [[ -s "${vcs_comm[gitdir]}/logs/refs/stash" ]] ; then + local -a stashes=( "${(@f)"$(<${vcs_comm[gitdir]}/logs/refs/stash)"}" ) + hook_com[misc]+=" $(print_icon 'VCS_STASH_ICON')${#stashes}" fi } diff --git a/powerlevel9k.zsh-theme b/powerlevel9k.zsh-theme index 3c3303fa..7077bfc8 100755 --- a/powerlevel9k.zsh-theme +++ b/powerlevel9k.zsh-theme @@ -803,7 +803,7 @@ prompt_dir() { # if we are not in "~" or "/", split the paths into an array and exclude "~" (( ${#current_path} > 1 )) && paths=(${(s:/:)${current_path//"~\/"/}}) || paths=() # only run the code if SHORTEN_DIR_LENGTH is set, or we are using the two strategies that don't rely on it. - if [[ -n "$POWERLEVEL9K_SHORTEN_DIR_LENGTH" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_with_folder_marker" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_to_last" ]]; then + if [[ -n "$POWERLEVEL9K_SHORTEN_DIR_LENGTH" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_with_folder_marker" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_to_last" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_with_package_name" ]]; then set_default POWERLEVEL9K_SHORTEN_DELIMITER "\u2026" # convert delimiter from unicode to literal character, so that we can get the correct length later local delim=$(echo -n $POWERLEVEL9K_SHORTEN_DELIMITER) @@ -1134,7 +1134,7 @@ prompt_ip() { set_default POWERLEVEL9K_VPN_IP_INTERFACE "tun" # prompt if vpn active prompt_vpn_ip() { - for vpn_iface in $(/sbin/ifconfig | grep -e ^"$POWERLEVEL9K_VPN_IP_INTERFACE" | cut -d":" -f1) + for vpn_iface in $(/sbin/ifconfig | grep -e "^${POWERLEVEL9K_VPN_IP_INTERFACE}" | cut -d":" -f1) do ip=$(/sbin/ifconfig "$vpn_iface" | grep -o "inet\s.*" | cut -d' ' -f2) "$1_prompt_segment" "$0" "$2" "cyan" "$DEFAULT_COLOR" "$ip" 'VPN_ICON' diff --git a/test/segments/vcs-git.spec b/test/segments/vcs-git.spec index bddecf6c..ab2962c8 100755 --- a/test/segments/vcs-git.spec +++ b/test/segments/vcs-git.spec @@ -375,4 +375,132 @@ function testShorteningCommitHashIsNotShownIfShowChangesetIsFalse() { assertEquals "%K{002} %F{000} master %k%F{002}%f " "$(build_left_prompt)" } -source shunit2/shunit2
\ No newline at end of file +function testDetectingUntrackedFilesInSubmodulesWork() { + local -a POWERLEVEL9K_LEFT_PROMPT_ELEMENTS + POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(vcs) + local POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY="true" + unset POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND + + mkdir ../submodule + cd ../submodule + git init 1>/dev/null + touch "i-am-tracked.txt" + git add . 1>/dev/null && git commit -m "Initial Commit" 1>/dev/null + + local submodulePath="${PWD}" + + cd - + git submodule add "${submodulePath}" 2>/dev/null + git commit -m "Add submodule" 1>/dev/null + + # Go into checked-out submodule path + cd submodule + # Create untracked file + touch "i-am-untracked.txt" + cd - + + source ${P9K_HOME}/powerlevel9k.zsh-theme + + assertEquals "%K{002} %F{000} master ? %k%F{002}%f " "$(build_left_prompt)" +} + +function testDetectinUntrackedFilesInMainRepoWithDirtySubmodulesWork() { + local -a POWERLEVEL9K_LEFT_PROMPT_ELEMENTS + POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(vcs) + local POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY="true" + unset POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND + + mkdir ../submodule + cd ../submodule + git init 1>/dev/null + touch "i-am-tracked.txt" + git add . 1>/dev/null && git commit -m "Initial Commit" 1>/dev/null + + local submodulePath="${PWD}" + + cd - + git submodule add "${submodulePath}" 2>/dev/null + git commit -m "Add submodule" 1>/dev/null + + # Create untracked file + touch "i-am-untracked.txt" + + source ${P9K_HOME}/powerlevel9k.zsh-theme + + assertEquals "%K{002} %F{000} master ? %k%F{002}%f " "$(build_left_prompt)" +} + +function testDetectingUntrackedFilesInNestedSubmodulesWork() { + local -a POWERLEVEL9K_LEFT_PROMPT_ELEMENTS + POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(vcs) + local POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY="true" + unset POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND + + local mainRepo="${PWD}" + + mkdir ../submodule + cd ../submodule + git init 1>/dev/null + touch "i-am-tracked.txt" + git add . 1>/dev/null && git commit -m "Initial Commit" 1>/dev/null + + local submodulePath="${PWD}" + + mkdir ../subsubmodule + cd ../subsubmodule + git init 1>/dev/null + touch "i-am-tracked-too.txt" + git add . 1>/dev/null && git commit -m "Initial Commit" 1>/dev/null + + local subsubmodulePath="${PWD}" + + cd "${submodulePath}" + git submodule add "${subsubmodulePath}" 2>/dev/null + git commit -m "Add subsubmodule" 1>/dev/null + cd "${mainRepo}" + git submodule add "${submodulePath}" 2>/dev/null + git commit -m "Add submodule" 1>/dev/null + + git submodule update --init --recursive 2>/dev/null + + cd submodule/subsubmodule + # Create untracked file + touch "i-am-untracked.txt" + cd - + + source ${P9K_HOME}/powerlevel9k.zsh-theme + + assertEquals "%K{002} %F{000} master ? %k%F{002}%f " "$(build_left_prompt)" +} + +function testDetectingUntrackedFilesInCleanSubdirectoryWorks() { + local -a POWERLEVEL9K_LEFT_PROMPT_ELEMENTS + POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(vcs) + local POWERLEVEL9K_VCS_SHOW_SUBMODULE_DIRTY="true" + unset POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND + mkdir clean-folder + touch clean-folder/file.txt + mkdir dirty-folder + touch dirty-folder/file.txt + git add . 2>/dev/null + git commit -m "Initial commit" >/dev/null + touch dirty-folder/new-file.txt + cd clean-folder + source ${P9K_HOME}/powerlevel9k.zsh-theme + assertEquals "%K{002} %F{000} master ? %k%F{002}%f " "$(build_left_prompt)" +} + +function testBranchNameScriptingVulnerability() { + local -a POWERLEVEL9K_LEFT_PROMPT_ELEMENTS + POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(vcs) + echo "#!/bin/sh\n\necho 'hacked'\n" > evil_script.sh + chmod +x evil_script.sh + + git checkout -b '$(./evil_script.sh)' 2>/dev/null + git add . 2>/dev/null + git commit -m "Initial commit" >/dev/null + + assertEquals '%K{002} %F{000} $(./evil_script.sh) %k%F{002}%f ' "$(build_left_prompt)" +} + +source shunit2/shunit2 diff --git a/test/segments/vcs-hg.spec b/test/segments/vcs-hg.spec index 2903f544..c4289cef 100755 --- a/test/segments/vcs-hg.spec +++ b/test/segments/vcs-hg.spec @@ -204,4 +204,17 @@ function testBookmarkIconWorks() { assertEquals "%K{002} %F{000} default Binitial %k%F{002}%f " "$(build_left_prompt)" } -source shunit2/shunit2
\ No newline at end of file +function testBranchNameScriptingVulnerability() { + local -a POWERLEVEL9K_LEFT_PROMPT_ELEMENTS + POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(vcs) + echo "#!/bin/sh\n\necho 'hacked'\n" > evil_script.sh + chmod +x evil_script.sh + + hg branch '$(./evil_script.sh)' >/dev/null + hg add . >/dev/null + hg commit -m "Initial commit" >/dev/null + + assertEquals '%K{002} %F{000} $(./evil_script.sh) %k%F{002}%f ' "$(build_left_prompt)" +} + +source shunit2/shunit2 |