##// END OF EJS Templates
convert: don't use bytes as a variable name...
Pulkit Goyal -
r36349:e218830f default
parent child Browse files
Show More
@@ -1,495 +1,495 b''
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 from __future__ import absolute_import
8 8
9 9 import base64
10 10 import datetime
11 11 import errno
12 12 import os
13 13 import re
14 14 import subprocess
15 15
16 16 from mercurial.i18n import _
17 17 from mercurial import (
18 18 encoding,
19 19 error,
20 20 phases,
21 21 pycompat,
22 22 util,
23 23 )
24 24
25 25 pickle = util.pickle
26 26 propertycache = util.propertycache
27 27
28 28 def encodeargs(args):
29 29 def encodearg(s):
30 30 lines = base64.encodestring(s)
31 31 lines = [l.splitlines()[0] for l in lines]
32 32 return ''.join(lines)
33 33
34 34 s = pickle.dumps(args)
35 35 return encodearg(s)
36 36
37 37 def decodeargs(s):
38 38 s = base64.decodestring(s)
39 39 return pickle.loads(s)
40 40
41 41 class MissingTool(Exception):
42 42 pass
43 43
44 44 def checktool(exe, name=None, abort=True):
45 45 name = name or exe
46 46 if not util.findexe(exe):
47 47 if abort:
48 48 exc = error.Abort
49 49 else:
50 50 exc = MissingTool
51 51 raise exc(_('cannot find required "%s" tool') % name)
52 52
53 53 class NoRepo(Exception):
54 54 pass
55 55
56 56 SKIPREV = 'SKIP'
57 57
58 58 class commit(object):
59 59 def __init__(self, author, date, desc, parents, branch=None, rev=None,
60 60 extra=None, sortkey=None, saverev=True, phase=phases.draft,
61 61 optparents=None):
62 62 self.author = author or 'unknown'
63 63 self.date = date or '0 0'
64 64 self.desc = desc
65 65 self.parents = parents # will be converted and used as parents
66 66 self.optparents = optparents or [] # will be used if already converted
67 67 self.branch = branch
68 68 self.rev = rev
69 69 self.extra = extra or {}
70 70 self.sortkey = sortkey
71 71 self.saverev = saverev
72 72 self.phase = phase
73 73
74 74 class converter_source(object):
75 75 """Conversion source interface"""
76 76
77 77 def __init__(self, ui, repotype, path=None, revs=None):
78 78 """Initialize conversion source (or raise NoRepo("message")
79 79 exception if path is not a valid repository)"""
80 80 self.ui = ui
81 81 self.path = path
82 82 self.revs = revs
83 83 self.repotype = repotype
84 84
85 85 self.encoding = 'utf-8'
86 86
87 87 def checkhexformat(self, revstr, mapname='splicemap'):
88 88 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
89 89 such format for their revision numbering
90 90 """
91 91 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
92 92 raise error.Abort(_('%s entry %s is not a valid revision'
93 93 ' identifier') % (mapname, revstr))
94 94
95 95 def before(self):
96 96 pass
97 97
98 98 def after(self):
99 99 pass
100 100
101 101 def targetfilebelongstosource(self, targetfilename):
102 102 """Returns true if the given targetfile belongs to the source repo. This
103 103 is useful when only a subdirectory of the target belongs to the source
104 104 repo."""
105 105 # For normal full repo converts, this is always True.
106 106 return True
107 107
108 108 def setrevmap(self, revmap):
109 109 """set the map of already-converted revisions"""
110 110
111 111 def getheads(self):
112 112 """Return a list of this repository's heads"""
113 113 raise NotImplementedError
114 114
115 115 def getfile(self, name, rev):
116 116 """Return a pair (data, mode) where data is the file content
117 117 as a string and mode one of '', 'x' or 'l'. rev is the
118 118 identifier returned by a previous call to getchanges().
119 119 Data is None if file is missing/deleted in rev.
120 120 """
121 121 raise NotImplementedError
122 122
123 123 def getchanges(self, version, full):
124 124 """Returns a tuple of (files, copies, cleanp2).
125 125
126 126 files is a sorted list of (filename, id) tuples for all files
127 127 changed between version and its first parent returned by
128 128 getcommit(). If full, all files in that revision is returned.
129 129 id is the source revision id of the file.
130 130
131 131 copies is a dictionary of dest: source
132 132
133 133 cleanp2 is the set of files filenames that are clean against p2.
134 134 (Files that are clean against p1 are already not in files (unless
135 135 full). This makes it possible to handle p2 clean files similarly.)
136 136 """
137 137 raise NotImplementedError
138 138
139 139 def getcommit(self, version):
140 140 """Return the commit object for version"""
141 141 raise NotImplementedError
142 142
143 143 def numcommits(self):
144 144 """Return the number of commits in this source.
145 145
146 146 If unknown, return None.
147 147 """
148 148 return None
149 149
150 150 def gettags(self):
151 151 """Return the tags as a dictionary of name: revision
152 152
153 153 Tag names must be UTF-8 strings.
154 154 """
155 155 raise NotImplementedError
156 156
157 157 def recode(self, s, encoding=None):
158 158 if not encoding:
159 159 encoding = self.encoding or 'utf-8'
160 160
161 161 if isinstance(s, unicode):
162 162 return s.encode("utf-8")
163 163 try:
164 164 return s.decode(encoding).encode("utf-8")
165 165 except UnicodeError:
166 166 try:
167 167 return s.decode("latin-1").encode("utf-8")
168 168 except UnicodeError:
169 169 return s.decode(encoding, "replace").encode("utf-8")
170 170
171 171 def getchangedfiles(self, rev, i):
172 172 """Return the files changed by rev compared to parent[i].
173 173
174 174 i is an index selecting one of the parents of rev. The return
175 175 value should be the list of files that are different in rev and
176 176 this parent.
177 177
178 178 If rev has no parents, i is None.
179 179
180 180 This function is only needed to support --filemap
181 181 """
182 182 raise NotImplementedError
183 183
184 184 def converted(self, rev, sinkrev):
185 185 '''Notify the source that a revision has been converted.'''
186 186
187 187 def hasnativeorder(self):
188 188 """Return true if this source has a meaningful, native revision
189 189 order. For instance, Mercurial revisions are store sequentially
190 190 while there is no such global ordering with Darcs.
191 191 """
192 192 return False
193 193
194 194 def hasnativeclose(self):
195 195 """Return true if this source has ability to close branch.
196 196 """
197 197 return False
198 198
199 199 def lookuprev(self, rev):
200 200 """If rev is a meaningful revision reference in source, return
201 201 the referenced identifier in the same format used by getcommit().
202 202 return None otherwise.
203 203 """
204 204 return None
205 205
206 206 def getbookmarks(self):
207 207 """Return the bookmarks as a dictionary of name: revision
208 208
209 209 Bookmark names are to be UTF-8 strings.
210 210 """
211 211 return {}
212 212
213 213 def checkrevformat(self, revstr, mapname='splicemap'):
214 214 """revstr is a string that describes a revision in the given
215 215 source control system. Return true if revstr has correct
216 216 format.
217 217 """
218 218 return True
219 219
220 220 class converter_sink(object):
221 221 """Conversion sink (target) interface"""
222 222
223 223 def __init__(self, ui, repotype, path):
224 224 """Initialize conversion sink (or raise NoRepo("message")
225 225 exception if path is not a valid repository)
226 226
227 227 created is a list of paths to remove if a fatal error occurs
228 228 later"""
229 229 self.ui = ui
230 230 self.path = path
231 231 self.created = []
232 232 self.repotype = repotype
233 233
234 234 def revmapfile(self):
235 235 """Path to a file that will contain lines
236 236 source_rev_id sink_rev_id
237 237 mapping equivalent revision identifiers for each system."""
238 238 raise NotImplementedError
239 239
240 240 def authorfile(self):
241 241 """Path to a file that will contain lines
242 242 srcauthor=dstauthor
243 243 mapping equivalent authors identifiers for each system."""
244 244 return None
245 245
246 246 def putcommit(self, files, copies, parents, commit, source, revmap, full,
247 247 cleanp2):
248 248 """Create a revision with all changed files listed in 'files'
249 249 and having listed parents. 'commit' is a commit object
250 250 containing at a minimum the author, date, and message for this
251 251 changeset. 'files' is a list of (path, version) tuples,
252 252 'copies' is a dictionary mapping destinations to sources,
253 253 'source' is the source repository, and 'revmap' is a mapfile
254 254 of source revisions to converted revisions. Only getfile() and
255 255 lookuprev() should be called on 'source'. 'full' means that 'files'
256 256 is complete and all other files should be removed.
257 257 'cleanp2' is a set of the filenames that are unchanged from p2
258 258 (only in the common merge case where there two parents).
259 259
260 260 Note that the sink repository is not told to update itself to
261 261 a particular revision (or even what that revision would be)
262 262 before it receives the file data.
263 263 """
264 264 raise NotImplementedError
265 265
266 266 def puttags(self, tags):
267 267 """Put tags into sink.
268 268
269 269 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
270 270 Return a pair (tag_revision, tag_parent_revision), or (None, None)
271 271 if nothing was changed.
272 272 """
273 273 raise NotImplementedError
274 274
275 275 def setbranch(self, branch, pbranches):
276 276 """Set the current branch name. Called before the first putcommit
277 277 on the branch.
278 278 branch: branch name for subsequent commits
279 279 pbranches: (converted parent revision, parent branch) tuples"""
280 280
281 281 def setfilemapmode(self, active):
282 282 """Tell the destination that we're using a filemap
283 283
284 284 Some converter_sources (svn in particular) can claim that a file
285 285 was changed in a revision, even if there was no change. This method
286 286 tells the destination that we're using a filemap and that it should
287 287 filter empty revisions.
288 288 """
289 289
290 290 def before(self):
291 291 pass
292 292
293 293 def after(self):
294 294 pass
295 295
296 296 def putbookmarks(self, bookmarks):
297 297 """Put bookmarks into sink.
298 298
299 299 bookmarks: {bookmarkname: sink_rev_id, ...}
300 300 where bookmarkname is an UTF-8 string.
301 301 """
302 302
303 303 def hascommitfrommap(self, rev):
304 304 """Return False if a rev mentioned in a filemap is known to not be
305 305 present."""
306 306 raise NotImplementedError
307 307
308 308 def hascommitforsplicemap(self, rev):
309 309 """This method is for the special needs for splicemap handling and not
310 310 for general use. Returns True if the sink contains rev, aborts on some
311 311 special cases."""
312 312 raise NotImplementedError
313 313
314 314 class commandline(object):
315 315 def __init__(self, ui, command):
316 316 self.ui = ui
317 317 self.command = command
318 318
319 319 def prerun(self):
320 320 pass
321 321
322 322 def postrun(self):
323 323 pass
324 324
325 325 def _cmdline(self, cmd, *args, **kwargs):
326 326 kwargs = pycompat.byteskwargs(kwargs)
327 327 cmdline = [self.command, cmd] + list(args)
328 328 for k, v in kwargs.iteritems():
329 329 if len(k) == 1:
330 330 cmdline.append('-' + k)
331 331 else:
332 332 cmdline.append('--' + k.replace('_', '-'))
333 333 try:
334 334 if len(k) == 1:
335 335 cmdline.append('' + v)
336 336 else:
337 337 cmdline[-1] += '=' + v
338 338 except TypeError:
339 339 pass
340 340 cmdline = [util.shellquote(arg) for arg in cmdline]
341 341 if not self.ui.debugflag:
342 342 cmdline += ['2>', os.devnull]
343 343 cmdline = ' '.join(cmdline)
344 344 return cmdline
345 345
346 346 def _run(self, cmd, *args, **kwargs):
347 347 def popen(cmdline):
348 348 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
349 349 close_fds=util.closefds,
350 350 stdout=subprocess.PIPE)
351 351 return p
352 352 return self._dorun(popen, cmd, *args, **kwargs)
353 353
354 354 def _run2(self, cmd, *args, **kwargs):
355 355 return self._dorun(util.popen2, cmd, *args, **kwargs)
356 356
357 357 def _run3(self, cmd, *args, **kwargs):
358 358 return self._dorun(util.popen3, cmd, *args, **kwargs)
359 359
360 360 def _dorun(self, openfunc, cmd, *args, **kwargs):
361 361 cmdline = self._cmdline(cmd, *args, **kwargs)
362 362 self.ui.debug('running: %s\n' % (cmdline,))
363 363 self.prerun()
364 364 try:
365 365 return openfunc(cmdline)
366 366 finally:
367 367 self.postrun()
368 368
369 369 def run(self, cmd, *args, **kwargs):
370 370 p = self._run(cmd, *args, **kwargs)
371 371 output = p.communicate()[0]
372 372 self.ui.debug(output)
373 373 return output, p.returncode
374 374
375 375 def runlines(self, cmd, *args, **kwargs):
376 376 p = self._run(cmd, *args, **kwargs)
377 377 output = p.stdout.readlines()
378 378 p.wait()
379 379 self.ui.debug(''.join(output))
380 380 return output, p.returncode
381 381
382 382 def checkexit(self, status, output=''):
383 383 if status:
384 384 if output:
385 385 self.ui.warn(_('%s error:\n') % self.command)
386 386 self.ui.warn(output)
387 387 msg = util.explainexit(status)[0]
388 388 raise error.Abort('%s %s' % (self.command, msg))
389 389
390 390 def run0(self, cmd, *args, **kwargs):
391 391 output, status = self.run(cmd, *args, **kwargs)
392 392 self.checkexit(status, output)
393 393 return output
394 394
395 395 def runlines0(self, cmd, *args, **kwargs):
396 396 output, status = self.runlines(cmd, *args, **kwargs)
397 397 self.checkexit(status, ''.join(output))
398 398 return output
399 399
400 400 @propertycache
401 401 def argmax(self):
402 402 # POSIX requires at least 4096 bytes for ARG_MAX
403 403 argmax = 4096
404 404 try:
405 405 argmax = os.sysconf("SC_ARG_MAX")
406 406 except (AttributeError, ValueError):
407 407 pass
408 408
409 409 # Windows shells impose their own limits on command line length,
410 410 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
411 411 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
412 412 # details about cmd.exe limitations.
413 413
414 414 # Since ARG_MAX is for command line _and_ environment, lower our limit
415 415 # (and make happy Windows shells while doing this).
416 416 return argmax // 2 - 1
417 417
418 418 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
419 419 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
420 420 limit = self.argmax - cmdlen
421 bytes = 0
421 numbytes = 0
422 422 fl = []
423 423 for fn in arglist:
424 424 b = len(fn) + 3
425 if bytes + b < limit or len(fl) == 0:
425 if numbytes + b < limit or len(fl) == 0:
426 426 fl.append(fn)
427 bytes += b
427 numbytes += b
428 428 else:
429 429 yield fl
430 430 fl = [fn]
431 bytes = b
431 numbytes = b
432 432 if fl:
433 433 yield fl
434 434
435 435 def xargs(self, arglist, cmd, *args, **kwargs):
436 436 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
437 437 self.run0(cmd, *(list(args) + l), **kwargs)
438 438
439 439 class mapfile(dict):
440 440 def __init__(self, ui, path):
441 441 super(mapfile, self).__init__()
442 442 self.ui = ui
443 443 self.path = path
444 444 self.fp = None
445 445 self.order = []
446 446 self._read()
447 447
448 448 def _read(self):
449 449 if not self.path:
450 450 return
451 451 try:
452 452 fp = open(self.path, 'rb')
453 453 except IOError as err:
454 454 if err.errno != errno.ENOENT:
455 455 raise
456 456 return
457 457 for i, line in enumerate(util.iterfile(fp)):
458 458 line = line.splitlines()[0].rstrip()
459 459 if not line:
460 460 # Ignore blank lines
461 461 continue
462 462 try:
463 463 key, value = line.rsplit(' ', 1)
464 464 except ValueError:
465 465 raise error.Abort(
466 466 _('syntax error in %s(%d): key/value pair expected')
467 467 % (self.path, i + 1))
468 468 if key not in self:
469 469 self.order.append(key)
470 470 super(mapfile, self).__setitem__(key, value)
471 471 fp.close()
472 472
473 473 def __setitem__(self, key, value):
474 474 if self.fp is None:
475 475 try:
476 476 self.fp = open(self.path, 'ab')
477 477 except IOError as err:
478 478 raise error.Abort(
479 479 _('could not open map file %r: %s') %
480 480 (self.path, encoding.strtolocal(err.strerror)))
481 481 self.fp.write(util.tonativeeol('%s %s\n' % (key, value)))
482 482 self.fp.flush()
483 483 super(mapfile, self).__setitem__(key, value)
484 484
485 485 def close(self):
486 486 if self.fp:
487 487 self.fp.close()
488 488 self.fp = None
489 489
490 490 def makedatetimestamp(t):
491 491 """Like util.makedate() but for time t instead of current time"""
492 492 delta = (datetime.datetime.utcfromtimestamp(t) -
493 493 datetime.datetime.fromtimestamp(t))
494 494 tz = delta.days * 86400 + delta.seconds
495 495 return t, tz
General Comments 0
You need to be logged in to leave comments. Login now