cmd/gitcheckup.py

150 lines
4.5 KiB
Python

import argparse
import os
import subprocess
import sys
from voussoirkit import winwhich
GIT = winwhich.which('git')
# https://git-scm.com/docs/git-status#_short_format
# Here is an example of typical `git status --short` output:
#
# M file1
# D file2
# A file4
# ?? file3
def checkup_committed():
command = [GIT, 'status', '--short', '--untracked-files=all']
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
added = 0
modified = 0
deleted = 0
for line in output.splitlines():
status = line.split()[0].strip().decode('ascii')
# These are ifs instead of elifs because you might have a file that is
# added in the index but deleted on disk, etc. Anyway these numbers
# don't need to be super accurate, just enough to remind you to commit.
if {'A', '?'}.intersection(status):
added += 1
if {'M', 'R', '!'}.intersection(status):
modified += 1
if {'D'}.intersection(status):
deleted += 1
if (added, modified, deleted) == (0, 0, 0):
committed = True
details = ''
else:
committed = False
details = []
if added: details.append(f'+{added}')
if deleted: details.append(f'-{deleted}')
if modified: details.append(f'~{modified}')
details = ', '.join(details)
details = f'({details})'
return (committed, details)
def checkup_pushed():
command = [GIT, 'rev-parse', '@', '@{u}']
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
(my_head, remote_head) = output.strip().decode().splitlines()
command = [GIT, 'merge-base', '@', '@{u}']
merge_base = subprocess.check_output(command, stderr=subprocess.STDOUT)
merge_base = merge_base.strip().decode()
if my_head == remote_head:
to_push = 0
to_pull = 0
else:
to_push = len(git_commits_between(merge_base, my_head))
to_pull = len(git_commits_between(merge_base, remote_head))
if (to_push, to_pull) == (0, 0):
pushed = True
details = ''
else:
pushed = False
details = []
if to_push: details.append(f'{to_push}')
if to_pull: details.append(f'{to_pull}')
details = ', '.join(details)
details = f'({details})'
return (pushed, details)
def git_commits_between(a, b):
command = [GIT, 'log', '--oneline', f'{a}..{b}']
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
lines = output.strip().decode().splitlines()
return lines
def git_fetch():
command = [GIT, 'fetch', '--all']
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
def checkup(directory, do_fetch=False):
os.chdir(directory)
if do_fetch:
git_fetch()
(committed, commit_details) = checkup_committed()
(pushed, push_details) = checkup_pushed()
return {
'committed': committed,
'commit_details': commit_details,
'pushed': pushed,
'push_details': push_details,
}
def read_directories_file():
directories_file = os.path.join(os.path.dirname(__file__), 'gitcheckup.txt')
try:
handle = open(directories_file, 'r')
except FileNotFoundError:
print(f'Please put your git repo locations in {directories_file}.')
return 1
directories = handle.readlines()
handle.close()
directories = [line.strip() for line in directories]
directories = [line for line in directories if line]
return directories
def gitcheckup(do_fetch=False):
directories = read_directories_file()
for directory in directories:
result = checkup(directory, do_fetch=do_fetch)
committed = 'C' if result['committed'] else ' '
pushed = 'P' if result['pushed'] else ' '
details = []
if result['commit_details']:
details.append(result['commit_details'])
if result['push_details']:
details.append(result['push_details'])
details = ' '.join(details)
details = (' ' + details).rstrip()
print(f'[{committed}][{pushed}] {directory}{details}')
def gitcheckup_argparse(args):
return gitcheckup(do_fetch=args.do_fetch)
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--fetch', dest='do_fetch', action='store_true')
parser.set_defaults(func=gitcheckup_argparse)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))