##// END OF EJS Templates
convert: print darcs error messages iff --debug
Bryan O'Sullivan -
r7610:87846613 default
parent child Browse files
Show More
@@ -1,355 +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, strutil
5 from mercurial import util, strutil
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. rev is the identifier returned
71 """Return file contents as a string. rev is the identifier returned
72 by a previous call to getchanges().
72 by a previous call to getchanges().
73 """
73 """
74 raise NotImplementedError()
74 raise NotImplementedError()
75
75
76 def getmode(self, name, rev):
76 def getmode(self, name, rev):
77 """Return file mode, eg. '', 'x', or 'l'. rev is the identifier
77 """Return file mode, eg. '', 'x', or 'l'. rev is the identifier
78 returned by a previous call to getchanges().
78 returned by a previous call to getchanges().
79 """
79 """
80 raise NotImplementedError()
80 raise NotImplementedError()
81
81
82 def getchanges(self, version):
82 def getchanges(self, version):
83 """Returns a tuple of (files, copies).
83 """Returns a tuple of (files, copies).
84
84
85 files is a sorted list of (filename, id) tuples for all files
85 files is a sorted list of (filename, id) tuples for all files
86 changed between version and it's first parent returned by
86 changed between version and it's first parent returned by
87 getcommit(). id is the source revision id of the file.
87 getcommit(). id is the source revision id of the file.
88
88
89 copies is a dictionary of dest: source
89 copies is a dictionary of dest: source
90 """
90 """
91 raise NotImplementedError()
91 raise NotImplementedError()
92
92
93 def getcommit(self, version):
93 def getcommit(self, version):
94 """Return the commit object for version"""
94 """Return the commit object for version"""
95 raise NotImplementedError()
95 raise NotImplementedError()
96
96
97 def gettags(self):
97 def gettags(self):
98 """Return the tags as a dictionary of name: revision"""
98 """Return the tags as a dictionary of name: revision"""
99 raise NotImplementedError()
99 raise NotImplementedError()
100
100
101 def recode(self, s, encoding=None):
101 def recode(self, s, encoding=None):
102 if not encoding:
102 if not encoding:
103 encoding = self.encoding or 'utf-8'
103 encoding = self.encoding or 'utf-8'
104
104
105 if isinstance(s, unicode):
105 if isinstance(s, unicode):
106 return s.encode("utf-8")
106 return s.encode("utf-8")
107 try:
107 try:
108 return s.decode(encoding).encode("utf-8")
108 return s.decode(encoding).encode("utf-8")
109 except:
109 except:
110 try:
110 try:
111 return s.decode("latin-1").encode("utf-8")
111 return s.decode("latin-1").encode("utf-8")
112 except:
112 except:
113 return s.decode(encoding, "replace").encode("utf-8")
113 return s.decode(encoding, "replace").encode("utf-8")
114
114
115 def getchangedfiles(self, rev, i):
115 def getchangedfiles(self, rev, i):
116 """Return the files changed by rev compared to parent[i].
116 """Return the files changed by rev compared to parent[i].
117
117
118 i is an index selecting one of the parents of rev. The return
118 i is an index selecting one of the parents of rev. The return
119 value should be the list of files that are different in rev and
119 value should be the list of files that are different in rev and
120 this parent.
120 this parent.
121
121
122 If rev has no parents, i is None.
122 If rev has no parents, i is None.
123
123
124 This function is only needed to support --filemap
124 This function is only needed to support --filemap
125 """
125 """
126 raise NotImplementedError()
126 raise NotImplementedError()
127
127
128 def converted(self, rev, sinkrev):
128 def converted(self, rev, sinkrev):
129 '''Notify the source that a revision has been converted.'''
129 '''Notify the source that a revision has been converted.'''
130 pass
130 pass
131
131
132
132
133 class converter_sink(object):
133 class converter_sink(object):
134 """Conversion sink (target) interface"""
134 """Conversion sink (target) interface"""
135
135
136 def __init__(self, ui, path):
136 def __init__(self, ui, path):
137 """Initialize conversion sink (or raise NoRepo("message")
137 """Initialize conversion sink (or raise NoRepo("message")
138 exception if path is not a valid repository)
138 exception if path is not a valid repository)
139
139
140 created is a list of paths to remove if a fatal error occurs
140 created is a list of paths to remove if a fatal error occurs
141 later"""
141 later"""
142 self.ui = ui
142 self.ui = ui
143 self.path = path
143 self.path = path
144 self.created = []
144 self.created = []
145
145
146 def getheads(self):
146 def getheads(self):
147 """Return a list of this repository's heads"""
147 """Return a list of this repository's heads"""
148 raise NotImplementedError()
148 raise NotImplementedError()
149
149
150 def revmapfile(self):
150 def revmapfile(self):
151 """Path to a file that will contain lines
151 """Path to a file that will contain lines
152 source_rev_id sink_rev_id
152 source_rev_id sink_rev_id
153 mapping equivalent revision identifiers for each system."""
153 mapping equivalent revision identifiers for each system."""
154 raise NotImplementedError()
154 raise NotImplementedError()
155
155
156 def authorfile(self):
156 def authorfile(self):
157 """Path to a file that will contain lines
157 """Path to a file that will contain lines
158 srcauthor=dstauthor
158 srcauthor=dstauthor
159 mapping equivalent authors identifiers for each system."""
159 mapping equivalent authors identifiers for each system."""
160 return None
160 return None
161
161
162 def putcommit(self, files, copies, parents, commit, source):
162 def putcommit(self, files, copies, parents, commit, source):
163 """Create a revision with all changed files listed in 'files'
163 """Create a revision with all changed files listed in 'files'
164 and having listed parents. 'commit' is a commit object containing
164 and having listed parents. 'commit' is a commit object containing
165 at a minimum the author, date, and message for this changeset.
165 at a minimum the author, date, and message for this changeset.
166 'files' is a list of (path, version) tuples, 'copies'is a dictionary
166 'files' is a list of (path, version) tuples, 'copies'is a dictionary
167 mapping destinations to sources, and 'source' is the source repository.
167 mapping destinations to sources, and 'source' is the source repository.
168 Only getfile() and getmode() should be called on 'source'.
168 Only getfile() and getmode() should be called on 'source'.
169
169
170 Note that the sink repository is not told to update itself to
170 Note that the sink repository is not told to update itself to
171 a particular revision (or even what that revision would be)
171 a particular revision (or even what that revision would be)
172 before it receives the file data.
172 before it receives the file data.
173 """
173 """
174 raise NotImplementedError()
174 raise NotImplementedError()
175
175
176 def puttags(self, tags):
176 def puttags(self, tags):
177 """Put tags into sink.
177 """Put tags into sink.
178 tags: {tagname: sink_rev_id, ...}"""
178 tags: {tagname: sink_rev_id, ...}"""
179 raise NotImplementedError()
179 raise NotImplementedError()
180
180
181 def setbranch(self, branch, pbranches):
181 def setbranch(self, branch, pbranches):
182 """Set the current branch name. Called before the first putcommit
182 """Set the current branch name. Called before the first putcommit
183 on the branch.
183 on the branch.
184 branch: branch name for subsequent commits
184 branch: branch name for subsequent commits
185 pbranches: (converted parent revision, parent branch) tuples"""
185 pbranches: (converted parent revision, parent branch) tuples"""
186 pass
186 pass
187
187
188 def setfilemapmode(self, active):
188 def setfilemapmode(self, active):
189 """Tell the destination that we're using a filemap
189 """Tell the destination that we're using a filemap
190
190
191 Some converter_sources (svn in particular) can claim that a file
191 Some converter_sources (svn in particular) can claim that a file
192 was changed in a revision, even if there was no change. This method
192 was changed in a revision, even if there was no change. This method
193 tells the destination that we're using a filemap and that it should
193 tells the destination that we're using a filemap and that it should
194 filter empty revisions.
194 filter empty revisions.
195 """
195 """
196 pass
196 pass
197
197
198 def before(self):
198 def before(self):
199 pass
199 pass
200
200
201 def after(self):
201 def after(self):
202 pass
202 pass
203
203
204
204
205 class commandline(object):
205 class commandline(object):
206 def __init__(self, ui, command):
206 def __init__(self, ui, command):
207 self.ui = ui
207 self.ui = ui
208 self.command = command
208 self.command = command
209
209
210 def prerun(self):
210 def prerun(self):
211 pass
211 pass
212
212
213 def postrun(self):
213 def postrun(self):
214 pass
214 pass
215
215
216 def _cmdline(self, cmd, *args, **kwargs):
216 def _cmdline(self, cmd, *args, **kwargs):
217 cmdline = [self.command, cmd] + list(args)
217 cmdline = [self.command, cmd] + list(args)
218 for k, v in kwargs.iteritems():
218 for k, v in kwargs.iteritems():
219 if len(k) == 1:
219 if len(k) == 1:
220 cmdline.append('-' + k)
220 cmdline.append('-' + k)
221 else:
221 else:
222 cmdline.append('--' + k.replace('_', '-'))
222 cmdline.append('--' + k.replace('_', '-'))
223 try:
223 try:
224 if len(k) == 1:
224 if len(k) == 1:
225 cmdline.append('' + v)
225 cmdline.append('' + v)
226 else:
226 else:
227 cmdline[-1] += '=' + v
227 cmdline[-1] += '=' + v
228 except TypeError:
228 except TypeError:
229 pass
229 pass
230 cmdline = [util.shellquote(arg) for arg in cmdline]
230 cmdline = [util.shellquote(arg) for arg in cmdline]
231 cmdline += ['2>', util.nulldev, '<', util.nulldev]
231 if not self.ui.debug:
232 cmdline += ['2>', util.nulldev]
233 cmdline += ['<', util.nulldev]
232 cmdline = ' '.join(cmdline)
234 cmdline = ' '.join(cmdline)
233 return cmdline
235 return cmdline
234
236
235 def _run(self, cmd, *args, **kwargs):
237 def _run(self, cmd, *args, **kwargs):
236 cmdline = self._cmdline(cmd, *args, **kwargs)
238 cmdline = self._cmdline(cmd, *args, **kwargs)
237 self.ui.debug(_('running: %s\n') % (cmdline,))
239 self.ui.debug(_('running: %s\n') % (cmdline,))
238 self.prerun()
240 self.prerun()
239 try:
241 try:
240 return util.popen(cmdline)
242 return util.popen(cmdline)
241 finally:
243 finally:
242 self.postrun()
244 self.postrun()
243
245
244 def run(self, cmd, *args, **kwargs):
246 def run(self, cmd, *args, **kwargs):
245 fp = self._run(cmd, *args, **kwargs)
247 fp = self._run(cmd, *args, **kwargs)
246 output = fp.read()
248 output = fp.read()
247 self.ui.debug(output)
249 self.ui.debug(output)
248 return output, fp.close()
250 return output, fp.close()
249
251
250 def runlines(self, cmd, *args, **kwargs):
252 def runlines(self, cmd, *args, **kwargs):
251 fp = self._run(cmd, *args, **kwargs)
253 fp = self._run(cmd, *args, **kwargs)
252 output = fp.readlines()
254 output = fp.readlines()
253 self.ui.debug(''.join(output))
255 self.ui.debug(''.join(output))
254 return output, fp.close()
256 return output, fp.close()
255
257
256 def checkexit(self, status, output=''):
258 def checkexit(self, status, output=''):
257 if status:
259 if status:
258 if output:
260 if output:
259 self.ui.warn(_('%s error:\n') % self.command)
261 self.ui.warn(_('%s error:\n') % self.command)
260 self.ui.warn(output)
262 self.ui.warn(output)
261 msg = util.explain_exit(status)[0]
263 msg = util.explain_exit(status)[0]
262 raise util.Abort(_('%s %s') % (self.command, msg))
264 raise util.Abort(_('%s %s') % (self.command, msg))
263
265
264 def run0(self, cmd, *args, **kwargs):
266 def run0(self, cmd, *args, **kwargs):
265 output, status = self.run(cmd, *args, **kwargs)
267 output, status = self.run(cmd, *args, **kwargs)
266 self.checkexit(status, output)
268 self.checkexit(status, output)
267 return output
269 return output
268
270
269 def runlines0(self, cmd, *args, **kwargs):
271 def runlines0(self, cmd, *args, **kwargs):
270 output, status = self.runlines(cmd, *args, **kwargs)
272 output, status = self.runlines(cmd, *args, **kwargs)
271 self.checkexit(status, ''.join(output))
273 self.checkexit(status, ''.join(output))
272 return output
274 return output
273
275
274 def getargmax(self):
276 def getargmax(self):
275 if '_argmax' in self.__dict__:
277 if '_argmax' in self.__dict__:
276 return self._argmax
278 return self._argmax
277
279
278 # POSIX requires at least 4096 bytes for ARG_MAX
280 # POSIX requires at least 4096 bytes for ARG_MAX
279 self._argmax = 4096
281 self._argmax = 4096
280 try:
282 try:
281 self._argmax = os.sysconf("SC_ARG_MAX")
283 self._argmax = os.sysconf("SC_ARG_MAX")
282 except:
284 except:
283 pass
285 pass
284
286
285 # Windows shells impose their own limits on command line length,
287 # Windows shells impose their own limits on command line length,
286 # 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
287 # 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
288 # details about cmd.exe limitations.
290 # details about cmd.exe limitations.
289
291
290 # 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
291 # (and make happy Windows shells while doing this).
293 # (and make happy Windows shells while doing this).
292
294
293 self._argmax = self._argmax/2 - 1
295 self._argmax = self._argmax/2 - 1
294 return self._argmax
296 return self._argmax
295
297
296 def limit_arglist(self, arglist, cmd, *args, **kwargs):
298 def limit_arglist(self, arglist, cmd, *args, **kwargs):
297 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
299 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
298 bytes = 0
300 bytes = 0
299 fl = []
301 fl = []
300 for fn in arglist:
302 for fn in arglist:
301 b = len(fn) + 3
303 b = len(fn) + 3
302 if bytes + b < limit or len(fl) == 0:
304 if bytes + b < limit or len(fl) == 0:
303 fl.append(fn)
305 fl.append(fn)
304 bytes += b
306 bytes += b
305 else:
307 else:
306 yield fl
308 yield fl
307 fl = [fn]
309 fl = [fn]
308 bytes = b
310 bytes = b
309 if fl:
311 if fl:
310 yield fl
312 yield fl
311
313
312 def xargs(self, arglist, cmd, *args, **kwargs):
314 def xargs(self, arglist, cmd, *args, **kwargs):
313 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
315 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
314 self.run0(cmd, *(list(args) + l), **kwargs)
316 self.run0(cmd, *(list(args) + l), **kwargs)
315
317
316 class mapfile(dict):
318 class mapfile(dict):
317 def __init__(self, ui, path):
319 def __init__(self, ui, path):
318 super(mapfile, self).__init__()
320 super(mapfile, self).__init__()
319 self.ui = ui
321 self.ui = ui
320 self.path = path
322 self.path = path
321 self.fp = None
323 self.fp = None
322 self.order = []
324 self.order = []
323 self._read()
325 self._read()
324
326
325 def _read(self):
327 def _read(self):
326 if self.path is None:
328 if self.path is None:
327 return
329 return
328 try:
330 try:
329 fp = open(self.path, 'r')
331 fp = open(self.path, 'r')
330 except IOError, err:
332 except IOError, err:
331 if err.errno != errno.ENOENT:
333 if err.errno != errno.ENOENT:
332 raise
334 raise
333 return
335 return
334 for line in fp:
336 for line in fp:
335 key, value = strutil.rsplit(line[:-1], ' ', 1)
337 key, value = strutil.rsplit(line[:-1], ' ', 1)
336 if key not in self:
338 if key not in self:
337 self.order.append(key)
339 self.order.append(key)
338 super(mapfile, self).__setitem__(key, value)
340 super(mapfile, self).__setitem__(key, value)
339 fp.close()
341 fp.close()
340
342
341 def __setitem__(self, key, value):
343 def __setitem__(self, key, value):
342 if self.fp is None:
344 if self.fp is None:
343 try:
345 try:
344 self.fp = open(self.path, 'a')
346 self.fp = open(self.path, 'a')
345 except IOError, err:
347 except IOError, err:
346 raise util.Abort(_('could not open map file %r: %s') %
348 raise util.Abort(_('could not open map file %r: %s') %
347 (self.path, err.strerror))
349 (self.path, err.strerror))
348 self.fp.write('%s %s\n' % (key, value))
350 self.fp.write('%s %s\n' % (key, value))
349 self.fp.flush()
351 self.fp.flush()
350 super(mapfile, self).__setitem__(key, value)
352 super(mapfile, self).__setitem__(key, value)
351
353
352 def close(self):
354 def close(self):
353 if self.fp:
355 if self.fp:
354 self.fp.close()
356 self.fp.close()
355 self.fp = None
357 self.fp = None
General Comments 0
You need to be logged in to leave comments. Login now