Skip to content

Instantly share code, notes, and snippets.

@ganwell
Last active August 15, 2019 10:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ganwell/b1007add1400935a89b0d1eb239b9f3d to your computer and use it in GitHub Desktop.
Save ganwell/b1007add1400935a89b0d1eb239b9f3d to your computer and use it in GitHub Desktop.
Bisect get merge/rebase (seems safe, but use at your own risk)
#!/usr/bin/env python3
import bisect
import functools
import shlex
import subprocess as s
import sys
devel = False
tokens = """
git
merge-base
rev-parse
HEAD
rev-list
--ancestry-path
-b
--abort
symbolic-ref
--short
--no-commit
stash
pop
--get
config
rebase.autostash
"""
help = """
Seems safe, but use at your own risk.
Brase will try merge/rebase a branch as far as possible using bisect.
Please specify which branch you want to merge/rebase.
git brase <merge|rebase> '<branch>' [-n]
--exec execute the merge/rebase found
""".strip()
class Tokens:
def __init__(self):
token_list = shlex.split(tokens)
token_dict = {}
for token in token_list:
name = token.lstrip("-")
name = name.replace("-", "_")
name = name.replace(".", "_")
token_dict[name] = token
assert len(token_dict.keys()) == len(token_list)
for name in token_dict.keys():
setattr(self, name, token_dict[name])
t = Tokens()
def git(*args):
try:
return s.check_output((t.git,) + args, stderr=s.STDOUT).strip().decode("UTF-8")
except s.CalledProcessError as e:
if devel:
print(get_output(e))
raise
def igit(*args):
try:
return git(*args)
except s.CalledProcessError as e:
if devel:
print(get_output(e))
import traceback
traceback.print_exc()
def get_output(e):
return e.output.decode("UTF-8")
def execute(target, index):
ret = False
try:
git(t.command, t.no_commit, target[index])
ret = True
except s.CalledProcessError:
pass
finally:
igit(t.command, t.abort)
short = target[index][:8]
if ret:
text = "sucessful"
else:
text = "failed"
print(f"{t.command} index({index}) rev({short}) {text}")
return ret
def brase(target, base):
max_index = len(target) - 1
driver = functools.partial(execute, target)
class BisectTarget(object):
def __getitem__(self, index):
ret = driver(index)
return ret
def __len__(self):
return max_index
if driver(0):
return 0
else:
bt = BisectTarget()
return bisect.bisect(bt, False)
def main():
head = git(t.rev_parse, t.HEAD)
head_name = git(t.symbolic_ref, t.short, t.HEAD)
dry = True
len_argv = len(sys.argv)
if len_argv == 4:
if sys.argv[3] != "--exec":
print(help)
sys.exit(1)
else:
dry = False
elif len_argv < 3:
print(help)
sys.exit(1)
elif sys.argv[1] not in ("merge", "rebase"):
print(help)
sys.exit(1)
t.command = sys.argv[1]
target_name = sys.argv[2]
target = git(t.rev_parse, target_name)
assert target != head
base = git(t.merge_base, head, target)
path = git(t.rev_list, t.ancestry_path, f"{base}..{target}").split("\n")
print(f"found {len(path)} commits to {t.command}")
# _path_parent = git(t.rev_parse, f"{path[-1]}^")
assert path[0] == target
# assert _path_parent == base
autostash = git(t.config, t.get, t.rebase_autostash).lower() == "true"
stashed = False
if autostash and t.command == "rebase":
try:
if not git(t.stash):
stashed = True
except s.CalledProcessError:
pass
try:
index = brase(path, base)
if dry:
sys.stdout.write("-> ")
execute(path, index)
finally:
igit(t.command, t.abort)
if stashed:
git(t.stash, t.pop)
if not dry:
try:
git(t.command, path[index])
except s.CalledProcessError as e:
print(get_output(e))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment