From f4b868713874d8c6b3f71436b5f7421c88b601a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20S=C5=82owik?= Date: Mon, 2 May 2016 15:02:18 +0200 Subject: [PATCH] Rewrite git-prompt plugin in ZSH instead of Python. Makes the plugin faster and eliminates dependency on Python. --- plugins/git-prompt/git-prompt.plugin.zsh | 101 ++++++++++++++++++++--- plugins/git-prompt/gitstatus.py | 84 ------------------- 2 files changed, 88 insertions(+), 97 deletions(-) delete mode 100644 plugins/git-prompt/gitstatus.py diff --git a/plugins/git-prompt/git-prompt.plugin.zsh b/plugins/git-prompt/git-prompt.plugin.zsh index 5175bf70f..5310b315f 100644 --- a/plugins/git-prompt/git-prompt.plugin.zsh +++ b/plugins/git-prompt/git-prompt.plugin.zsh @@ -29,24 +29,99 @@ preexec_functions+=(preexec_update_git_vars) ## Function definitions -function update_current_git_vars() { - unset __CURRENT_GIT_STATUS - local gitstatus="$__GIT_PROMPT_DIR/gitstatus.py" - _GIT_STATUS=$(python ${gitstatus} 2>/dev/null) - __CURRENT_GIT_STATUS=("${(@s: :)_GIT_STATUS}") - GIT_BRANCH=$__CURRENT_GIT_STATUS[1] - GIT_AHEAD=$__CURRENT_GIT_STATUS[2] - GIT_BEHIND=$__CURRENT_GIT_STATUS[3] - GIT_STAGED=$__CURRENT_GIT_STATUS[4] - GIT_CONFLICTS=$__CURRENT_GIT_STATUS[5] - GIT_CHANGED=$__CURRENT_GIT_STATUS[6] - GIT_UNTRACKED=$__CURRENT_GIT_STATUS[7] +function get_tagname_or_hash() { + local log_string + local refs ref + local ret_hash ret_tag + log_string="$(git log -1 --decorate=full --format="%h%d" 2> /dev/null)" + if [[ "$log_string" == *' ('*')' ]]; then + ret_hash="${log_string%% (*)}" + refs="${(M)log_string%% (*)}" + refs="${refs# \(}" + refs="${refs%\)}" + for ref in ${(s:, :)refs}; do + if [[ "$ref" == 'refs/tags/'* ]]; then # git 1.7.x + ret_tag="${ref#refs/tags/}" + elif [[ "$ref" == 'tag: refs/tags/'* ]]; then # git 2.1.x + ret_tag="${ref#tag: refs/tags/}" + fi + if [[ "$ret_tag" != "" ]]; then + TAG_OR_HASH="tags/$ret_tag" + return + fi + done + TAG_OR_HASH="$ret_hash" + fi +} + +function update_current_git_vars() { + local branch branch_description branch_parts + local ahead=0 behind=0 + local staged=0 conflict=0 changed=0 untracked=0 + local status_string status_line + local divergence div + + __CURRENT_GIT_STATUS=0 + status_string="$(git status --branch --porcelain -z 2> /dev/null)" + if [[ $? -ne 0 ]]; then + # not a git repository + return + fi + __CURRENT_GIT_STATUS=1 + + for status_line in ${(0)status_string}; do + if [[ "${status_line:0:2}" == '##' ]]; then + branch_description="${status_line:2}" + if [[ "$branch_description" == *'Initial commit on '* ]]; then + branch="${branch_description/#*'Initial commit on '/}" + elif [[ "$branch_description" == *'no branch'* ]]; then + get_tagname_or_hash + branch="$TAG_OR_HASH" + elif [[ "$branch_description" != *'...'* ]]; then + branch="${branch_description# }" + else + # local and remote branch info + branch_parts=(${(s:...:)branch_description}) + branch="${branch_parts[1]# }" + if [[ $#branch_parts -ne 1 ]]; then + # ahead or behind + divergence="${(M)branch_parts[2]%\[*\]}" + divergence="${divergence#\[}" + divergence="${divergence%\]}" + for div in ${(s:, :)divergence}; do + if [[ "$div" == 'ahead '* ]]; then + ahead="${div#ahead }" + elif [[ "$div" == 'behind '* ]]; then + behind="${div#behind }" + fi + done + fi + fi + elif [[ "${status_line:0:2}" == '??' ]]; then + untracked=$((untracked + 1)) + else + if [[ "${status_line:1:1}" == 'M' ]]; then + changed=$((changed + 1)) + elif [[ "${status_line:0:1}" == 'U' ]]; then + conflict=$((conflict + 1)) + elif [[ "${status_line:0:1}" != ' ' ]]; then + staged=$((staged + 1)) + fi + fi + done + GIT_BRANCH="$branch" + GIT_AHEAD="$ahead" + GIT_BEHIND="$behind" + GIT_STAGED="$staged" + GIT_CONFLICTS="$conflict" + GIT_CHANGED="$changed" + GIT_UNTRACKED="$untracked" } git_super_status() { precmd_update_git_vars - if [ -n "$__CURRENT_GIT_STATUS" ]; then + if [ "$__CURRENT_GIT_STATUS" -eq 1 ]; then STATUS="$ZSH_THEME_GIT_PROMPT_PREFIX$ZSH_THEME_GIT_PROMPT_BRANCH$GIT_BRANCH%{${reset_color}%}" if [ "$GIT_BEHIND" -ne "0" ]; then STATUS="$STATUS$ZSH_THEME_GIT_PROMPT_BEHIND$GIT_BEHIND%{${reset_color}%}" diff --git a/plugins/git-prompt/gitstatus.py b/plugins/git-prompt/gitstatus.py deleted file mode 100644 index a8eb8284b..000000000 --- a/plugins/git-prompt/gitstatus.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function - -import sys -import re -import shlex -from subprocess import Popen, PIPE, check_output - - -def get_tagname_or_hash(): - """return tagname if exists else hash""" - cmd = 'git log -1 --format="%h%d"' - output = check_output(shlex.split(cmd)).decode('utf-8').strip() - hash_, tagname = None, None - # get hash - m = re.search('\(.*\)$', output) - if m: - hash_ = output[:m.start()-1] - # get tagname - m = re.search('tag: .*[,\)]', output) - if m: - tagname = 'tags/' + output[m.start()+len('tag: '): m.end()-1] - - if tagname: - return tagname - elif hash_: - return hash_ - return None - - -# `git status --porcelain --branch` can collect all information -# branch, remote_branch, untracked, staged, changed, conflicts, ahead, behind -po = Popen(['git', 'status', '--porcelain', '--branch'], stdout=PIPE, stderr=PIPE) -stdout, sterr = po.communicate() -if po.returncode != 0: - sys.exit(0) # Not a git repository - -# collect git status information -untracked, staged, changed, conflicts = [], [], [], [] -ahead, behind = 0, 0 -status = [(line[0], line[1], line[2:]) for line in stdout.decode('utf-8').splitlines()] -for st in status: - if st[0] == '#' and st[1] == '#': - if re.search('Initial commit on', st[2]): - branch = st[2].split(' ')[-1] - elif re.search('no branch', st[2]): # detached status - branch = get_tagname_or_hash() - elif len(st[2].strip().split('...')) == 1: - branch = st[2].strip() - else: - # current and remote branch info - branch, rest = st[2].strip().split('...') - if len(rest.split(' ')) == 1: - # remote_branch = rest.split(' ')[0] - pass - else: - # ahead or behind - divergence = ' '.join(rest.split(' ')[1:]) - divergence = divergence.lstrip('[').rstrip(']') - for div in divergence.split(', '): - if 'ahead' in div: - ahead = int(div[len('ahead '):].strip()) - elif 'behind' in div: - behind = int(div[len('behind '):].strip()) - elif st[0] == '?' and st[1] == '?': - untracked.append(st) - else: - if st[1] == 'M': - changed.append(st) - if st[0] == 'U': - conflicts.append(st) - elif st[0] != ' ': - staged.append(st) - -out = ' '.join([ - branch, - str(ahead), - str(behind), - str(len(staged)), - str(len(conflicts)), - str(len(changed)), - str(len(untracked)), -]) -print(out, end='')