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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
|
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2020 by the Linux Foundation
#
__author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
import os
import sys
import b4
import b4.mbox
import mailbox
import shutil
import urllib.parse
from tempfile import mkstemp
logger = b4.logger
def make_fake_commit_range(gitdir, lser):
start_commit = end_commit = None
# Do we have it in cache already?
cachedir = b4.get_cache_dir()
# Use the msgid of the first non-None patch in the series
msgid = None
for lmsg in lser.patches:
if lmsg is not None:
msgid = lmsg.msgid
break
if msgid is None:
logger.critical('Cannot operate on an empty series')
return None, None
cachefile = os.path.join(cachedir, '%s.fakeam' % urllib.parse.quote_plus(msgid))
if os.path.exists(cachefile):
stalecache = False
with open(cachefile, 'r') as fh:
cachedata = fh.read()
chunks = cachedata.strip().split()
if len(chunks) == 2:
start_commit, end_commit = chunks
else:
stalecache = True
if start_commit is not None and end_commit is not None:
# Make sure they are still there
ecode, out = b4.git_run_command(gitdir, ['cat-file', '-e', start_commit])
if ecode > 0:
stalecache = True
else:
ecode, out = b4.git_run_command(gitdir, ['cat-file', '-e', end_commit])
if ecode > 0:
stalecache = True
else:
logger.debug('Using previously generated range')
return start_commit, end_commit
if stalecache:
logger.debug('Stale cache for [v%s] %s', lser.revision, lser.subject)
os.unlink(cachefile)
logger.info('Preparing fake-am for v%s: %s', lser.revision, lser.subject)
with b4.git_temp_worktree(gitdir):
# We are in a temporary chdir at this time, so writing to a known file should be safe
mbxf = '.__git-am__'
mbx = mailbox.mbox(mbxf)
# Logic largely borrowed from gj_tools
seenfiles = set()
for lmsg in lser.patches[1:]:
logger.debug('Looking at %s', lmsg.full_subject)
lmsg.load_hashes()
if not len(lmsg.blob_indexes):
logger.critical('ERROR: some patches do not have indexes')
logger.critical(' automatic range-diff would be misleading')
return None, None
for fn, fi in lmsg.blob_indexes:
if fn in seenfiles:
# We already processed this file, so this blob won't match
continue
seenfiles.add(fn)
if set(fi) == {'0'}:
# New file creation, nothing to do here
logger.debug(' New file: %s', fn)
continue
# Try to grab full ref_id of this hash
ecode, out = b4.git_run_command(gitdir, ['rev-parse', fi])
if ecode > 0:
logger.critical(' ERROR: Could not find matching blob for %s (%s)', fn, fi)
# TODO: better handling
return None, None
logger.debug(' Found matching blob for: %s', fn)
fullref = out.strip()
gitargs = ['update-index', '--add', '--cacheinfo', f'0644,{fullref},{fn}']
ecode, out = b4.git_run_command(None, gitargs)
if ecode > 0:
logger.critical(' ERROR: Could not run update-index for %s (%s)', fn, fullref)
return None, None
mbx.add(lmsg.msg.as_string(policy=b4.emlpolicy).encode('utf-8'))
mbx.close()
ecode, out = b4.git_run_command(None, ['write-tree'])
if ecode > 0:
logger.critical('ERROR: Could not write fake-am tree')
return None, None
treeid = out.strip()
# At this point we have a worktree with files that should cleanly receive a git am
gitargs = ['commit-tree', treeid + '^{tree}', '-F', '-']
ecode, out = b4.git_run_command(None, gitargs, stdin='Initial fake commit'.encode('utf-8'))
if ecode > 0:
logger.critical('ERROR: Could not commit-tree')
return None, None
start_commit = out.strip()
b4.git_run_command(None, ['reset', '--hard', start_commit])
ecode, out = b4.git_run_command(None, ['am', mbxf])
if ecode > 0:
logger.critical('ERROR: Could not fake-am version %s', lser.revision)
return None, None
ecode, out = b4.git_run_command(None, ['rev-parse', 'HEAD'])
end_commit = out.strip()
logger.info(' range: %.12s..%.12s', start_commit, end_commit)
with open(cachefile, 'w') as fh:
logger.debug('Saving into cache: %s', cachefile)
logger.debug(' %s..%s', start_commit, end_commit)
fh.write(f'{start_commit} {end_commit}\n')
return start_commit, end_commit
def diff_same_thread_series(cmdargs):
msgid = b4.get_msgid(cmdargs)
wantvers = cmdargs.wantvers
if wantvers and len(wantvers) > 2:
logger.critical('Can only compare two versions at a time')
sys.exit(1)
# start by grabbing the mbox provided
savefile = mkstemp('b4-diff-to')[1]
# Do we have a cache of this lookup?
cachedir = b4.get_cache_dir()
if wantvers:
cachefile = os.path.join(cachedir, '%s-%s.diff.mbx' % (urllib.parse.quote_plus(msgid),
'-'.join([str(x) for x in wantvers])))
else:
cachefile = os.path.join(cachedir, '%s-latest.diff.mbx' % urllib.parse.quote_plus(msgid))
if os.path.exists(cachefile) and not cmdargs.nocache:
logger.info('Using cached copy of the lookup')
shutil.copyfile(cachefile, savefile)
mboxfile = savefile
else:
mboxfile = b4.get_pi_thread_by_msgid(msgid, savefile, useproject=cmdargs.useproject, nocache=cmdargs.nocache)
if mboxfile is None:
logger.critical('Unable to retrieve thread: %s', msgid)
return
b4.mbox.get_extra_series(mboxfile, direction=-1, wantvers=wantvers)
shutil.copyfile(mboxfile, cachefile)
mbx = mailbox.mbox(mboxfile)
count = len(mbx)
logger.info('---')
logger.info('Analyzing %s messages in the thread', count)
lmbx = b4.LoreMailbox()
for key, msg in mbx.items():
lmbx.add_message(msg)
mbx.close()
os.unlink(mboxfile)
if wantvers and len(wantvers) == 1:
upper = max(lmbx.series.keys())
lower = wantvers[0]
elif wantvers and len(wantvers) == 2:
upper = max(wantvers)
lower = min(wantvers)
else:
upper = max(lmbx.series.keys())
lower = min(lmbx.series.keys())
if upper == lower:
logger.critical('Could not find previous revision')
return None, None
if upper not in lmbx.series:
return None, None
if lower not in lmbx.series:
return None, None
return lmbx.series[lower], lmbx.series[upper]
def diff_mboxes(cmdargs):
chunks = list()
for mboxfile in cmdargs.ambox:
if not os.path.exists(mboxfile):
logger.critical('Cannot open %s', mboxfile)
return None, None
mbx = mailbox.mbox(mboxfile)
count = len(mbx)
logger.info('Loading %s messages from %s', count, mboxfile)
lmbx = b4.LoreMailbox()
for key, msg in mbx.items():
lmbx.add_message(msg)
if len(lmbx.series) > 1:
logger.critical('More than one series version in %s, will use latest', mboxfile)
chunks.append(lmbx.series[max(lmbx.series.keys())])
return chunks
def main(cmdargs):
if cmdargs.ambox is not None:
lser, user = diff_mboxes(cmdargs)
else:
lser, user = diff_same_thread_series(cmdargs)
if lser is None or user is None:
sys.exit(1)
# Prepare the lower fake-am range
lsc, lec = make_fake_commit_range(cmdargs.gitdir, lser)
if lsc is None or lec is None:
logger.critical('---')
logger.critical('Could not create fake-am range for lower series v%s', lser.revision)
sys.exit(1)
# Prepare the upper fake-am range
usc, uec = make_fake_commit_range(cmdargs.gitdir, user)
if usc is None or uec is None:
logger.critical('---')
logger.critical('Could not create fake-am range for upper series v%s', user.revision)
sys.exit(1)
grdcmd = 'git range-diff %.12s..%.12s %.12s..%.12s' % (lsc, lec, usc, uec)
if cmdargs.nodiff:
logger.info('Success, to compare v%s and v%s:', lser.revision, user.revision)
logger.info(f' {grdcmd}')
sys.exit(0)
logger.info('Diffing v%s and v%s', lser.revision, user.revision)
logger.info(' Running: %s', grdcmd)
gitargs = ['range-diff', f'{lsc}..{lec}', f'{usc}..{uec}']
if cmdargs.outdiff is None or cmdargs.color:
gitargs.append('--color')
ecode, rdiff = b4.git_run_command(cmdargs.gitdir, gitargs)
if ecode > 0:
logger.critical('Unable to generate diff')
logger.critical('Try running it yourself:')
logger.critical(f' {grdcmd}')
sys.exit(1)
if cmdargs.outdiff is not None:
logger.info('Writing %s', cmdargs.outdiff)
fh = open(cmdargs.outdiff, 'w')
else:
logger.info('---')
fh = sys.stdout
fh.write(rdiff)
|