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