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