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