|
from pathlib import Path |
|
|
|
import argparse |
|
import os |
|
import subprocess |
|
import sys |
|
|
|
COMMIT_MSG = '!!hop_temp_commit!!' |
|
CLEAN_MSG = 'nothing to commit, working tree clean' |
|
|
|
SELF_CHECK = '_GIT_HOP_DISABLE' |
|
|
|
MSG_PREFIX = 'Git Hop:' |
|
|
|
# set this to false if you don't want git_hop to automatically embed a |
|
# post-checkout hook checking for the temp commit on switch/checkout |
|
EMBED_HOOK = True |
|
|
|
def main(): |
|
|
|
if os.environ.get(SELF_CHECK, False): |
|
return |
|
|
|
if EMBED_HOOK: |
|
_embed_hook() |
|
|
|
args = _parse_args() |
|
|
|
if args.check: |
|
check() |
|
elif args.pop: |
|
pop() |
|
else: |
|
hop(args.branch) |
|
|
|
|
|
def hop(branch: str): |
|
|
|
if not branch: |
|
_log('No branch specified!') |
|
return |
|
|
|
if not _is_clean(): |
|
_push_commit() |
|
|
|
_switch(branch) |
|
|
|
if _has_temp_commit(): |
|
_pop_commit() |
|
|
|
def check(): |
|
if _has_temp_commit(): |
|
_log('Temp hop commit found, but not popped! (pop with "git hop -p")') |
|
|
|
def pop(): |
|
if _has_temp_commit(): |
|
_pop_commit() |
|
else: |
|
_log('Temp hop commit not found', error = False) |
|
|
|
|
|
|
|
#region helpers |
|
|
|
def _push_commit(): |
|
|
|
_run(['git', 'add', '-A']) |
|
|
|
_run(['git', 'commit', '-m', COMMIT_MSG]) |
|
|
|
def _pop_commit(): |
|
|
|
_run(['git', 'reset', 'HEAD~1']) |
|
|
|
def _switch(branch: str): |
|
|
|
env = { |
|
**os.environ, |
|
SELF_CHECK: str(1) |
|
} |
|
|
|
_run(['git', 'switch', branch], env=env) |
|
|
|
def _has_temp_commit(): |
|
|
|
p = _run([ |
|
'git', 'log', |
|
'-n', '1', |
|
'--format=%B' |
|
], capture_output = True) |
|
|
|
msg = p.stdout.decode('utf-8').strip() |
|
|
|
return msg == COMMIT_MSG |
|
|
|
def _is_clean(): |
|
|
|
p = _run(['git', 'status'], capture_output = True) |
|
|
|
return CLEAN_MSG in str(p.stdout).strip() |
|
|
|
def _embed_hook(): |
|
|
|
cwd = Path(os.getcwd()).resolve() |
|
me = Path(__file__).resolve() |
|
|
|
hook_dir = cwd / '.git/hooks' |
|
|
|
# not a git repo |
|
if not hook_dir.exists(): |
|
return |
|
|
|
hook = hook_dir / 'post-checkout' |
|
|
|
if hook.exists(): |
|
|
|
# check for our script in existing hook |
|
if me.name not in hook.read_text(): |
|
_log(f'existing "post-checkout" hook found, but does not call "{me.name}"') |
|
|
|
# hook already injected previously |
|
else: |
|
return |
|
|
|
else: |
|
_log(f'"post-checkout" not found. Adding temp commit check hook') |
|
hook.write_text('\n'.join([ |
|
f'#!/bin/sh', |
|
f'python "{me}" -c' |
|
f'' |
|
])) |
|
|
|
#endregion |
|
|
|
#region utilities |
|
|
|
def _run(cmd: list | str, **kwargs) -> subprocess.CompletedProcess: |
|
|
|
cmd = ( |
|
cmd.split(' ') if isinstance(cmd, str) |
|
else [str(c) for c in cmd] |
|
) |
|
|
|
return subprocess.run(cmd, **kwargs) |
|
|
|
def _log(msg, error = True): |
|
file_ = sys.stderr if error else sys.stdout |
|
|
|
print(MSG_PREFIX, msg, file = file_) |
|
|
|
def _parse_args(): |
|
|
|
parser = argparse.ArgumentParser( |
|
description="Usage: add this script as an alias to git (eg. git hop develop)" |
|
) |
|
|
|
parser.add_argument('branch', nargs='?', default='', help = 'The branch to hop to') |
|
|
|
parser.add_argument('-c', '--check', action = 'store_true', help = 'Check if temp commit exists') |
|
parser.add_argument('-p', '--pop', action = 'store_true', help = 'Pop temp commit of current branch') |
|
|
|
return parser.parse_args() |
|
|
|
#endregion |
|
|
|
if __name__ == '__main__': |
|
main() |