mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-05 17:00:58 +01:00
commit
75eada8412
4 changed files with 116 additions and 100 deletions
16
news/vc_branch.rst
Normal file
16
news/vc_branch.rst
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
**Added:** None
|
||||||
|
|
||||||
|
**Changed:**
|
||||||
|
|
||||||
|
* ``xonsh.prompt.gitstatus.gitstatus`` now returns a namedtuple
|
||||||
|
|
||||||
|
* implementation of ``xonsh.prompt.vc_branch.get_git_branch`` and
|
||||||
|
``xonsh.prompt.vc_branch.git_dirty_working_directory`` to be faster
|
||||||
|
|
||||||
|
**Deprecated:** None
|
||||||
|
|
||||||
|
**Removed:** None
|
||||||
|
|
||||||
|
**Fixed:** None
|
||||||
|
|
||||||
|
**Security:** None
|
|
@ -24,7 +24,9 @@ from xonsh.prompt.vc_branch import (
|
||||||
from xonsh.prompt.gitstatus import gitstatus_prompt
|
from xonsh.prompt.gitstatus import gitstatus_prompt
|
||||||
|
|
||||||
|
|
||||||
FORMATTER_DICT = xl.LazyObject(lambda: dict(
|
@xl.lazyobject
|
||||||
|
def FORMATTER_DICT():
|
||||||
|
return dict(
|
||||||
user=os.environ.get('USERNAME' if xp.ON_WINDOWS else 'USER', '<user>'),
|
user=os.environ.get('USERNAME' if xp.ON_WINDOWS else 'USER', '<user>'),
|
||||||
prompt_end='#' if xt.is_superuser() else '$',
|
prompt_end='#' if xt.is_superuser() else '$',
|
||||||
hostname=socket.gethostname().split('.', 1)[0],
|
hostname=socket.gethostname().split('.', 1)[0],
|
||||||
|
@ -39,7 +41,7 @@ FORMATTER_DICT = xl.LazyObject(lambda: dict(
|
||||||
env_name=env_name,
|
env_name=env_name,
|
||||||
vte_new_tab_cwd=vte_new_tab_cwd,
|
vte_new_tab_cwd=vte_new_tab_cwd,
|
||||||
gitstatus=gitstatus_prompt,
|
gitstatus=gitstatus_prompt,
|
||||||
), globals(), 'FORMATTER_DICT')
|
)
|
||||||
|
|
||||||
|
|
||||||
@xl.lazyobject
|
@xl.lazyobject
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Informative git status prompt formatter"""
|
"""Informative git status prompt formatter"""
|
||||||
|
|
||||||
import os
|
|
||||||
import builtins
|
import builtins
|
||||||
|
import collections
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import xonsh.lazyasd as xl
|
import xonsh.lazyasd as xl
|
||||||
|
|
||||||
|
|
||||||
|
GitStatus = collections.namedtuple('GitStatus',
|
||||||
|
['branch', 'num_ahead', 'num_behind',
|
||||||
|
'untracked', 'changed', 'conflicts',
|
||||||
|
'staged', 'stashed', 'operations'])
|
||||||
|
|
||||||
def _check_output(*args, **kwargs):
|
def _check_output(*args, **kwargs):
|
||||||
kwargs.update(dict(env=builtins.__xonsh_env__.detype(),
|
kwargs.update(dict(env=builtins.__xonsh_env__.detype(),
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
|
@ -70,9 +76,10 @@ def _gitoperation(gitdir):
|
||||||
|
|
||||||
|
|
||||||
def gitstatus():
|
def gitstatus():
|
||||||
"""Return (branch name, number of ahead commit, number of behind commit,
|
"""Return namedtuple with fields:
|
||||||
|
branch name, number of ahead commit, number of behind commit,
|
||||||
untracked number, changed number, conflicts number,
|
untracked number, changed number, conflicts number,
|
||||||
staged number, stashed number, operation)"""
|
staged number, stashed number, operation."""
|
||||||
status = _check_output(['git', 'status', '--porcelain', '--branch'])
|
status = _check_output(['git', 'status', '--porcelain', '--branch'])
|
||||||
branch = ''
|
branch = ''
|
||||||
num_ahead, num_behind = 0, 0
|
num_ahead, num_behind = 0, 0
|
||||||
|
@ -111,7 +118,7 @@ def gitstatus():
|
||||||
stashed = _get_stash(gitdir)
|
stashed = _get_stash(gitdir)
|
||||||
operations = _gitoperation(gitdir)
|
operations = _gitoperation(gitdir)
|
||||||
|
|
||||||
return (branch, num_ahead, num_behind,
|
return GitStatus(branch, num_ahead, num_behind,
|
||||||
untracked, changed, conflicts, staged, stashed,
|
untracked, changed, conflicts, staged, stashed,
|
||||||
operations)
|
operations)
|
||||||
|
|
||||||
|
@ -119,31 +126,29 @@ def gitstatus():
|
||||||
def gitstatus_prompt():
|
def gitstatus_prompt():
|
||||||
"""Return str `BRANCH|OPERATOR|numbers`"""
|
"""Return str `BRANCH|OPERATOR|numbers`"""
|
||||||
try:
|
try:
|
||||||
(branch, num_ahead, num_behind,
|
s = gitstatus()
|
||||||
untracked, changed, conflicts, staged, stashed,
|
|
||||||
operations) = gitstatus()
|
|
||||||
except subprocess.SubprocessError:
|
except subprocess.SubprocessError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ret = _get_def('BRANCH') + branch
|
ret = _get_def('BRANCH') + s.branch
|
||||||
if num_ahead > 0:
|
if s.num_ahead > 0:
|
||||||
ret += _get_def('AHEAD') + str(num_ahead)
|
ret += _get_def('AHEAD') + str(s.num_ahead)
|
||||||
if num_behind > 0:
|
if s.num_behind > 0:
|
||||||
ret += _get_def('BEHIND') + str(num_behind)
|
ret += _get_def('BEHIND') + str(s.num_behind)
|
||||||
if operations:
|
if s.operations:
|
||||||
ret += _get_def('OPERATION') + '|' + '|'.join(operations)
|
ret += _get_def('OPERATION') + '|' + '|'.join(s.operations)
|
||||||
ret += '|'
|
ret += '|'
|
||||||
if staged > 0:
|
if s.staged > 0:
|
||||||
ret += _get_def('STAGED') + str(staged) + '{NO_COLOR}'
|
ret += _get_def('STAGED') + str(s.staged) + '{NO_COLOR}'
|
||||||
if conflicts > 0:
|
if s.conflicts > 0:
|
||||||
ret += _get_def('CONFLICTS') + str(conflicts) + '{NO_COLOR}'
|
ret += _get_def('CONFLICTS') + str(s.conflicts) + '{NO_COLOR}'
|
||||||
if changed > 0:
|
if s.changed > 0:
|
||||||
ret += _get_def('CHANGED') + str(changed) + '{NO_COLOR}'
|
ret += _get_def('CHANGED') + str(s.changed) + '{NO_COLOR}'
|
||||||
if untracked > 0:
|
if s.untracked > 0:
|
||||||
ret += _get_def('UNTRACKED') + str(untracked) + '{NO_COLOR}'
|
ret += _get_def('UNTRACKED') + str(s.untracked) + '{NO_COLOR}'
|
||||||
if stashed > 0:
|
if s.stashed > 0:
|
||||||
ret += _get_def('STASHED') + str(stashed) + '{NO_COLOR}'
|
ret += _get_def('STASHED') + str(s.stashed) + '{NO_COLOR}'
|
||||||
if staged + conflicts + changed + untracked + stashed == 0:
|
if s.staged + s.conflicts + s.changed + s.untracked + s.stashed == 0:
|
||||||
ret += _get_def('CLEAN') + '{NO_COLOR}'
|
ret += _get_def('CLEAN') + '{NO_COLOR}'
|
||||||
ret += '{NO_COLOR}'
|
ret += '{NO_COLOR}'
|
||||||
|
|
||||||
|
|
|
@ -9,48 +9,39 @@ import warnings
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import xonsh.platform as xp
|
import xonsh.platform as xp
|
||||||
|
import xonsh.prompt
|
||||||
|
|
||||||
|
import threading, queue
|
||||||
|
|
||||||
|
|
||||||
|
def _get_git_branch(q):
|
||||||
|
try:
|
||||||
|
status = subprocess.check_output(['git', 'status'],
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
except (subprocess.CalledProcessError, OSError):
|
||||||
|
q.put(None)
|
||||||
|
else:
|
||||||
|
status = status.decode().split()
|
||||||
|
if status[2] == 'at':
|
||||||
|
q.put(status[3])
|
||||||
|
else:
|
||||||
|
q.put(status[2])
|
||||||
|
|
||||||
|
|
||||||
def get_git_branch():
|
def get_git_branch():
|
||||||
"""Attempts to find the current git branch. If no branch is found, then
|
"""Attempts to find the current git branch. If this could not
|
||||||
an empty string is returned. If a timeout occured, the timeout exception
|
be determined (timeout, not in a git repo, etc.) then this returns None.
|
||||||
(subprocess.TimeoutExpired) is returned.
|
|
||||||
"""
|
"""
|
||||||
branch = None
|
branch = None
|
||||||
env = builtins.__xonsh_env__
|
timeout = builtins.__xonsh_env__.get('VC_BRANCH_TIMEOUT')
|
||||||
cwd = env['PWD']
|
q = queue.Queue()
|
||||||
denv = env.detype()
|
|
||||||
vcbt = env['VC_BRANCH_TIMEOUT']
|
t = threading.Thread(target=_get_git_branch, args=(q,))
|
||||||
if not xp.ON_WINDOWS:
|
t.start()
|
||||||
prompt_scripts = ['/usr/lib/git-core/git-sh-prompt',
|
t.join(timeout=timeout)
|
||||||
'/usr/local/etc/bash_completion.d/git-prompt.sh']
|
|
||||||
for script in prompt_scripts:
|
|
||||||
# note that this is about 10x faster than bash -i "__git_ps1"
|
|
||||||
inp = 'source {}; __git_ps1 "${{1:-%s}}"'.format(script)
|
|
||||||
try:
|
try:
|
||||||
branch = subprocess.check_output(['bash'], cwd=cwd, input=inp,
|
branch = q.get_nowait()
|
||||||
stderr=subprocess.PIPE, timeout=vcbt, env=denv,
|
except queue.Empty:
|
||||||
universal_newlines=True)
|
|
||||||
break
|
|
||||||
except subprocess.TimeoutExpired as e:
|
|
||||||
branch = e
|
|
||||||
break
|
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
||||||
continue
|
|
||||||
# fall back to using the git binary if the above failed
|
|
||||||
if branch is None:
|
|
||||||
cmd = ['git', 'rev-parse', '--abbrev-ref', 'HEAD']
|
|
||||||
try:
|
|
||||||
s = subprocess.check_output(cmd, cwd=cwd, timeout=vcbt, env=denv,
|
|
||||||
stderr=subprocess.PIPE, universal_newlines=True)
|
|
||||||
if xp.ON_WINDOWS and len(s) == 0:
|
|
||||||
# Workaround for a bug in ConEMU/cmder, retry without redirection
|
|
||||||
s = subprocess.check_output(cmd, cwd=cwd, timeout=vcbt,
|
|
||||||
env=denv, universal_newlines=True)
|
|
||||||
branch = s.strip()
|
|
||||||
except subprocess.TimeoutExpired as e:
|
|
||||||
branch = e
|
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
||||||
branch = None
|
branch = None
|
||||||
return branch
|
return branch
|
||||||
|
|
||||||
|
@ -137,30 +128,32 @@ def current_branch(pad=NotImplemented):
|
||||||
return branch or None
|
return branch or None
|
||||||
|
|
||||||
|
|
||||||
def git_dirty_working_directory(cwd=None, include_untracked=False):
|
def _git_dirty_working_directory(q):
|
||||||
|
status = None
|
||||||
|
try:
|
||||||
|
status = subprocess.check_output(['git', 'status'],
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
except (subprocess.CalledProcessError, OSError):
|
||||||
|
q.put(None)
|
||||||
|
if status is not None:
|
||||||
|
if b'nothing to commit' in status:
|
||||||
|
return q.put(False)
|
||||||
|
else:
|
||||||
|
return q.put(True)
|
||||||
|
|
||||||
|
|
||||||
|
def git_dirty_working_directory():
|
||||||
"""Returns whether or not the git directory is dirty. If this could not
|
"""Returns whether or not the git directory is dirty. If this could not
|
||||||
be determined (timeout, file not sound, etc.) then this returns None.
|
be determined (timeout, file not sound, etc.) then this returns None.
|
||||||
"""
|
"""
|
||||||
cmd = ['git', 'status', '--porcelain']
|
timeout = builtins.__xonsh_env__.get("VC_BRANCH_TIMEOUT")
|
||||||
if include_untracked:
|
q = queue.Queue()
|
||||||
cmd.append('--untracked-files=normal')
|
t = threading.Thread(target=_git_dirty_working_directory, args=(q,))
|
||||||
else:
|
t.start()
|
||||||
cmd.append('--untracked-files=no')
|
t.join(timeout=timeout)
|
||||||
env = builtins.__xonsh_env__
|
|
||||||
cwd = env['PWD']
|
|
||||||
denv = env.detype()
|
|
||||||
vcbt = env['VC_BRANCH_TIMEOUT']
|
|
||||||
try:
|
try:
|
||||||
s = subprocess.check_output(cmd, stderr=subprocess.PIPE, cwd=cwd,
|
return q.get_nowait()
|
||||||
timeout=vcbt, universal_newlines=True,
|
except queue.Empty:
|
||||||
env=denv)
|
|
||||||
if xp.ON_WINDOWS and len(s) == 0:
|
|
||||||
# Workaround for a bug in ConEMU/cmder, retry without redirection
|
|
||||||
s = subprocess.check_output(cmd, cwd=cwd, timeout=vcbt,
|
|
||||||
env=denv, universal_newlines=True)
|
|
||||||
return bool(s)
|
|
||||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired,
|
|
||||||
FileNotFoundError):
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue