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