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