##// END OF EJS Templates
convert: Fix debugging output when running multiple commands with xargs.
Thomas Arendsen Hein -
r6868:93b03f1b default
parent child Browse files
Show More
@@ -1,357 +1,357 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64, errno
2 import base64, errno
3 import os
3 import os
4 import cPickle as pickle
4 import cPickle as pickle
5 from mercurial import util
5 from mercurial import util
6 from mercurial.i18n import _
6 from mercurial.i18n import _
7
7
8 def encodeargs(args):
8 def encodeargs(args):
9 def encodearg(s):
9 def encodearg(s):
10 lines = base64.encodestring(s)
10 lines = base64.encodestring(s)
11 lines = [l.splitlines()[0] for l in lines]
11 lines = [l.splitlines()[0] for l in lines]
12 return ''.join(lines)
12 return ''.join(lines)
13
13
14 s = pickle.dumps(args)
14 s = pickle.dumps(args)
15 return encodearg(s)
15 return encodearg(s)
16
16
17 def decodeargs(s):
17 def decodeargs(s):
18 s = base64.decodestring(s)
18 s = base64.decodestring(s)
19 return pickle.loads(s)
19 return pickle.loads(s)
20
20
21 class MissingTool(Exception): pass
21 class MissingTool(Exception): pass
22
22
23 def checktool(exe, name=None, abort=True):
23 def checktool(exe, name=None, abort=True):
24 name = name or exe
24 name = name or exe
25 if not util.find_exe(exe):
25 if not util.find_exe(exe):
26 exc = abort and util.Abort or MissingTool
26 exc = abort and util.Abort or MissingTool
27 raise exc(_('cannot find required "%s" tool') % name)
27 raise exc(_('cannot find required "%s" tool') % name)
28
28
29 class NoRepo(Exception): pass
29 class NoRepo(Exception): pass
30
30
31 SKIPREV = 'SKIP'
31 SKIPREV = 'SKIP'
32
32
33 class commit(object):
33 class commit(object):
34 def __init__(self, author, date, desc, parents, branch=None, rev=None,
34 def __init__(self, author, date, desc, parents, branch=None, rev=None,
35 extra={}):
35 extra={}):
36 self.author = author or 'unknown'
36 self.author = author or 'unknown'
37 self.date = date or '0 0'
37 self.date = date or '0 0'
38 self.desc = desc
38 self.desc = desc
39 self.parents = parents
39 self.parents = parents
40 self.branch = branch
40 self.branch = branch
41 self.rev = rev
41 self.rev = rev
42 self.extra = extra
42 self.extra = extra
43
43
44 class converter_source(object):
44 class converter_source(object):
45 """Conversion source interface"""
45 """Conversion source interface"""
46
46
47 def __init__(self, ui, path=None, rev=None):
47 def __init__(self, ui, path=None, rev=None):
48 """Initialize conversion source (or raise NoRepo("message")
48 """Initialize conversion source (or raise NoRepo("message")
49 exception if path is not a valid repository)"""
49 exception if path is not a valid repository)"""
50 self.ui = ui
50 self.ui = ui
51 self.path = path
51 self.path = path
52 self.rev = rev
52 self.rev = rev
53
53
54 self.encoding = 'utf-8'
54 self.encoding = 'utf-8'
55
55
56 def before(self):
56 def before(self):
57 pass
57 pass
58
58
59 def after(self):
59 def after(self):
60 pass
60 pass
61
61
62 def setrevmap(self, revmap):
62 def setrevmap(self, revmap):
63 """set the map of already-converted revisions"""
63 """set the map of already-converted revisions"""
64 pass
64 pass
65
65
66 def getheads(self):
66 def getheads(self):
67 """Return a list of this repository's heads"""
67 """Return a list of this repository's heads"""
68 raise NotImplementedError()
68 raise NotImplementedError()
69
69
70 def getfile(self, name, rev):
70 def getfile(self, name, rev):
71 """Return file contents as a string"""
71 """Return file contents as a string"""
72 raise NotImplementedError()
72 raise NotImplementedError()
73
73
74 def getmode(self, name, rev):
74 def getmode(self, name, rev):
75 """Return file mode, eg. '', 'x', or 'l'"""
75 """Return file mode, eg. '', 'x', or 'l'"""
76 raise NotImplementedError()
76 raise NotImplementedError()
77
77
78 def getchanges(self, version):
78 def getchanges(self, version):
79 """Returns a tuple of (files, copies)
79 """Returns a tuple of (files, copies)
80 Files is a sorted list of (filename, id) tuples for all files changed
80 Files is a sorted list of (filename, id) tuples for all files changed
81 in version, where id is the source revision id of the file.
81 in version, where id is the source revision id of the file.
82
82
83 copies is a dictionary of dest: source
83 copies is a dictionary of dest: source
84 """
84 """
85 raise NotImplementedError()
85 raise NotImplementedError()
86
86
87 def getcommit(self, version):
87 def getcommit(self, version):
88 """Return the commit object for version"""
88 """Return the commit object for version"""
89 raise NotImplementedError()
89 raise NotImplementedError()
90
90
91 def gettags(self):
91 def gettags(self):
92 """Return the tags as a dictionary of name: revision"""
92 """Return the tags as a dictionary of name: revision"""
93 raise NotImplementedError()
93 raise NotImplementedError()
94
94
95 def recode(self, s, encoding=None):
95 def recode(self, s, encoding=None):
96 if not encoding:
96 if not encoding:
97 encoding = self.encoding or 'utf-8'
97 encoding = self.encoding or 'utf-8'
98
98
99 if isinstance(s, unicode):
99 if isinstance(s, unicode):
100 return s.encode("utf-8")
100 return s.encode("utf-8")
101 try:
101 try:
102 return s.decode(encoding).encode("utf-8")
102 return s.decode(encoding).encode("utf-8")
103 except:
103 except:
104 try:
104 try:
105 return s.decode("latin-1").encode("utf-8")
105 return s.decode("latin-1").encode("utf-8")
106 except:
106 except:
107 return s.decode(encoding, "replace").encode("utf-8")
107 return s.decode(encoding, "replace").encode("utf-8")
108
108
109 def getchangedfiles(self, rev, i):
109 def getchangedfiles(self, rev, i):
110 """Return the files changed by rev compared to parent[i].
110 """Return the files changed by rev compared to parent[i].
111
111
112 i is an index selecting one of the parents of rev. The return
112 i is an index selecting one of the parents of rev. The return
113 value should be the list of files that are different in rev and
113 value should be the list of files that are different in rev and
114 this parent.
114 this parent.
115
115
116 If rev has no parents, i is None.
116 If rev has no parents, i is None.
117
117
118 This function is only needed to support --filemap
118 This function is only needed to support --filemap
119 """
119 """
120 raise NotImplementedError()
120 raise NotImplementedError()
121
121
122 def converted(self, rev, sinkrev):
122 def converted(self, rev, sinkrev):
123 '''Notify the source that a revision has been converted.'''
123 '''Notify the source that a revision has been converted.'''
124 pass
124 pass
125
125
126
126
127 class converter_sink(object):
127 class converter_sink(object):
128 """Conversion sink (target) interface"""
128 """Conversion sink (target) interface"""
129
129
130 def __init__(self, ui, path):
130 def __init__(self, ui, path):
131 """Initialize conversion sink (or raise NoRepo("message")
131 """Initialize conversion sink (or raise NoRepo("message")
132 exception if path is not a valid repository)
132 exception if path is not a valid repository)
133
133
134 created is a list of paths to remove if a fatal error occurs
134 created is a list of paths to remove if a fatal error occurs
135 later"""
135 later"""
136 self.ui = ui
136 self.ui = ui
137 self.path = path
137 self.path = path
138 self.created = []
138 self.created = []
139
139
140 def getheads(self):
140 def getheads(self):
141 """Return a list of this repository's heads"""
141 """Return a list of this repository's heads"""
142 raise NotImplementedError()
142 raise NotImplementedError()
143
143
144 def revmapfile(self):
144 def revmapfile(self):
145 """Path to a file that will contain lines
145 """Path to a file that will contain lines
146 source_rev_id sink_rev_id
146 source_rev_id sink_rev_id
147 mapping equivalent revision identifiers for each system."""
147 mapping equivalent revision identifiers for each system."""
148 raise NotImplementedError()
148 raise NotImplementedError()
149
149
150 def authorfile(self):
150 def authorfile(self):
151 """Path to a file that will contain lines
151 """Path to a file that will contain lines
152 srcauthor=dstauthor
152 srcauthor=dstauthor
153 mapping equivalent authors identifiers for each system."""
153 mapping equivalent authors identifiers for each system."""
154 return None
154 return None
155
155
156 def putfile(self, f, e, data):
156 def putfile(self, f, e, data):
157 """Put file for next putcommit().
157 """Put file for next putcommit().
158 f: path to file
158 f: path to file
159 e: '', 'x', or 'l' (regular file, executable, or symlink)
159 e: '', 'x', or 'l' (regular file, executable, or symlink)
160 data: file contents"""
160 data: file contents"""
161 raise NotImplementedError()
161 raise NotImplementedError()
162
162
163 def delfile(self, f):
163 def delfile(self, f):
164 """Delete file for next putcommit().
164 """Delete file for next putcommit().
165 f: path to file"""
165 f: path to file"""
166 raise NotImplementedError()
166 raise NotImplementedError()
167
167
168 def putcommit(self, files, parents, commit):
168 def putcommit(self, files, parents, commit):
169 """Create a revision with all changed files listed in 'files'
169 """Create a revision with all changed files listed in 'files'
170 and having listed parents. 'commit' is a commit object containing
170 and having listed parents. 'commit' is a commit object containing
171 at a minimum the author, date, and message for this changeset.
171 at a minimum the author, date, and message for this changeset.
172 Called after putfile() and delfile() calls. Note that the sink
172 Called after putfile() and delfile() calls. Note that the sink
173 repository is not told to update itself to a particular revision
173 repository is not told to update itself to a particular revision
174 (or even what that revision would be) before it receives the
174 (or even what that revision would be) before it receives the
175 file data."""
175 file data."""
176 raise NotImplementedError()
176 raise NotImplementedError()
177
177
178 def puttags(self, tags):
178 def puttags(self, tags):
179 """Put tags into sink.
179 """Put tags into sink.
180 tags: {tagname: sink_rev_id, ...}"""
180 tags: {tagname: sink_rev_id, ...}"""
181 raise NotImplementedError()
181 raise NotImplementedError()
182
182
183 def setbranch(self, branch, pbranches):
183 def setbranch(self, branch, pbranches):
184 """Set the current branch name. Called before the first putfile
184 """Set the current branch name. Called before the first putfile
185 on the branch.
185 on the branch.
186 branch: branch name for subsequent commits
186 branch: branch name for subsequent commits
187 pbranches: (converted parent revision, parent branch) tuples"""
187 pbranches: (converted parent revision, parent branch) tuples"""
188 pass
188 pass
189
189
190 def setfilemapmode(self, active):
190 def setfilemapmode(self, active):
191 """Tell the destination that we're using a filemap
191 """Tell the destination that we're using a filemap
192
192
193 Some converter_sources (svn in particular) can claim that a file
193 Some converter_sources (svn in particular) can claim that a file
194 was changed in a revision, even if there was no change. This method
194 was changed in a revision, even if there was no change. This method
195 tells the destination that we're using a filemap and that it should
195 tells the destination that we're using a filemap and that it should
196 filter empty revisions.
196 filter empty revisions.
197 """
197 """
198 pass
198 pass
199
199
200 def before(self):
200 def before(self):
201 pass
201 pass
202
202
203 def after(self):
203 def after(self):
204 pass
204 pass
205
205
206
206
207 class commandline(object):
207 class commandline(object):
208 def __init__(self, ui, command):
208 def __init__(self, ui, command):
209 self.ui = ui
209 self.ui = ui
210 self.command = command
210 self.command = command
211
211
212 def prerun(self):
212 def prerun(self):
213 pass
213 pass
214
214
215 def postrun(self):
215 def postrun(self):
216 pass
216 pass
217
217
218 def _cmdline(self, cmd, *args, **kwargs):
218 def _cmdline(self, cmd, *args, **kwargs):
219 cmdline = [self.command, cmd] + list(args)
219 cmdline = [self.command, cmd] + list(args)
220 for k, v in kwargs.iteritems():
220 for k, v in kwargs.iteritems():
221 if len(k) == 1:
221 if len(k) == 1:
222 cmdline.append('-' + k)
222 cmdline.append('-' + k)
223 else:
223 else:
224 cmdline.append('--' + k.replace('_', '-'))
224 cmdline.append('--' + k.replace('_', '-'))
225 try:
225 try:
226 if len(k) == 1:
226 if len(k) == 1:
227 cmdline.append('' + v)
227 cmdline.append('' + v)
228 else:
228 else:
229 cmdline[-1] += '=' + v
229 cmdline[-1] += '=' + v
230 except TypeError:
230 except TypeError:
231 pass
231 pass
232 cmdline = [util.shellquote(arg) for arg in cmdline]
232 cmdline = [util.shellquote(arg) for arg in cmdline]
233 cmdline += ['2>', util.nulldev, '<', util.nulldev]
233 cmdline += ['2>', util.nulldev, '<', util.nulldev]
234 cmdline = ' '.join(cmdline)
234 cmdline = ' '.join(cmdline)
235 self.ui.debug(cmdline, '\n')
236 return cmdline
235 return cmdline
237
236
238 def _run(self, cmd, *args, **kwargs):
237 def _run(self, cmd, *args, **kwargs):
239 cmdline = self._cmdline(cmd, *args, **kwargs)
238 cmdline = self._cmdline(cmd, *args, **kwargs)
239 self.ui.debug('running: %s\n' % (cmdline,))
240 self.prerun()
240 self.prerun()
241 try:
241 try:
242 return util.popen(cmdline)
242 return util.popen(cmdline)
243 finally:
243 finally:
244 self.postrun()
244 self.postrun()
245
245
246 def run(self, cmd, *args, **kwargs):
246 def run(self, cmd, *args, **kwargs):
247 fp = self._run(cmd, *args, **kwargs)
247 fp = self._run(cmd, *args, **kwargs)
248 output = fp.read()
248 output = fp.read()
249 self.ui.debug(output)
249 self.ui.debug(output)
250 return output, fp.close()
250 return output, fp.close()
251
251
252 def runlines(self, cmd, *args, **kwargs):
252 def runlines(self, cmd, *args, **kwargs):
253 fp = self._run(cmd, *args, **kwargs)
253 fp = self._run(cmd, *args, **kwargs)
254 output = fp.readlines()
254 output = fp.readlines()
255 self.ui.debug(''.join(output))
255 self.ui.debug(''.join(output))
256 return output, fp.close()
256 return output, fp.close()
257
257
258 def checkexit(self, status, output=''):
258 def checkexit(self, status, output=''):
259 if status:
259 if status:
260 if output:
260 if output:
261 self.ui.warn(_('%s error:\n') % self.command)
261 self.ui.warn(_('%s error:\n') % self.command)
262 self.ui.warn(output)
262 self.ui.warn(output)
263 msg = util.explain_exit(status)[0]
263 msg = util.explain_exit(status)[0]
264 raise util.Abort(_('%s %s') % (self.command, msg))
264 raise util.Abort(_('%s %s') % (self.command, msg))
265
265
266 def run0(self, cmd, *args, **kwargs):
266 def run0(self, cmd, *args, **kwargs):
267 output, status = self.run(cmd, *args, **kwargs)
267 output, status = self.run(cmd, *args, **kwargs)
268 self.checkexit(status, output)
268 self.checkexit(status, output)
269 return output
269 return output
270
270
271 def runlines0(self, cmd, *args, **kwargs):
271 def runlines0(self, cmd, *args, **kwargs):
272 output, status = self.runlines(cmd, *args, **kwargs)
272 output, status = self.runlines(cmd, *args, **kwargs)
273 self.checkexit(status, ''.join(output))
273 self.checkexit(status, ''.join(output))
274 return output
274 return output
275
275
276 def getargmax(self):
276 def getargmax(self):
277 if '_argmax' in self.__dict__:
277 if '_argmax' in self.__dict__:
278 return self._argmax
278 return self._argmax
279
279
280 # POSIX requires at least 4096 bytes for ARG_MAX
280 # POSIX requires at least 4096 bytes for ARG_MAX
281 self._argmax = 4096
281 self._argmax = 4096
282 try:
282 try:
283 self._argmax = os.sysconf("SC_ARG_MAX")
283 self._argmax = os.sysconf("SC_ARG_MAX")
284 except:
284 except:
285 pass
285 pass
286
286
287 # Windows shells impose their own limits on command line length,
287 # Windows shells impose their own limits on command line length,
288 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
288 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
289 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
289 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
290 # details about cmd.exe limitations.
290 # details about cmd.exe limitations.
291
291
292 # Since ARG_MAX is for command line _and_ environment, lower our limit
292 # Since ARG_MAX is for command line _and_ environment, lower our limit
293 # (and make happy Windows shells while doing this).
293 # (and make happy Windows shells while doing this).
294
294
295 self._argmax = self._argmax/2 - 1
295 self._argmax = self._argmax/2 - 1
296 return self._argmax
296 return self._argmax
297
297
298 def limit_arglist(self, arglist, cmd, *args, **kwargs):
298 def limit_arglist(self, arglist, cmd, *args, **kwargs):
299 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
299 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
300 bytes = 0
300 bytes = 0
301 fl = []
301 fl = []
302 for fn in arglist:
302 for fn in arglist:
303 b = len(fn) + 3
303 b = len(fn) + 3
304 if bytes + b < limit or len(fl) == 0:
304 if bytes + b < limit or len(fl) == 0:
305 fl.append(fn)
305 fl.append(fn)
306 bytes += b
306 bytes += b
307 else:
307 else:
308 yield fl
308 yield fl
309 fl = [fn]
309 fl = [fn]
310 bytes = b
310 bytes = b
311 if fl:
311 if fl:
312 yield fl
312 yield fl
313
313
314 def xargs(self, arglist, cmd, *args, **kwargs):
314 def xargs(self, arglist, cmd, *args, **kwargs):
315 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
315 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
316 self.run0(cmd, *(list(args) + l), **kwargs)
316 self.run0(cmd, *(list(args) + l), **kwargs)
317
317
318 class mapfile(dict):
318 class mapfile(dict):
319 def __init__(self, ui, path):
319 def __init__(self, ui, path):
320 super(mapfile, self).__init__()
320 super(mapfile, self).__init__()
321 self.ui = ui
321 self.ui = ui
322 self.path = path
322 self.path = path
323 self.fp = None
323 self.fp = None
324 self.order = []
324 self.order = []
325 self._read()
325 self._read()
326
326
327 def _read(self):
327 def _read(self):
328 if self.path is None:
328 if self.path is None:
329 return
329 return
330 try:
330 try:
331 fp = open(self.path, 'r')
331 fp = open(self.path, 'r')
332 except IOError, err:
332 except IOError, err:
333 if err.errno != errno.ENOENT:
333 if err.errno != errno.ENOENT:
334 raise
334 raise
335 return
335 return
336 for line in fp:
336 for line in fp:
337 key, value = line[:-1].split(' ', 1)
337 key, value = line[:-1].split(' ', 1)
338 if key not in self:
338 if key not in self:
339 self.order.append(key)
339 self.order.append(key)
340 super(mapfile, self).__setitem__(key, value)
340 super(mapfile, self).__setitem__(key, value)
341 fp.close()
341 fp.close()
342
342
343 def __setitem__(self, key, value):
343 def __setitem__(self, key, value):
344 if self.fp is None:
344 if self.fp is None:
345 try:
345 try:
346 self.fp = open(self.path, 'a')
346 self.fp = open(self.path, 'a')
347 except IOError, err:
347 except IOError, err:
348 raise util.Abort(_('could not open map file %r: %s') %
348 raise util.Abort(_('could not open map file %r: %s') %
349 (self.path, err.strerror))
349 (self.path, err.strerror))
350 self.fp.write('%s %s\n' % (key, value))
350 self.fp.write('%s %s\n' % (key, value))
351 self.fp.flush()
351 self.fp.flush()
352 super(mapfile, self).__setitem__(key, value)
352 super(mapfile, self).__setitem__(key, value)
353
353
354 def close(self):
354 def close(self):
355 if self.fp:
355 if self.fp:
356 self.fp.close()
356 self.fp.close()
357 self.fp = None
357 self.fp = None
General Comments 0
You need to be logged in to leave comments. Login now