-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreformat-rebase.py
executable file
·107 lines (88 loc) · 3.78 KB
/
reformat-rebase.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/usr/bin/env python3
import argparse
import sys
import subprocess
def parse_args(program_name):
parser = argparse.ArgumentParser(
# we need this for line breaks
formatter_class=argparse.RawDescriptionHelpFormatter,
description="todo: add description")
parser.add_argument('--old-base', type=str, help='Old git base commit.')
parser.add_argument('--new-branch', type=str, help="New branch name"),
parser.add_argument('--reformat',
type=str,
nargs='+',
help='External tool called via `subprocess.call()`.')
return parser.parse_args()
class GuardSyscall:
def getoutput(command):
status, output = subprocess.getstatusoutput(command)
if status != 0:
raise Exception("Command '{}' failed with status {}.".format(
command, status))
return output
def call(*args):
status = subprocess.call([arg for arg in args])
if status != 0:
raise Exception("Command '{}' failed with status {}.".format(
[arg for arg in args], status))
class GitHelpers:
# both ends are inclusive, from is the old commit, to is the new one
# if from_inclusive=False, from commit is dropped
# oldest commit is returned first
def commits_from_to(from_: str, to: str = "HEAD", from_inclusive=True):
if from_inclusive:
caret = '^'
else:
caret = ''
command = "git rev-list {}{}..{}".format(from_, caret, to)
res = GuardSyscall.getoutput(command).splitlines()
res.reverse()
return res
def commit_description(commit: str) -> str:
command = "git log --format=%B -n 1 {}".format(commit)
return GuardSyscall.getoutput(command)
def steal_and_reapply_commit(commit: str, reformat):
checkout_command = ["git", "checkout", commit, "--", "."]
GuardSyscall.call(*checkout_command)
GuardSyscall.call(*reformat)
get_commit_message_command = "git log --format=%B -n 1 {}".format(
commit)
commit_message = GuardSyscall.getoutput(get_commit_message_command)
# this works for whitespaced commit messages too:
recommit_command = [
"git", "commit", ".", "--message={}".format(commit_message)
]
GuardSyscall.call(*recommit_command)
def reapply_commit_range(commits, reformat):
for commit in commits:
GitHelpers.steal_and_reapply_commit(commit, reformat)
def main(old_base, new_branch, reformat):
commit_history = GitHelpers.commits_from_to(old_base,
"HEAD",
from_inclusive=False)
checkout_command = ["git", "checkout", old_base]
GuardSyscall.call(*checkout_command)
new_branch_command = ["git", "checkout", "-b", new_branch]
GuardSyscall.call(*new_branch_command)
GuardSyscall.call(*reformat)
commit_command = [
"git", "commit", ".",
"--message=Temporary commit for reformat. Skip when rebasing."
]
GuardSyscall.call(*commit_command)
get_current_commit_call = "git rev-list HEAD^..HEAD"
temp_commit = GuardSyscall.getoutput(get_current_commit_call)
GitHelpers.reapply_commit_range(commit_history, reformat)
print("""
You can now rebase onto the commit that reformatted your code base:
> git rebase --onto <new_base> {}
Not doing that automatically as rebasing might fail and you need
to fix any issues manually.
""".format(temp_commit))
if __name__ == "__main__":
args = parse_args(sys.argv[0])
if not args.reformat or not args.old_base or not args.new_branch:
raise Exception("All arguments must be specified.")
main(args.old_base, args.new_branch, args.reformat)
sys.exit()