##// END OF EJS Templates
py3: use mail.parsebytes() in gnuarch catlog parser...
Denis Laxalde -
r43699:cf3bf3b0 stable
parent child Browse files
Show More
@@ -1,376 +1,375 b''
1 1 # gnuarch.py - GNU Arch support for the convert extension
2 2 #
3 3 # Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org>
4 4 # and others
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8 from __future__ import absolute_import
9 9
10 import email.parser as emailparser
11 10 import os
12 11 import shutil
13 12 import stat
14 13 import tempfile
15 14
16 15 from mercurial.i18n import _
17 16 from mercurial import (
18 17 encoding,
19 18 error,
19 mail,
20 20 pycompat,
21 21 util,
22 22 )
23 23 from mercurial.utils import (
24 24 dateutil,
25 25 procutil,
26 26 )
27 27 from . import common
28 28
29 29
30 30 class gnuarch_source(common.converter_source, common.commandline):
31 31 class gnuarch_rev(object):
32 32 def __init__(self, rev):
33 33 self.rev = rev
34 34 self.summary = b''
35 35 self.date = None
36 36 self.author = b''
37 37 self.continuationof = None
38 38 self.add_files = []
39 39 self.mod_files = []
40 40 self.del_files = []
41 41 self.ren_files = {}
42 42 self.ren_dirs = {}
43 43
44 44 def __init__(self, ui, repotype, path, revs=None):
45 45 super(gnuarch_source, self).__init__(ui, repotype, path, revs=revs)
46 46
47 47 if not os.path.exists(os.path.join(path, b'{arch}')):
48 48 raise common.NoRepo(
49 49 _(b"%s does not look like a GNU Arch repository") % path
50 50 )
51 51
52 52 # Could use checktool, but we want to check for baz or tla.
53 53 self.execmd = None
54 54 if procutil.findexe(b'baz'):
55 55 self.execmd = b'baz'
56 56 else:
57 57 if procutil.findexe(b'tla'):
58 58 self.execmd = b'tla'
59 59 else:
60 60 raise error.Abort(_(b'cannot find a GNU Arch tool'))
61 61
62 62 common.commandline.__init__(self, ui, self.execmd)
63 63
64 64 self.path = os.path.realpath(path)
65 65 self.tmppath = None
66 66
67 67 self.treeversion = None
68 68 self.lastrev = None
69 69 self.changes = {}
70 70 self.parents = {}
71 71 self.tags = {}
72 self.catlogparser = emailparser.Parser()
73 72 self.encoding = encoding.encoding
74 73 self.archives = []
75 74
76 75 def before(self):
77 76 # Get registered archives
78 77 self.archives = [
79 78 i.rstrip(b'\n') for i in self.runlines0(b'archives', b'-n')
80 79 ]
81 80
82 81 if self.execmd == b'tla':
83 82 output = self.run0(b'tree-version', self.path)
84 83 else:
85 84 output = self.run0(b'tree-version', b'-d', self.path)
86 85 self.treeversion = output.strip()
87 86
88 87 # Get name of temporary directory
89 88 version = self.treeversion.split(b'/')
90 89 self.tmppath = os.path.join(
91 90 pycompat.fsencode(tempfile.gettempdir()), b'hg-%s' % version[1]
92 91 )
93 92
94 93 # Generate parents dictionary
95 94 self.parents[None] = []
96 95 treeversion = self.treeversion
97 96 child = None
98 97 while treeversion:
99 98 self.ui.status(_(b'analyzing tree version %s...\n') % treeversion)
100 99
101 100 archive = treeversion.split(b'/')[0]
102 101 if archive not in self.archives:
103 102 self.ui.status(
104 103 _(
105 104 b'tree analysis stopped because it points to '
106 105 b'an unregistered archive %s...\n'
107 106 )
108 107 % archive
109 108 )
110 109 break
111 110
112 111 # Get the complete list of revisions for that tree version
113 112 output, status = self.runlines(
114 113 b'revisions', b'-r', b'-f', treeversion
115 114 )
116 115 self.checkexit(
117 116 status, b'failed retrieving revisions for %s' % treeversion
118 117 )
119 118
120 119 # No new iteration unless a revision has a continuation-of header
121 120 treeversion = None
122 121
123 122 for l in output:
124 123 rev = l.strip()
125 124 self.changes[rev] = self.gnuarch_rev(rev)
126 125 self.parents[rev] = []
127 126
128 127 # Read author, date and summary
129 128 catlog, status = self.run(b'cat-log', b'-d', self.path, rev)
130 129 if status:
131 130 catlog = self.run0(b'cat-archive-log', rev)
132 131 self._parsecatlog(catlog, rev)
133 132
134 133 # Populate the parents map
135 134 self.parents[child].append(rev)
136 135
137 136 # Keep track of the current revision as the child of the next
138 137 # revision scanned
139 138 child = rev
140 139
141 140 # Check if we have to follow the usual incremental history
142 141 # or if we have to 'jump' to a different treeversion given
143 142 # by the continuation-of header.
144 143 if self.changes[rev].continuationof:
145 144 treeversion = b'--'.join(
146 145 self.changes[rev].continuationof.split(b'--')[:-1]
147 146 )
148 147 break
149 148
150 149 # If we reached a base-0 revision w/o any continuation-of
151 150 # header, it means the tree history ends here.
152 151 if rev[-6:] == b'base-0':
153 152 break
154 153
155 154 def after(self):
156 155 self.ui.debug(b'cleaning up %s\n' % self.tmppath)
157 156 shutil.rmtree(self.tmppath, ignore_errors=True)
158 157
159 158 def getheads(self):
160 159 return self.parents[None]
161 160
162 161 def getfile(self, name, rev):
163 162 if rev != self.lastrev:
164 163 raise error.Abort(_(b'internal calling inconsistency'))
165 164
166 165 if not os.path.lexists(os.path.join(self.tmppath, name)):
167 166 return None, None
168 167
169 168 return self._getfile(name, rev)
170 169
171 170 def getchanges(self, rev, full):
172 171 if full:
173 172 raise error.Abort(_(b"convert from arch does not support --full"))
174 173 self._update(rev)
175 174 changes = []
176 175 copies = {}
177 176
178 177 for f in self.changes[rev].add_files:
179 178 changes.append((f, rev))
180 179
181 180 for f in self.changes[rev].mod_files:
182 181 changes.append((f, rev))
183 182
184 183 for f in self.changes[rev].del_files:
185 184 changes.append((f, rev))
186 185
187 186 for src in self.changes[rev].ren_files:
188 187 to = self.changes[rev].ren_files[src]
189 188 changes.append((src, rev))
190 189 changes.append((to, rev))
191 190 copies[to] = src
192 191
193 192 for src in self.changes[rev].ren_dirs:
194 193 to = self.changes[rev].ren_dirs[src]
195 194 chgs, cps = self._rendirchanges(src, to)
196 195 changes += [(f, rev) for f in chgs]
197 196 copies.update(cps)
198 197
199 198 self.lastrev = rev
200 199 return sorted(set(changes)), copies, set()
201 200
202 201 def getcommit(self, rev):
203 202 changes = self.changes[rev]
204 203 return common.commit(
205 204 author=changes.author,
206 205 date=changes.date,
207 206 desc=changes.summary,
208 207 parents=self.parents[rev],
209 208 rev=rev,
210 209 )
211 210
212 211 def gettags(self):
213 212 return self.tags
214 213
215 214 def _execute(self, cmd, *args, **kwargs):
216 215 cmdline = [self.execmd, cmd]
217 216 cmdline += args
218 217 cmdline = [procutil.shellquote(arg) for arg in cmdline]
219 218 bdevnull = pycompat.bytestr(os.devnull)
220 219 cmdline += [b'>', bdevnull, b'2>', bdevnull]
221 220 cmdline = procutil.quotecommand(b' '.join(cmdline))
222 221 self.ui.debug(cmdline, b'\n')
223 222 return os.system(pycompat.rapply(procutil.tonativestr, cmdline))
224 223
225 224 def _update(self, rev):
226 225 self.ui.debug(b'applying revision %s...\n' % rev)
227 226 changeset, status = self.runlines(b'replay', b'-d', self.tmppath, rev)
228 227 if status:
229 228 # Something went wrong while merging (baz or tla
230 229 # issue?), get latest revision and try from there
231 230 shutil.rmtree(self.tmppath, ignore_errors=True)
232 231 self._obtainrevision(rev)
233 232 else:
234 233 old_rev = self.parents[rev][0]
235 234 self.ui.debug(
236 235 b'computing changeset between %s and %s...\n' % (old_rev, rev)
237 236 )
238 237 self._parsechangeset(changeset, rev)
239 238
240 239 def _getfile(self, name, rev):
241 240 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
242 241 if stat.S_ISLNK(mode):
243 242 data = util.readlink(os.path.join(self.tmppath, name))
244 243 if mode:
245 244 mode = b'l'
246 245 else:
247 246 mode = b''
248 247 else:
249 248 data = util.readfile(os.path.join(self.tmppath, name))
250 249 mode = (mode & 0o111) and b'x' or b''
251 250 return data, mode
252 251
253 252 def _exclude(self, name):
254 253 exclude = [b'{arch}', b'.arch-ids', b'.arch-inventory']
255 254 for exc in exclude:
256 255 if name.find(exc) != -1:
257 256 return True
258 257 return False
259 258
260 259 def _readcontents(self, path):
261 260 files = []
262 261 contents = os.listdir(path)
263 262 while len(contents) > 0:
264 263 c = contents.pop()
265 264 p = os.path.join(path, c)
266 265 # os.walk could be used, but here we avoid internal GNU
267 266 # Arch files and directories, thus saving a lot time.
268 267 if not self._exclude(p):
269 268 if os.path.isdir(p):
270 269 contents += [os.path.join(c, f) for f in os.listdir(p)]
271 270 else:
272 271 files.append(c)
273 272 return files
274 273
275 274 def _rendirchanges(self, src, dest):
276 275 changes = []
277 276 copies = {}
278 277 files = self._readcontents(os.path.join(self.tmppath, dest))
279 278 for f in files:
280 279 s = os.path.join(src, f)
281 280 d = os.path.join(dest, f)
282 281 changes.append(s)
283 282 changes.append(d)
284 283 copies[d] = s
285 284 return changes, copies
286 285
287 286 def _obtainrevision(self, rev):
288 287 self.ui.debug(b'obtaining revision %s...\n' % rev)
289 288 output = self._execute(b'get', rev, self.tmppath)
290 289 self.checkexit(output)
291 290 self.ui.debug(b'analyzing revision %s...\n' % rev)
292 291 files = self._readcontents(self.tmppath)
293 292 self.changes[rev].add_files += files
294 293
295 294 def _stripbasepath(self, path):
296 295 if path.startswith(b'./'):
297 296 return path[2:]
298 297 return path
299 298
300 299 def _parsecatlog(self, data, rev):
301 300 try:
302 catlog = self.catlogparser.parsestr(data)
301 catlog = mail.parsebytes(data)
303 302
304 303 # Commit date
305 304 self.changes[rev].date = dateutil.datestr(
306 305 dateutil.strdate(catlog[b'Standard-date'], b'%Y-%m-%d %H:%M:%S')
307 306 )
308 307
309 308 # Commit author
310 309 self.changes[rev].author = self.recode(catlog[b'Creator'])
311 310
312 311 # Commit description
313 312 self.changes[rev].summary = b'\n\n'.join(
314 313 (catlog[b'Summary'], catlog.get_payload())
315 314 )
316 315 self.changes[rev].summary = self.recode(self.changes[rev].summary)
317 316
318 317 # Commit revision origin when dealing with a branch or tag
319 318 if b'Continuation-of' in catlog:
320 319 self.changes[rev].continuationof = self.recode(
321 320 catlog[b'Continuation-of']
322 321 )
323 322 except Exception:
324 323 raise error.Abort(_(b'could not parse cat-log of %s') % rev)
325 324
326 325 def _parsechangeset(self, data, rev):
327 326 for l in data:
328 327 l = l.strip()
329 328 # Added file (ignore added directory)
330 329 if l.startswith(b'A') and not l.startswith(b'A/'):
331 330 file = self._stripbasepath(l[1:].strip())
332 331 if not self._exclude(file):
333 332 self.changes[rev].add_files.append(file)
334 333 # Deleted file (ignore deleted directory)
335 334 elif l.startswith(b'D') and not l.startswith(b'D/'):
336 335 file = self._stripbasepath(l[1:].strip())
337 336 if not self._exclude(file):
338 337 self.changes[rev].del_files.append(file)
339 338 # Modified binary file
340 339 elif l.startswith(b'Mb'):
341 340 file = self._stripbasepath(l[2:].strip())
342 341 if not self._exclude(file):
343 342 self.changes[rev].mod_files.append(file)
344 343 # Modified link
345 344 elif l.startswith(b'M->'):
346 345 file = self._stripbasepath(l[3:].strip())
347 346 if not self._exclude(file):
348 347 self.changes[rev].mod_files.append(file)
349 348 # Modified file
350 349 elif l.startswith(b'M'):
351 350 file = self._stripbasepath(l[1:].strip())
352 351 if not self._exclude(file):
353 352 self.changes[rev].mod_files.append(file)
354 353 # Renamed file (or link)
355 354 elif l.startswith(b'=>'):
356 355 files = l[2:].strip().split(b' ')
357 356 if len(files) == 1:
358 357 files = l[2:].strip().split(b'\t')
359 358 src = self._stripbasepath(files[0])
360 359 dst = self._stripbasepath(files[1])
361 360 if not self._exclude(src) and not self._exclude(dst):
362 361 self.changes[rev].ren_files[src] = dst
363 362 # Conversion from file to link or from link to file (modified)
364 363 elif l.startswith(b'ch'):
365 364 file = self._stripbasepath(l[2:].strip())
366 365 if not self._exclude(file):
367 366 self.changes[rev].mod_files.append(file)
368 367 # Renamed directory
369 368 elif l.startswith(b'/>'):
370 369 dirs = l[2:].strip().split(b' ')
371 370 if len(dirs) == 1:
372 371 dirs = l[2:].strip().split(b'\t')
373 372 src = self._stripbasepath(dirs[0])
374 373 dst = self._stripbasepath(dirs[1])
375 374 if not self._exclude(src) and not self._exclude(dst):
376 375 self.changes[rev].ren_dirs[src] = dst
General Comments 0
You need to be logged in to leave comments. Login now