-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcommit.py
executable file
·226 lines (183 loc) · 6.85 KB
/
commit.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
__author__ = "Eric L. Frederich"
import argparse
import hashlib
import itertools
import multiprocessing
import queue
import time
from datetime import datetime
from subprocess import PIPE, Popen
try:
import argcomplete
HAS_ARGCOMPLETE = True
except ImportError:
HAS_ARGCOMPLETE = False
def finder(results_queue, stats_queue, stop_queue, start_time, start_lines, step, template, start, hsh):
commit_time = start_time
found = False
tries = 0
before = datetime.now()
content = template % {"TIME": commit_time}
# print('trying with time', commit_time)
for padding in white_noise_generator(start_lines, step):
# keep track of and print tries
tries += 1
if tries % 100000 == 0:
try:
stop_queue.get_nowait()
except queue.Empty:
pass
else:
print("got the stop signal")
stats_queue.put(tries)
# if tries % 10000 == 0:
# print tries, 'tries', '(%d%%)' % int(100.0 * tries / 16**len(hsh))
# calculate sha
header = "commit %d\0" % len(content + padding)
store = header + content + padding
h = hashlib.sha1()
h.update(store.encode("utf-8"))
sha = h.hexdigest()
# break if we found one that ends with the desired hash
if (start and sha.startswith(hsh)) or (not start and sha.endswith(hsh)):
found = True
break
after = datetime.now()
results_queue.put((after - before, sha, store, commit_time, content + padding))
stats_queue.put(tries)
class RunCommandError(Exception):
def __init__(self, cmd, out, err, ret):
self.cmd = cmd
self.out = out
self.err = err
self.ret = ret
def run_command(cmd, stdin=None, allowed_exit_codes=[0]):
"""
wrapper around subprocess.Popen
returns stdout
"""
if stdin:
p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
p.stdin.write(stdin.encode())
else:
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
print("?" * 80)
out, err = p.communicate()
ret = p.wait()
if allowed_exit_codes is not None and ret not in allowed_exit_codes:
print("--- return code:", ret)
for line in out.splitlines():
print("--- out:", line)
for line in err.splitlines():
print("--- err:", line)
raise RunCommandError(cmd, out, err, ret)
return out.decode()
def white_noise_generator(start, step, width=80):
for n_padding_lines in itertools.count(start, step):
print(start, "is now using", n_padding_lines, "lines")
for padding in itertools.product(*[range(width) for _ in range(n_padding_lines)]):
ret = ""
for n in padding:
ret += "\n" + (" " * n)
ret += "\n"
yield ret
def commit(git_dir, add, hsh, msg, n_procs, start_time, amend, start):
print("creating commit for")
print(" ", git_dir)
print(" ", hsh)
print(" ", msg)
git_cmd = ["git"]
if git_dir is not None:
git_cmd.extend(["--git-dir", git_dir])
# gather username and email
username = run_command(git_cmd + ["config", "user.name"]).rstrip()
email = run_command(git_cmd + ["config", "user.email"]).rstrip()
if add:
run_command(git_cmd + ["add", "."])
tree_hash = run_command(git_cmd + ["write-tree"]).rstrip()
# TODO: could we support amend by parsing 'HEAD^' instead of 'HEAD'?
try:
if amend:
parent_hash = run_command(git_cmd + ["rev-parse", "HEAD^"]).strip()
else:
parent_hash = run_command(git_cmd + ["rev-parse", "HEAD"]).strip()
except RunCommandError as rce:
parent_hash = None
print(f"username {username}")
print(f"email {email}")
print(f"tree hash {tree_hash}")
print(f"parent hash {parent_hash}")
# a partially applied template; leave %(TIME)s in there.
template = ""
template += "tree %s\n" % tree_hash
if parent_hash is not None:
template += "parent %s\n" % parent_hash
template += "author %s <%s> %s -0600\n" % (username, email, "%(TIME)s")
template += "committer %s <%s> %s -0600\n" % (username, email, "%(TIME)s")
template += "\n"
template += "%s\n" % msg
# create some queues for communication
results_queue = multiprocessing.Queue()
stats_queue = multiprocessing.Queue()
stop_queue = multiprocessing.Queue()
# create all the processes using an offset for the start time so they're unique
procs = []
for i in range(n_procs):
proc = multiprocessing.Process(
target=finder, args=(results_queue, stats_queue, stop_queue, start_time, i, n_procs, template, start, hsh)
)
procs.append(proc)
# start all processes
for proc in procs:
proc.start()
# first thing back on the results queue will alaways be the result
runtime, sha, store, commit_time, content = results_queue.get()
# signal all the other processes that we're done
for i in range(n_procs):
stop_queue.put(None)
# collect stats from each process
# do a blocking call because we know how many processes there were
tries = 0
for i in range(n_procs):
tries += stats_queue.get()
# kill all the processes though they should be done already
for proc in procs:
proc.terminate()
print("*" * 80)
print(sha)
print(repr(store))
print("*" * 80)
print("elapsed:", runtime)
print("tries :", tries)
print("%r tries per second" % (tries / runtime.total_seconds()))
print("had to increment commit time by", commit_time - start_time, "seconds")
commit_hash = run_command(git_cmd + ["hash-object", "-t", "commit", "-w", "--stdin"], stdin=content).strip()
if commit_hash == sha:
print("WOO HOO")
else:
raise RuntimeError("unexpected hash")
run_command(git_cmd + ["update-ref", "HEAD", sha])
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--add", "-a", action="store_true")
parser.add_argument("--amend", action="store_true")
parser.add_argument("--message", "-m", required=True)
parser.add_argument("--git-dir")
parser.add_argument("--start", action="store_true")
parser.add_argument("--parallel", type=int, default=1)
parser.add_argument("--time", type=int, default=407891580)
parser.add_argument("hash")
if HAS_ARGCOMPLETE:
argcomplete.autocomplete(parser)
args = parser.parse_args()
# if len(args.hash) > 4:
# raise ValueError('hash too big, only 4 supported')
try:
int(args.hash, 16)
except ValueError:
raise ValueError("Invalid hex for hash")
commit(args.git_dir, args.add, args.hash.lower(), args.message, args.parallel, args.time, args.amend, args.start)
if __name__ == "__main__":
main()