From 8fd7f9bfed753dbaa5543747569b4c2543aff03d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 17 Jul 2021 09:26:34 +0200 Subject: Merging upstream version 0.15.1. Signed-off-by: Daniel Baumann --- gita/info.py | 103 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 39 deletions(-) (limited to 'gita/info.py') diff --git a/gita/info.py b/gita/info.py index a8044e9..d18a097 100644 --- a/gita/info.py +++ b/gita/info.py @@ -1,5 +1,5 @@ import os -import sys +import csv import yaml import subprocess from enum import Enum @@ -31,40 +31,42 @@ class Color(str, Enum): b_purple = '\x1b[35;1m' b_cyan = '\x1b[36;1m' b_white = '\x1b[37;1m' + underline = '\x1B[4m' def show_colors(): # pragma: no cover """ """ - names = {c.value: c.name for c in Color} for i, c in enumerate(Color, start=1): - if c != Color.end: + if c != Color.end and c != Color.underline: print(f'{c.value}{c.name:<8} ', end='') if i % 9 == 0: print() print(f'{Color.end}') for situation, c in sorted(get_color_encoding().items()): - print(f'{situation:<12}: {c}{names[c]:<8}{Color.end} ') + print(f'{situation:<12}: {Color[c].value}{c:<8}{Color.end} ') @lru_cache() def get_color_encoding() -> Dict[str, str]: """ Return color scheme for different local/remote situations. + In the format of {situation: color name} """ # custom settings - yml_config = Path(common.get_config_fname('color.yml')) - if yml_config.is_file(): - with open(yml_config, 'r') as stream: - colors = yaml.load(stream, Loader=yaml.FullLoader) + csv_config = Path(common.get_config_fname('color.csv')) + if csv_config.is_file(): + with open(csv_config, 'r') as f: + reader = csv.DictReader(f) + colors = next(reader) else: colors = { - 'no-remote': Color.white.value, - 'in-sync': Color.green.value, - 'diverged': Color.red.value, - 'local-ahead': Color.purple.value, - 'remote-ahead': Color.yellow.value, + 'no-remote': Color.white.name, + 'in-sync': Color.green.name, + 'diverged': Color.red.name, + 'local-ahead': Color.purple.name, + 'remote-ahead': Color.yellow.name, } return colors @@ -80,6 +82,7 @@ def get_info_funcs() -> List[Callable[[str], str]]: all_info_items = { 'branch': get_repo_status, 'commit_msg': get_commit_msg, + 'commit_time': get_commit_time, 'path': get_path, } return [all_info_items[k] for k in to_display] @@ -90,23 +93,26 @@ def get_info_items() -> List[str]: Return the information items to be displayed in the `gita ll` command. """ # custom settings - yml_config = Path(common.get_config_fname('info.yml')) - if yml_config.is_file(): - with open(yml_config, 'r') as stream: - display_items = yaml.load(stream, Loader=yaml.FullLoader) + csv_config = Path(common.get_config_fname('info.csv')) + if csv_config.is_file(): + with open(csv_config, 'r') as f: + reader = csv.reader(f) + display_items = next(reader) display_items = [x for x in display_items if x in ALL_INFO_ITEMS] else: # default settings - display_items = ['branch', 'commit_msg'] + display_items = ['branch', 'commit_msg', 'commit_time'] return display_items -def get_path(path): - return f'{Color.cyan}{path}{Color.end}' +def get_path(prop: Dict[str, str]) -> str: + return f'{Color.cyan}{prop["path"]}{Color.end}' +# TODO: do we need to add the flags here too? def get_head(path: str) -> str: - result = subprocess.run('git rev-parse --abbrev-ref HEAD'.split(), + result = subprocess.run('git symbolic-ref -q --short HEAD || git describe --tags --exact-match', + shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True, @@ -114,12 +120,12 @@ def get_head(path: str) -> str: return result.stdout.strip() -def run_quiet_diff(args: List[str]) -> bool: +def run_quiet_diff(flags: List[str], args: List[str]) -> int: """ Return the return code of git diff `args` in quiet mode """ result = subprocess.run( - ['git', 'diff', '--quiet'] + args, + ['git'] + flags + ['diff', '--quiet'] + args, stderr=subprocess.DEVNULL, ) return result.returncode @@ -135,50 +141,68 @@ def get_common_commit() -> str: return result.stdout.strip() -def has_untracked() -> bool: +def has_untracked(flags: List[str]) -> bool: """ Return True if untracked file/folder exists """ - result = subprocess.run('git ls-files -zo --exclude-standard'.split(), + cmd = ['git'] + flags + 'ls-files -zo --exclude-standard'.split() + result = subprocess.run(cmd, stdout=subprocess.PIPE) return bool(result.stdout) -def get_commit_msg(path: str) -> str: +def get_commit_msg(prop: Dict[str, str]) -> str: """ Return the last commit message. """ # `git show-branch --no-name HEAD` is faster than `git show -s --format=%s` - result = subprocess.run('git show-branch --no-name HEAD'.split(), + cmd = ['git'] + prop['flags'] + 'show-branch --no-name HEAD'.split() + result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True, - cwd=path) + cwd=prop['path']) return result.stdout.strip() -def get_repo_status(path: str, no_colors=False) -> str: - head = get_head(path) - dirty, staged, untracked, color = _get_repo_status(path, no_colors) +def get_commit_time(prop: Dict[str, str]) -> str: + """ + Return the last commit time in parenthesis. + """ + cmd = ['git'] + prop['flags'] + 'log -1 --format=%cd --date=relative'.split() + result = subprocess.run(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + universal_newlines=True, + cwd=prop['path']) + return f"({result.stdout.strip()})" + + +def get_repo_status(prop: Dict[str, str], no_colors=False) -> str: + head = get_head(prop['path']) + dirty, staged, untracked, color = _get_repo_status(prop, no_colors) if color: return f'{color}{head+" "+dirty+staged+untracked:<10}{Color.end}' return f'{head+" "+dirty+staged+untracked:<10}' -def _get_repo_status(path: str, no_colors: bool) -> Tuple[str]: +def _get_repo_status(prop: Dict[str, str], no_colors: bool) -> Tuple[str]: """ Return the status of one repo """ + path = prop['path'] + flags = prop['flags'] os.chdir(path) - dirty = '*' if run_quiet_diff([]) else '' - staged = '+' if run_quiet_diff(['--cached']) else '' - untracked = '_' if has_untracked() else '' + dirty = '*' if run_quiet_diff(flags, []) else '' + staged = '+' if run_quiet_diff(flags, ['--cached']) else '' + untracked = '_' if has_untracked(flags) else '' if no_colors: return dirty, staged, untracked, '' - colors = get_color_encoding() - diff_returncode = run_quiet_diff(['@{u}', '@{0}']) + colors = {situ: Color[name].value + for situ, name in get_color_encoding().items()} + diff_returncode = run_quiet_diff(flags, ['@{u}', '@{0}']) has_no_remote = diff_returncode == 128 has_no_diff = diff_returncode == 0 if has_no_remote: @@ -187,9 +211,9 @@ def _get_repo_status(path: str, no_colors: bool) -> Tuple[str]: color = colors['in-sync'] else: common_commit = get_common_commit() - outdated = run_quiet_diff(['@{u}', common_commit]) + outdated = run_quiet_diff(flags, ['@{u}', common_commit]) if outdated: - diverged = run_quiet_diff(['@{0}', common_commit]) + diverged = run_quiet_diff(flags, ['@{0}', common_commit]) color = colors['diverged'] if diverged else colors['remote-ahead'] else: # local is ahead of remote color = colors['local-ahead'] @@ -199,5 +223,6 @@ def _get_repo_status(path: str, no_colors: bool) -> Tuple[str]: ALL_INFO_ITEMS = { 'branch': get_repo_status, 'commit_msg': get_commit_msg, + 'commit_time': get_commit_time, 'path': get_path, } -- cgit v1.2.3