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