##// END OF EJS Templates
py3: use raw strings in line continuation (convert ext)...
Mateusz Kwapich -
r30132:72f75eda default
parent child Browse files
Show More
@@ -1,1354 +1,1354 b''
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4 from __future__ import absolute_import
5 5
6 6 import os
7 7 import re
8 8 import sys
9 9 import tempfile
10 10 import xml.dom.minidom
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 encoding,
15 15 error,
16 16 scmutil,
17 17 strutil,
18 18 util,
19 19 )
20 20
21 21 from . import common
22 22
23 23 pickle = util.pickle
24 24 stringio = util.stringio
25 25 propertycache = util.propertycache
26 26 urlerr = util.urlerr
27 27 urlreq = util.urlreq
28 28
29 29 commandline = common.commandline
30 30 commit = common.commit
31 31 converter_sink = common.converter_sink
32 32 converter_source = common.converter_source
33 33 decodeargs = common.decodeargs
34 34 encodeargs = common.encodeargs
35 35 makedatetimestamp = common.makedatetimestamp
36 36 mapfile = common.mapfile
37 37 MissingTool = common.MissingTool
38 38 NoRepo = common.NoRepo
39 39
40 40 # Subversion stuff. Works best with very recent Python SVN bindings
41 41 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
42 42 # these bindings.
43 43
44 44 try:
45 45 import svn
46 46 import svn.client
47 47 import svn.core
48 48 import svn.ra
49 49 import svn.delta
50 50 from . import transport
51 51 import warnings
52 52 warnings.filterwarnings('ignore',
53 53 module='svn.core',
54 54 category=DeprecationWarning)
55 55 svn.core.SubversionException # trigger import to catch error
56 56
57 57 except ImportError:
58 58 svn = None
59 59
60 60 class SvnPathNotFound(Exception):
61 61 pass
62 62
63 63 def revsplit(rev):
64 64 """Parse a revision string and return (uuid, path, revnum).
65 65 >>> revsplit('svn:a2147622-4a9f-4db4-a8d3-13562ff547b2'
66 66 ... '/proj%20B/mytrunk/mytrunk@1')
67 67 ('a2147622-4a9f-4db4-a8d3-13562ff547b2', '/proj%20B/mytrunk/mytrunk', 1)
68 68 >>> revsplit('svn:8af66a51-67f5-4354-b62c-98d67cc7be1d@1')
69 69 ('', '', 1)
70 70 >>> revsplit('@7')
71 71 ('', '', 7)
72 72 >>> revsplit('7')
73 73 ('', '', 0)
74 74 >>> revsplit('bad')
75 75 ('', '', 0)
76 76 """
77 77 parts = rev.rsplit('@', 1)
78 78 revnum = 0
79 79 if len(parts) > 1:
80 80 revnum = int(parts[1])
81 81 parts = parts[0].split('/', 1)
82 82 uuid = ''
83 83 mod = ''
84 84 if len(parts) > 1 and parts[0].startswith('svn:'):
85 85 uuid = parts[0][4:]
86 86 mod = '/' + parts[1]
87 87 return uuid, mod, revnum
88 88
89 89 def quote(s):
90 90 # As of svn 1.7, many svn calls expect "canonical" paths. In
91 91 # theory, we should call svn.core.*canonicalize() on all paths
92 92 # before passing them to the API. Instead, we assume the base url
93 93 # is canonical and copy the behaviour of svn URL encoding function
94 94 # so we can extend it safely with new components. The "safe"
95 95 # characters were taken from the "svn_uri__char_validity" table in
96 96 # libsvn_subr/path.c.
97 97 return urlreq.quote(s, "!$&'()*+,-./:=@_~")
98 98
99 99 def geturl(path):
100 100 try:
101 101 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
102 102 except svn.core.SubversionException:
103 103 # svn.client.url_from_path() fails with local repositories
104 104 pass
105 105 if os.path.isdir(path):
106 106 path = os.path.normpath(os.path.abspath(path))
107 107 if os.name == 'nt':
108 108 path = '/' + util.normpath(path)
109 109 # Module URL is later compared with the repository URL returned
110 110 # by svn API, which is UTF-8.
111 111 path = encoding.tolocal(path)
112 112 path = 'file://%s' % quote(path)
113 113 return svn.core.svn_path_canonicalize(path)
114 114
115 115 def optrev(number):
116 116 optrev = svn.core.svn_opt_revision_t()
117 117 optrev.kind = svn.core.svn_opt_revision_number
118 118 optrev.value.number = number
119 119 return optrev
120 120
121 121 class changedpath(object):
122 122 def __init__(self, p):
123 123 self.copyfrom_path = p.copyfrom_path
124 124 self.copyfrom_rev = p.copyfrom_rev
125 125 self.action = p.action
126 126
127 127 def get_log_child(fp, url, paths, start, end, limit=0,
128 128 discover_changed_paths=True, strict_node_history=False):
129 129 protocol = -1
130 130 def receiver(orig_paths, revnum, author, date, message, pool):
131 131 paths = {}
132 132 if orig_paths is not None:
133 133 for k, v in orig_paths.iteritems():
134 134 paths[k] = changedpath(v)
135 135 pickle.dump((paths, revnum, author, date, message),
136 136 fp, protocol)
137 137
138 138 try:
139 139 # Use an ra of our own so that our parent can consume
140 140 # our results without confusing the server.
141 141 t = transport.SvnRaTransport(url=url)
142 142 svn.ra.get_log(t.ra, paths, start, end, limit,
143 143 discover_changed_paths,
144 144 strict_node_history,
145 145 receiver)
146 146 except IOError:
147 147 # Caller may interrupt the iteration
148 148 pickle.dump(None, fp, protocol)
149 149 except Exception as inst:
150 150 pickle.dump(str(inst), fp, protocol)
151 151 else:
152 152 pickle.dump(None, fp, protocol)
153 153 fp.close()
154 154 # With large history, cleanup process goes crazy and suddenly
155 155 # consumes *huge* amount of memory. The output file being closed,
156 156 # there is no need for clean termination.
157 157 os._exit(0)
158 158
159 159 def debugsvnlog(ui, **opts):
160 160 """Fetch SVN log in a subprocess and channel them back to parent to
161 161 avoid memory collection issues.
162 162 """
163 163 if svn is None:
164 164 raise error.Abort(_('debugsvnlog could not load Subversion python '
165 165 'bindings'))
166 166
167 167 util.setbinary(sys.stdin)
168 168 util.setbinary(sys.stdout)
169 169 args = decodeargs(sys.stdin.read())
170 170 get_log_child(sys.stdout, *args)
171 171
172 172 class logstream(object):
173 173 """Interruptible revision log iterator."""
174 174 def __init__(self, stdout):
175 175 self._stdout = stdout
176 176
177 177 def __iter__(self):
178 178 while True:
179 179 try:
180 180 entry = pickle.load(self._stdout)
181 181 except EOFError:
182 182 raise error.Abort(_('Mercurial failed to run itself, check'
183 183 ' hg executable is in PATH'))
184 184 try:
185 185 orig_paths, revnum, author, date, message = entry
186 186 except (TypeError, ValueError):
187 187 if entry is None:
188 188 break
189 189 raise error.Abort(_("log stream exception '%s'") % entry)
190 190 yield entry
191 191
192 192 def close(self):
193 193 if self._stdout:
194 194 self._stdout.close()
195 195 self._stdout = None
196 196
197 197 class directlogstream(list):
198 198 """Direct revision log iterator.
199 199 This can be used for debugging and development but it will probably leak
200 200 memory and is not suitable for real conversions."""
201 201 def __init__(self, url, paths, start, end, limit=0,
202 202 discover_changed_paths=True, strict_node_history=False):
203 203
204 204 def receiver(orig_paths, revnum, author, date, message, pool):
205 205 paths = {}
206 206 if orig_paths is not None:
207 207 for k, v in orig_paths.iteritems():
208 208 paths[k] = changedpath(v)
209 209 self.append((paths, revnum, author, date, message))
210 210
211 211 # Use an ra of our own so that our parent can consume
212 212 # our results without confusing the server.
213 213 t = transport.SvnRaTransport(url=url)
214 214 svn.ra.get_log(t.ra, paths, start, end, limit,
215 215 discover_changed_paths,
216 216 strict_node_history,
217 217 receiver)
218 218
219 219 def close(self):
220 220 pass
221 221
222 222 # Check to see if the given path is a local Subversion repo. Verify this by
223 223 # looking for several svn-specific files and directories in the given
224 224 # directory.
225 225 def filecheck(ui, path, proto):
226 226 for x in ('locks', 'hooks', 'format', 'db'):
227 227 if not os.path.exists(os.path.join(path, x)):
228 228 return False
229 229 return True
230 230
231 231 # Check to see if a given path is the root of an svn repo over http. We verify
232 232 # this by requesting a version-controlled URL we know can't exist and looking
233 233 # for the svn-specific "not found" XML.
234 234 def httpcheck(ui, path, proto):
235 235 try:
236 236 opener = urlreq.buildopener()
237 237 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
238 238 data = rsp.read()
239 239 except urlerr.httperror as inst:
240 240 if inst.code != 404:
241 241 # Except for 404 we cannot know for sure this is not an svn repo
242 242 ui.warn(_('svn: cannot probe remote repository, assume it could '
243 243 'be a subversion repository. Use --source-type if you '
244 244 'know better.\n'))
245 245 return True
246 246 data = inst.fp.read()
247 247 except Exception:
248 248 # Could be urlerr.urlerror if the URL is invalid or anything else.
249 249 return False
250 250 return '<m:human-readable errcode="160013">' in data
251 251
252 252 protomap = {'http': httpcheck,
253 253 'https': httpcheck,
254 254 'file': filecheck,
255 255 }
256 256 def issvnurl(ui, url):
257 257 try:
258 258 proto, path = url.split('://', 1)
259 259 if proto == 'file':
260 260 if (os.name == 'nt' and path[:1] == '/' and path[1:2].isalpha()
261 261 and path[2:6].lower() == '%3a/'):
262 262 path = path[:2] + ':/' + path[6:]
263 263 path = urlreq.url2pathname(path)
264 264 except ValueError:
265 265 proto = 'file'
266 266 path = os.path.abspath(url)
267 267 if proto == 'file':
268 268 path = util.pconvert(path)
269 269 check = protomap.get(proto, lambda *args: False)
270 270 while '/' in path:
271 271 if check(ui, path, proto):
272 272 return True
273 273 path = path.rsplit('/', 1)[0]
274 274 return False
275 275
276 276 # SVN conversion code stolen from bzr-svn and tailor
277 277 #
278 278 # Subversion looks like a versioned filesystem, branches structures
279 279 # are defined by conventions and not enforced by the tool. First,
280 280 # we define the potential branches (modules) as "trunk" and "branches"
281 281 # children directories. Revisions are then identified by their
282 282 # module and revision number (and a repository identifier).
283 283 #
284 284 # The revision graph is really a tree (or a forest). By default, a
285 285 # revision parent is the previous revision in the same module. If the
286 286 # module directory is copied/moved from another module then the
287 287 # revision is the module root and its parent the source revision in
288 288 # the parent module. A revision has at most one parent.
289 289 #
290 290 class svn_source(converter_source):
291 291 def __init__(self, ui, url, revs=None):
292 292 super(svn_source, self).__init__(ui, url, revs=revs)
293 293
294 294 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
295 295 (os.path.exists(url) and
296 296 os.path.exists(os.path.join(url, '.svn'))) or
297 297 issvnurl(ui, url)):
298 298 raise NoRepo(_("%s does not look like a Subversion repository")
299 299 % url)
300 300 if svn is None:
301 301 raise MissingTool(_('could not load Subversion python bindings'))
302 302
303 303 try:
304 304 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
305 305 if version < (1, 4):
306 306 raise MissingTool(_('Subversion python bindings %d.%d found, '
307 307 '1.4 or later required') % version)
308 308 except AttributeError:
309 309 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
310 310 'or later required'))
311 311
312 312 self.lastrevs = {}
313 313
314 314 latest = None
315 315 try:
316 316 # Support file://path@rev syntax. Useful e.g. to convert
317 317 # deleted branches.
318 318 at = url.rfind('@')
319 319 if at >= 0:
320 320 latest = int(url[at + 1:])
321 321 url = url[:at]
322 322 except ValueError:
323 323 pass
324 324 self.url = geturl(url)
325 325 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
326 326 try:
327 327 self.transport = transport.SvnRaTransport(url=self.url)
328 328 self.ra = self.transport.ra
329 329 self.ctx = self.transport.client
330 330 self.baseurl = svn.ra.get_repos_root(self.ra)
331 331 # Module is either empty or a repository path starting with
332 332 # a slash and not ending with a slash.
333 333 self.module = urlreq.unquote(self.url[len(self.baseurl):])
334 334 self.prevmodule = None
335 335 self.rootmodule = self.module
336 336 self.commits = {}
337 337 self.paths = {}
338 338 self.uuid = svn.ra.get_uuid(self.ra)
339 339 except svn.core.SubversionException:
340 340 ui.traceback()
341 341 svnversion = '%d.%d.%d' % (svn.core.SVN_VER_MAJOR,
342 342 svn.core.SVN_VER_MINOR,
343 343 svn.core.SVN_VER_MICRO)
344 344 raise NoRepo(_("%s does not look like a Subversion repository "
345 345 "to libsvn version %s")
346 346 % (self.url, svnversion))
347 347
348 348 if revs:
349 349 if len(revs) > 1:
350 350 raise error.Abort(_('subversion source does not support '
351 351 'specifying multiple revisions'))
352 352 try:
353 353 latest = int(revs[0])
354 354 except ValueError:
355 355 raise error.Abort(_('svn: revision %s is not an integer') %
356 356 revs[0])
357 357
358 358 self.trunkname = self.ui.config('convert', 'svn.trunk',
359 359 'trunk').strip('/')
360 360 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
361 361 try:
362 362 self.startrev = int(self.startrev)
363 363 if self.startrev < 0:
364 364 self.startrev = 0
365 365 except ValueError:
366 366 raise error.Abort(_('svn: start revision %s is not an integer')
367 367 % self.startrev)
368 368
369 369 try:
370 370 self.head = self.latest(self.module, latest)
371 371 except SvnPathNotFound:
372 372 self.head = None
373 373 if not self.head:
374 374 raise error.Abort(_('no revision found in module %s')
375 375 % self.module)
376 376 self.last_changed = self.revnum(self.head)
377 377
378 378 self._changescache = (None, None)
379 379
380 380 if os.path.exists(os.path.join(url, '.svn/entries')):
381 381 self.wc = url
382 382 else:
383 383 self.wc = None
384 384 self.convertfp = None
385 385
386 386 def setrevmap(self, revmap):
387 387 lastrevs = {}
388 388 for revid in revmap.iterkeys():
389 389 uuid, module, revnum = revsplit(revid)
390 390 lastrevnum = lastrevs.setdefault(module, revnum)
391 391 if revnum > lastrevnum:
392 392 lastrevs[module] = revnum
393 393 self.lastrevs = lastrevs
394 394
395 395 def exists(self, path, optrev):
396 396 try:
397 397 svn.client.ls(self.url.rstrip('/') + '/' + quote(path),
398 398 optrev, False, self.ctx)
399 399 return True
400 400 except svn.core.SubversionException:
401 401 return False
402 402
403 403 def getheads(self):
404 404
405 405 def isdir(path, revnum):
406 406 kind = self._checkpath(path, revnum)
407 407 return kind == svn.core.svn_node_dir
408 408
409 409 def getcfgpath(name, rev):
410 410 cfgpath = self.ui.config('convert', 'svn.' + name)
411 411 if cfgpath is not None and cfgpath.strip() == '':
412 412 return None
413 413 path = (cfgpath or name).strip('/')
414 414 if not self.exists(path, rev):
415 415 if self.module.endswith(path) and name == 'trunk':
416 416 # we are converting from inside this directory
417 417 return None
418 418 if cfgpath:
419 419 raise error.Abort(_('expected %s to be at %r, but not found'
420 420 ) % (name, path))
421 421 return None
422 422 self.ui.note(_('found %s at %r\n') % (name, path))
423 423 return path
424 424
425 425 rev = optrev(self.last_changed)
426 426 oldmodule = ''
427 427 trunk = getcfgpath('trunk', rev)
428 428 self.tags = getcfgpath('tags', rev)
429 429 branches = getcfgpath('branches', rev)
430 430
431 431 # If the project has a trunk or branches, we will extract heads
432 432 # from them. We keep the project root otherwise.
433 433 if trunk:
434 434 oldmodule = self.module or ''
435 435 self.module += '/' + trunk
436 436 self.head = self.latest(self.module, self.last_changed)
437 437 if not self.head:
438 438 raise error.Abort(_('no revision found in module %s')
439 439 % self.module)
440 440
441 441 # First head in the list is the module's head
442 442 self.heads = [self.head]
443 443 if self.tags is not None:
444 444 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
445 445
446 446 # Check if branches bring a few more heads to the list
447 447 if branches:
448 448 rpath = self.url.strip('/')
449 449 branchnames = svn.client.ls(rpath + '/' + quote(branches),
450 450 rev, False, self.ctx)
451 451 for branch in sorted(branchnames):
452 452 module = '%s/%s/%s' % (oldmodule, branches, branch)
453 453 if not isdir(module, self.last_changed):
454 454 continue
455 455 brevid = self.latest(module, self.last_changed)
456 456 if not brevid:
457 457 self.ui.note(_('ignoring empty branch %s\n') % branch)
458 458 continue
459 459 self.ui.note(_('found branch %s at %d\n') %
460 460 (branch, self.revnum(brevid)))
461 461 self.heads.append(brevid)
462 462
463 463 if self.startrev and self.heads:
464 464 if len(self.heads) > 1:
465 465 raise error.Abort(_('svn: start revision is not supported '
466 466 'with more than one branch'))
467 467 revnum = self.revnum(self.heads[0])
468 468 if revnum < self.startrev:
469 469 raise error.Abort(
470 470 _('svn: no revision found after start revision %d')
471 471 % self.startrev)
472 472
473 473 return self.heads
474 474
475 475 def _getchanges(self, rev, full):
476 476 (paths, parents) = self.paths[rev]
477 477 copies = {}
478 478 if parents:
479 479 files, self.removed, copies = self.expandpaths(rev, paths, parents)
480 480 if full or not parents:
481 481 # Perform a full checkout on roots
482 482 uuid, module, revnum = revsplit(rev)
483 483 entries = svn.client.ls(self.baseurl + quote(module),
484 484 optrev(revnum), True, self.ctx)
485 485 files = [n for n, e in entries.iteritems()
486 486 if e.kind == svn.core.svn_node_file]
487 487 self.removed = set()
488 488
489 489 files.sort()
490 490 files = zip(files, [rev] * len(files))
491 491 return (files, copies)
492 492
493 493 def getchanges(self, rev, full):
494 494 # reuse cache from getchangedfiles
495 495 if self._changescache[0] == rev and not full:
496 496 (files, copies) = self._changescache[1]
497 497 else:
498 498 (files, copies) = self._getchanges(rev, full)
499 499 # caller caches the result, so free it here to release memory
500 500 del self.paths[rev]
501 501 return (files, copies, set())
502 502
503 503 def getchangedfiles(self, rev, i):
504 504 # called from filemap - cache computed values for reuse in getchanges
505 505 (files, copies) = self._getchanges(rev, False)
506 506 self._changescache = (rev, (files, copies))
507 507 return [f[0] for f in files]
508 508
509 509 def getcommit(self, rev):
510 510 if rev not in self.commits:
511 511 uuid, module, revnum = revsplit(rev)
512 512 self.module = module
513 513 self.reparent(module)
514 514 # We assume that:
515 515 # - requests for revisions after "stop" come from the
516 516 # revision graph backward traversal. Cache all of them
517 517 # down to stop, they will be used eventually.
518 518 # - requests for revisions before "stop" come to get
519 519 # isolated branches parents. Just fetch what is needed.
520 520 stop = self.lastrevs.get(module, 0)
521 521 if revnum < stop:
522 522 stop = revnum + 1
523 523 self._fetch_revisions(revnum, stop)
524 524 if rev not in self.commits:
525 525 raise error.Abort(_('svn: revision %s not found') % revnum)
526 526 revcommit = self.commits[rev]
527 527 # caller caches the result, so free it here to release memory
528 528 del self.commits[rev]
529 529 return revcommit
530 530
531 531 def checkrevformat(self, revstr, mapname='splicemap'):
532 532 """ fails if revision format does not match the correct format"""
533 533 if not re.match(r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
534 '[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]'
535 '{12,12}(.*)\@[0-9]+$',revstr):
534 r'[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]'
535 r'{12,12}(.*)\@[0-9]+$',revstr):
536 536 raise error.Abort(_('%s entry %s is not a valid revision'
537 537 ' identifier') % (mapname, revstr))
538 538
539 539 def numcommits(self):
540 540 return int(self.head.rsplit('@', 1)[1]) - self.startrev
541 541
542 542 def gettags(self):
543 543 tags = {}
544 544 if self.tags is None:
545 545 return tags
546 546
547 547 # svn tags are just a convention, project branches left in a
548 548 # 'tags' directory. There is no other relationship than
549 549 # ancestry, which is expensive to discover and makes them hard
550 550 # to update incrementally. Worse, past revisions may be
551 551 # referenced by tags far away in the future, requiring a deep
552 552 # history traversal on every calculation. Current code
553 553 # performs a single backward traversal, tracking moves within
554 554 # the tags directory (tag renaming) and recording a new tag
555 555 # everytime a project is copied from outside the tags
556 556 # directory. It also lists deleted tags, this behaviour may
557 557 # change in the future.
558 558 pendings = []
559 559 tagspath = self.tags
560 560 start = svn.ra.get_latest_revnum(self.ra)
561 561 stream = self._getlog([self.tags], start, self.startrev)
562 562 try:
563 563 for entry in stream:
564 564 origpaths, revnum, author, date, message = entry
565 565 if not origpaths:
566 566 origpaths = []
567 567 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
568 568 in origpaths.iteritems() if e.copyfrom_path]
569 569 # Apply moves/copies from more specific to general
570 570 copies.sort(reverse=True)
571 571
572 572 srctagspath = tagspath
573 573 if copies and copies[-1][2] == tagspath:
574 574 # Track tags directory moves
575 575 srctagspath = copies.pop()[0]
576 576
577 577 for source, sourcerev, dest in copies:
578 578 if not dest.startswith(tagspath + '/'):
579 579 continue
580 580 for tag in pendings:
581 581 if tag[0].startswith(dest):
582 582 tagpath = source + tag[0][len(dest):]
583 583 tag[:2] = [tagpath, sourcerev]
584 584 break
585 585 else:
586 586 pendings.append([source, sourcerev, dest])
587 587
588 588 # Filter out tags with children coming from different
589 589 # parts of the repository like:
590 590 # /tags/tag.1 (from /trunk:10)
591 591 # /tags/tag.1/foo (from /branches/foo:12)
592 592 # Here/tags/tag.1 discarded as well as its children.
593 593 # It happens with tools like cvs2svn. Such tags cannot
594 594 # be represented in mercurial.
595 595 addeds = dict((p, e.copyfrom_path) for p, e
596 596 in origpaths.iteritems()
597 597 if e.action == 'A' and e.copyfrom_path)
598 598 badroots = set()
599 599 for destroot in addeds:
600 600 for source, sourcerev, dest in pendings:
601 601 if (not dest.startswith(destroot + '/')
602 602 or source.startswith(addeds[destroot] + '/')):
603 603 continue
604 604 badroots.add(destroot)
605 605 break
606 606
607 607 for badroot in badroots:
608 608 pendings = [p for p in pendings if p[2] != badroot
609 609 and not p[2].startswith(badroot + '/')]
610 610
611 611 # Tell tag renamings from tag creations
612 612 renamings = []
613 613 for source, sourcerev, dest in pendings:
614 614 tagname = dest.split('/')[-1]
615 615 if source.startswith(srctagspath):
616 616 renamings.append([source, sourcerev, tagname])
617 617 continue
618 618 if tagname in tags:
619 619 # Keep the latest tag value
620 620 continue
621 621 # From revision may be fake, get one with changes
622 622 try:
623 623 tagid = self.latest(source, sourcerev)
624 624 if tagid and tagname not in tags:
625 625 tags[tagname] = tagid
626 626 except SvnPathNotFound:
627 627 # It happens when we are following directories
628 628 # we assumed were copied with their parents
629 629 # but were really created in the tag
630 630 # directory.
631 631 pass
632 632 pendings = renamings
633 633 tagspath = srctagspath
634 634 finally:
635 635 stream.close()
636 636 return tags
637 637
638 638 def converted(self, rev, destrev):
639 639 if not self.wc:
640 640 return
641 641 if self.convertfp is None:
642 642 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
643 643 'a')
644 644 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
645 645 self.convertfp.flush()
646 646
647 647 def revid(self, revnum, module=None):
648 648 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
649 649
650 650 def revnum(self, rev):
651 651 return int(rev.split('@')[-1])
652 652
653 653 def latest(self, path, stop=None):
654 654 """Find the latest revid affecting path, up to stop revision
655 655 number. If stop is None, default to repository latest
656 656 revision. It may return a revision in a different module,
657 657 since a branch may be moved without a change being
658 658 reported. Return None if computed module does not belong to
659 659 rootmodule subtree.
660 660 """
661 661 def findchanges(path, start, stop=None):
662 662 stream = self._getlog([path], start, stop or 1)
663 663 try:
664 664 for entry in stream:
665 665 paths, revnum, author, date, message = entry
666 666 if stop is None and paths:
667 667 # We do not know the latest changed revision,
668 668 # keep the first one with changed paths.
669 669 break
670 670 if revnum <= stop:
671 671 break
672 672
673 673 for p in paths:
674 674 if (not path.startswith(p) or
675 675 not paths[p].copyfrom_path):
676 676 continue
677 677 newpath = paths[p].copyfrom_path + path[len(p):]
678 678 self.ui.debug("branch renamed from %s to %s at %d\n" %
679 679 (path, newpath, revnum))
680 680 path = newpath
681 681 break
682 682 if not paths:
683 683 revnum = None
684 684 return revnum, path
685 685 finally:
686 686 stream.close()
687 687
688 688 if not path.startswith(self.rootmodule):
689 689 # Requests on foreign branches may be forbidden at server level
690 690 self.ui.debug('ignoring foreign branch %r\n' % path)
691 691 return None
692 692
693 693 if stop is None:
694 694 stop = svn.ra.get_latest_revnum(self.ra)
695 695 try:
696 696 prevmodule = self.reparent('')
697 697 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
698 698 self.reparent(prevmodule)
699 699 except svn.core.SubversionException:
700 700 dirent = None
701 701 if not dirent:
702 702 raise SvnPathNotFound(_('%s not found up to revision %d')
703 703 % (path, stop))
704 704
705 705 # stat() gives us the previous revision on this line of
706 706 # development, but it might be in *another module*. Fetch the
707 707 # log and detect renames down to the latest revision.
708 708 revnum, realpath = findchanges(path, stop, dirent.created_rev)
709 709 if revnum is None:
710 710 # Tools like svnsync can create empty revision, when
711 711 # synchronizing only a subtree for instance. These empty
712 712 # revisions created_rev still have their original values
713 713 # despite all changes having disappeared and can be
714 714 # returned by ra.stat(), at least when stating the root
715 715 # module. In that case, do not trust created_rev and scan
716 716 # the whole history.
717 717 revnum, realpath = findchanges(path, stop)
718 718 if revnum is None:
719 719 self.ui.debug('ignoring empty branch %r\n' % realpath)
720 720 return None
721 721
722 722 if not realpath.startswith(self.rootmodule):
723 723 self.ui.debug('ignoring foreign branch %r\n' % realpath)
724 724 return None
725 725 return self.revid(revnum, realpath)
726 726
727 727 def reparent(self, module):
728 728 """Reparent the svn transport and return the previous parent."""
729 729 if self.prevmodule == module:
730 730 return module
731 731 svnurl = self.baseurl + quote(module)
732 732 prevmodule = self.prevmodule
733 733 if prevmodule is None:
734 734 prevmodule = ''
735 735 self.ui.debug("reparent to %s\n" % svnurl)
736 736 svn.ra.reparent(self.ra, svnurl)
737 737 self.prevmodule = module
738 738 return prevmodule
739 739
740 740 def expandpaths(self, rev, paths, parents):
741 741 changed, removed = set(), set()
742 742 copies = {}
743 743
744 744 new_module, revnum = revsplit(rev)[1:]
745 745 if new_module != self.module:
746 746 self.module = new_module
747 747 self.reparent(self.module)
748 748
749 749 for i, (path, ent) in enumerate(paths):
750 750 self.ui.progress(_('scanning paths'), i, item=path,
751 751 total=len(paths), unit=_('paths'))
752 752 entrypath = self.getrelpath(path)
753 753
754 754 kind = self._checkpath(entrypath, revnum)
755 755 if kind == svn.core.svn_node_file:
756 756 changed.add(self.recode(entrypath))
757 757 if not ent.copyfrom_path or not parents:
758 758 continue
759 759 # Copy sources not in parent revisions cannot be
760 760 # represented, ignore their origin for now
761 761 pmodule, prevnum = revsplit(parents[0])[1:]
762 762 if ent.copyfrom_rev < prevnum:
763 763 continue
764 764 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
765 765 if not copyfrom_path:
766 766 continue
767 767 self.ui.debug("copied to %s from %s@%s\n" %
768 768 (entrypath, copyfrom_path, ent.copyfrom_rev))
769 769 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
770 770 elif kind == 0: # gone, but had better be a deleted *file*
771 771 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
772 772 pmodule, prevnum = revsplit(parents[0])[1:]
773 773 parentpath = pmodule + "/" + entrypath
774 774 fromkind = self._checkpath(entrypath, prevnum, pmodule)
775 775
776 776 if fromkind == svn.core.svn_node_file:
777 777 removed.add(self.recode(entrypath))
778 778 elif fromkind == svn.core.svn_node_dir:
779 779 oroot = parentpath.strip('/')
780 780 nroot = path.strip('/')
781 781 children = self._iterfiles(oroot, prevnum)
782 782 for childpath in children:
783 783 childpath = childpath.replace(oroot, nroot)
784 784 childpath = self.getrelpath("/" + childpath, pmodule)
785 785 if childpath:
786 786 removed.add(self.recode(childpath))
787 787 else:
788 788 self.ui.debug('unknown path in revision %d: %s\n' % \
789 789 (revnum, path))
790 790 elif kind == svn.core.svn_node_dir:
791 791 if ent.action == 'M':
792 792 # If the directory just had a prop change,
793 793 # then we shouldn't need to look for its children.
794 794 continue
795 795 if ent.action == 'R' and parents:
796 796 # If a directory is replacing a file, mark the previous
797 797 # file as deleted
798 798 pmodule, prevnum = revsplit(parents[0])[1:]
799 799 pkind = self._checkpath(entrypath, prevnum, pmodule)
800 800 if pkind == svn.core.svn_node_file:
801 801 removed.add(self.recode(entrypath))
802 802 elif pkind == svn.core.svn_node_dir:
803 803 # We do not know what files were kept or removed,
804 804 # mark them all as changed.
805 805 for childpath in self._iterfiles(pmodule, prevnum):
806 806 childpath = self.getrelpath("/" + childpath)
807 807 if childpath:
808 808 changed.add(self.recode(childpath))
809 809
810 810 for childpath in self._iterfiles(path, revnum):
811 811 childpath = self.getrelpath("/" + childpath)
812 812 if childpath:
813 813 changed.add(self.recode(childpath))
814 814
815 815 # Handle directory copies
816 816 if not ent.copyfrom_path or not parents:
817 817 continue
818 818 # Copy sources not in parent revisions cannot be
819 819 # represented, ignore their origin for now
820 820 pmodule, prevnum = revsplit(parents[0])[1:]
821 821 if ent.copyfrom_rev < prevnum:
822 822 continue
823 823 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
824 824 if not copyfrompath:
825 825 continue
826 826 self.ui.debug("mark %s came from %s:%d\n"
827 827 % (path, copyfrompath, ent.copyfrom_rev))
828 828 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
829 829 for childpath in children:
830 830 childpath = self.getrelpath("/" + childpath, pmodule)
831 831 if not childpath:
832 832 continue
833 833 copytopath = path + childpath[len(copyfrompath):]
834 834 copytopath = self.getrelpath(copytopath)
835 835 copies[self.recode(copytopath)] = self.recode(childpath)
836 836
837 837 self.ui.progress(_('scanning paths'), None)
838 838 changed.update(removed)
839 839 return (list(changed), removed, copies)
840 840
841 841 def _fetch_revisions(self, from_revnum, to_revnum):
842 842 if from_revnum < to_revnum:
843 843 from_revnum, to_revnum = to_revnum, from_revnum
844 844
845 845 self.child_cset = None
846 846
847 847 def parselogentry(orig_paths, revnum, author, date, message):
848 848 """Return the parsed commit object or None, and True if
849 849 the revision is a branch root.
850 850 """
851 851 self.ui.debug("parsing revision %d (%d changes)\n" %
852 852 (revnum, len(orig_paths)))
853 853
854 854 branched = False
855 855 rev = self.revid(revnum)
856 856 # branch log might return entries for a parent we already have
857 857
858 858 if rev in self.commits or revnum < to_revnum:
859 859 return None, branched
860 860
861 861 parents = []
862 862 # check whether this revision is the start of a branch or part
863 863 # of a branch renaming
864 864 orig_paths = sorted(orig_paths.iteritems())
865 865 root_paths = [(p, e) for p, e in orig_paths
866 866 if self.module.startswith(p)]
867 867 if root_paths:
868 868 path, ent = root_paths[-1]
869 869 if ent.copyfrom_path:
870 870 branched = True
871 871 newpath = ent.copyfrom_path + self.module[len(path):]
872 872 # ent.copyfrom_rev may not be the actual last revision
873 873 previd = self.latest(newpath, ent.copyfrom_rev)
874 874 if previd is not None:
875 875 prevmodule, prevnum = revsplit(previd)[1:]
876 876 if prevnum >= self.startrev:
877 877 parents = [previd]
878 878 self.ui.note(
879 879 _('found parent of branch %s at %d: %s\n') %
880 880 (self.module, prevnum, prevmodule))
881 881 else:
882 882 self.ui.debug("no copyfrom path, don't know what to do.\n")
883 883
884 884 paths = []
885 885 # filter out unrelated paths
886 886 for path, ent in orig_paths:
887 887 if self.getrelpath(path) is None:
888 888 continue
889 889 paths.append((path, ent))
890 890
891 891 # Example SVN datetime. Includes microseconds.
892 892 # ISO-8601 conformant
893 893 # '2007-01-04T17:35:00.902377Z'
894 894 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
895 895 if self.ui.configbool('convert', 'localtimezone'):
896 896 date = makedatetimestamp(date[0])
897 897
898 898 if message:
899 899 log = self.recode(message)
900 900 else:
901 901 log = ''
902 902
903 903 if author:
904 904 author = self.recode(author)
905 905 else:
906 906 author = ''
907 907
908 908 try:
909 909 branch = self.module.split("/")[-1]
910 910 if branch == self.trunkname:
911 911 branch = None
912 912 except IndexError:
913 913 branch = None
914 914
915 915 cset = commit(author=author,
916 916 date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
917 917 desc=log,
918 918 parents=parents,
919 919 branch=branch,
920 920 rev=rev)
921 921
922 922 self.commits[rev] = cset
923 923 # The parents list is *shared* among self.paths and the
924 924 # commit object. Both will be updated below.
925 925 self.paths[rev] = (paths, cset.parents)
926 926 if self.child_cset and not self.child_cset.parents:
927 927 self.child_cset.parents[:] = [rev]
928 928 self.child_cset = cset
929 929 return cset, branched
930 930
931 931 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
932 932 (self.module, from_revnum, to_revnum))
933 933
934 934 try:
935 935 firstcset = None
936 936 lastonbranch = False
937 937 stream = self._getlog([self.module], from_revnum, to_revnum)
938 938 try:
939 939 for entry in stream:
940 940 paths, revnum, author, date, message = entry
941 941 if revnum < self.startrev:
942 942 lastonbranch = True
943 943 break
944 944 if not paths:
945 945 self.ui.debug('revision %d has no entries\n' % revnum)
946 946 # If we ever leave the loop on an empty
947 947 # revision, do not try to get a parent branch
948 948 lastonbranch = lastonbranch or revnum == 0
949 949 continue
950 950 cset, lastonbranch = parselogentry(paths, revnum, author,
951 951 date, message)
952 952 if cset:
953 953 firstcset = cset
954 954 if lastonbranch:
955 955 break
956 956 finally:
957 957 stream.close()
958 958
959 959 if not lastonbranch and firstcset and not firstcset.parents:
960 960 # The first revision of the sequence (the last fetched one)
961 961 # has invalid parents if not a branch root. Find the parent
962 962 # revision now, if any.
963 963 try:
964 964 firstrevnum = self.revnum(firstcset.rev)
965 965 if firstrevnum > 1:
966 966 latest = self.latest(self.module, firstrevnum - 1)
967 967 if latest:
968 968 firstcset.parents.append(latest)
969 969 except SvnPathNotFound:
970 970 pass
971 971 except svn.core.SubversionException as xxx_todo_changeme:
972 972 (inst, num) = xxx_todo_changeme.args
973 973 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
974 974 raise error.Abort(_('svn: branch has no revision %s')
975 975 % to_revnum)
976 976 raise
977 977
978 978 def getfile(self, file, rev):
979 979 # TODO: ra.get_file transmits the whole file instead of diffs.
980 980 if file in self.removed:
981 981 return None, None
982 982 mode = ''
983 983 try:
984 984 new_module, revnum = revsplit(rev)[1:]
985 985 if self.module != new_module:
986 986 self.module = new_module
987 987 self.reparent(self.module)
988 988 io = stringio()
989 989 info = svn.ra.get_file(self.ra, file, revnum, io)
990 990 data = io.getvalue()
991 991 # ra.get_file() seems to keep a reference on the input buffer
992 992 # preventing collection. Release it explicitly.
993 993 io.close()
994 994 if isinstance(info, list):
995 995 info = info[-1]
996 996 mode = ("svn:executable" in info) and 'x' or ''
997 997 mode = ("svn:special" in info) and 'l' or mode
998 998 except svn.core.SubversionException as e:
999 999 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
1000 1000 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1001 1001 if e.apr_err in notfound: # File not found
1002 1002 return None, None
1003 1003 raise
1004 1004 if mode == 'l':
1005 1005 link_prefix = "link "
1006 1006 if data.startswith(link_prefix):
1007 1007 data = data[len(link_prefix):]
1008 1008 return data, mode
1009 1009
1010 1010 def _iterfiles(self, path, revnum):
1011 1011 """Enumerate all files in path at revnum, recursively."""
1012 1012 path = path.strip('/')
1013 1013 pool = svn.core.Pool()
1014 1014 rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
1015 1015 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
1016 1016 if path:
1017 1017 path += '/'
1018 1018 return ((path + p) for p, e in entries.iteritems()
1019 1019 if e.kind == svn.core.svn_node_file)
1020 1020
1021 1021 def getrelpath(self, path, module=None):
1022 1022 if module is None:
1023 1023 module = self.module
1024 1024 # Given the repository url of this wc, say
1025 1025 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
1026 1026 # extract the "entry" portion (a relative path) from what
1027 1027 # svn log --xml says, i.e.
1028 1028 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
1029 1029 # that is to say "tests/PloneTestCase.py"
1030 1030 if path.startswith(module):
1031 1031 relative = path.rstrip('/')[len(module):]
1032 1032 if relative.startswith('/'):
1033 1033 return relative[1:]
1034 1034 elif relative == '':
1035 1035 return relative
1036 1036
1037 1037 # The path is outside our tracked tree...
1038 1038 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
1039 1039 return None
1040 1040
1041 1041 def _checkpath(self, path, revnum, module=None):
1042 1042 if module is not None:
1043 1043 prevmodule = self.reparent('')
1044 1044 path = module + '/' + path
1045 1045 try:
1046 1046 # ra.check_path does not like leading slashes very much, it leads
1047 1047 # to PROPFIND subversion errors
1048 1048 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
1049 1049 finally:
1050 1050 if module is not None:
1051 1051 self.reparent(prevmodule)
1052 1052
1053 1053 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
1054 1054 strict_node_history=False):
1055 1055 # Normalize path names, svn >= 1.5 only wants paths relative to
1056 1056 # supplied URL
1057 1057 relpaths = []
1058 1058 for p in paths:
1059 1059 if not p.startswith('/'):
1060 1060 p = self.module + '/' + p
1061 1061 relpaths.append(p.strip('/'))
1062 1062 args = [self.baseurl, relpaths, start, end, limit,
1063 1063 discover_changed_paths, strict_node_history]
1064 1064 # developer config: convert.svn.debugsvnlog
1065 1065 if not self.ui.configbool('convert', 'svn.debugsvnlog', True):
1066 1066 return directlogstream(*args)
1067 1067 arg = encodeargs(args)
1068 1068 hgexe = util.hgexecutable()
1069 1069 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
1070 1070 stdin, stdout = util.popen2(util.quotecommand(cmd))
1071 1071 stdin.write(arg)
1072 1072 try:
1073 1073 stdin.close()
1074 1074 except IOError:
1075 1075 raise error.Abort(_('Mercurial failed to run itself, check'
1076 1076 ' hg executable is in PATH'))
1077 1077 return logstream(stdout)
1078 1078
1079 1079 pre_revprop_change = '''#!/bin/sh
1080 1080
1081 1081 REPOS="$1"
1082 1082 REV="$2"
1083 1083 USER="$3"
1084 1084 PROPNAME="$4"
1085 1085 ACTION="$5"
1086 1086
1087 1087 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
1088 1088 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
1089 1089 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
1090 1090
1091 1091 echo "Changing prohibited revision property" >&2
1092 1092 exit 1
1093 1093 '''
1094 1094
1095 1095 class svn_sink(converter_sink, commandline):
1096 1096 commit_re = re.compile(r'Committed revision (\d+).', re.M)
1097 1097 uuid_re = re.compile(r'Repository UUID:\s*(\S+)', re.M)
1098 1098
1099 1099 def prerun(self):
1100 1100 if self.wc:
1101 1101 os.chdir(self.wc)
1102 1102
1103 1103 def postrun(self):
1104 1104 if self.wc:
1105 1105 os.chdir(self.cwd)
1106 1106
1107 1107 def join(self, name):
1108 1108 return os.path.join(self.wc, '.svn', name)
1109 1109
1110 1110 def revmapfile(self):
1111 1111 return self.join('hg-shamap')
1112 1112
1113 1113 def authorfile(self):
1114 1114 return self.join('hg-authormap')
1115 1115
1116 1116 def __init__(self, ui, path):
1117 1117
1118 1118 converter_sink.__init__(self, ui, path)
1119 1119 commandline.__init__(self, ui, 'svn')
1120 1120 self.delete = []
1121 1121 self.setexec = []
1122 1122 self.delexec = []
1123 1123 self.copies = []
1124 1124 self.wc = None
1125 1125 self.cwd = os.getcwd()
1126 1126
1127 1127 created = False
1128 1128 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
1129 1129 self.wc = os.path.realpath(path)
1130 1130 self.run0('update')
1131 1131 else:
1132 1132 if not re.search(r'^(file|http|https|svn|svn\+ssh)\://', path):
1133 1133 path = os.path.realpath(path)
1134 1134 if os.path.isdir(os.path.dirname(path)):
1135 1135 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
1136 1136 ui.status(_('initializing svn repository %r\n') %
1137 1137 os.path.basename(path))
1138 1138 commandline(ui, 'svnadmin').run0('create', path)
1139 1139 created = path
1140 1140 path = util.normpath(path)
1141 1141 if not path.startswith('/'):
1142 1142 path = '/' + path
1143 1143 path = 'file://' + path
1144 1144
1145 1145 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
1146 1146 ui.status(_('initializing svn working copy %r\n')
1147 1147 % os.path.basename(wcpath))
1148 1148 self.run0('checkout', path, wcpath)
1149 1149
1150 1150 self.wc = wcpath
1151 1151 self.opener = scmutil.opener(self.wc)
1152 1152 self.wopener = scmutil.opener(self.wc)
1153 1153 self.childmap = mapfile(ui, self.join('hg-childmap'))
1154 1154 if util.checkexec(self.wc):
1155 1155 self.is_exec = util.isexec
1156 1156 else:
1157 1157 self.is_exec = None
1158 1158
1159 1159 if created:
1160 1160 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1161 1161 fp = open(hook, 'w')
1162 1162 fp.write(pre_revprop_change)
1163 1163 fp.close()
1164 1164 util.setflags(hook, False, True)
1165 1165
1166 1166 output = self.run0('info')
1167 1167 self.uuid = self.uuid_re.search(output).group(1).strip()
1168 1168
1169 1169 def wjoin(self, *names):
1170 1170 return os.path.join(self.wc, *names)
1171 1171
1172 1172 @propertycache
1173 1173 def manifest(self):
1174 1174 # As of svn 1.7, the "add" command fails when receiving
1175 1175 # already tracked entries, so we have to track and filter them
1176 1176 # ourselves.
1177 1177 m = set()
1178 1178 output = self.run0('ls', recursive=True, xml=True)
1179 1179 doc = xml.dom.minidom.parseString(output)
1180 1180 for e in doc.getElementsByTagName('entry'):
1181 1181 for n in e.childNodes:
1182 1182 if n.nodeType != n.ELEMENT_NODE or n.tagName != 'name':
1183 1183 continue
1184 1184 name = ''.join(c.data for c in n.childNodes
1185 1185 if c.nodeType == c.TEXT_NODE)
1186 1186 # Entries are compared with names coming from
1187 1187 # mercurial, so bytes with undefined encoding. Our
1188 1188 # best bet is to assume they are in local
1189 1189 # encoding. They will be passed to command line calls
1190 1190 # later anyway, so they better be.
1191 1191 m.add(encoding.tolocal(name.encode('utf-8')))
1192 1192 break
1193 1193 return m
1194 1194
1195 1195 def putfile(self, filename, flags, data):
1196 1196 if 'l' in flags:
1197 1197 self.wopener.symlink(data, filename)
1198 1198 else:
1199 1199 try:
1200 1200 if os.path.islink(self.wjoin(filename)):
1201 1201 os.unlink(filename)
1202 1202 except OSError:
1203 1203 pass
1204 1204 self.wopener.write(filename, data)
1205 1205
1206 1206 if self.is_exec:
1207 1207 if self.is_exec(self.wjoin(filename)):
1208 1208 if 'x' not in flags:
1209 1209 self.delexec.append(filename)
1210 1210 else:
1211 1211 if 'x' in flags:
1212 1212 self.setexec.append(filename)
1213 1213 util.setflags(self.wjoin(filename), False, 'x' in flags)
1214 1214
1215 1215 def _copyfile(self, source, dest):
1216 1216 # SVN's copy command pukes if the destination file exists, but
1217 1217 # our copyfile method expects to record a copy that has
1218 1218 # already occurred. Cross the semantic gap.
1219 1219 wdest = self.wjoin(dest)
1220 1220 exists = os.path.lexists(wdest)
1221 1221 if exists:
1222 1222 fd, tempname = tempfile.mkstemp(
1223 1223 prefix='hg-copy-', dir=os.path.dirname(wdest))
1224 1224 os.close(fd)
1225 1225 os.unlink(tempname)
1226 1226 os.rename(wdest, tempname)
1227 1227 try:
1228 1228 self.run0('copy', source, dest)
1229 1229 finally:
1230 1230 self.manifest.add(dest)
1231 1231 if exists:
1232 1232 try:
1233 1233 os.unlink(wdest)
1234 1234 except OSError:
1235 1235 pass
1236 1236 os.rename(tempname, wdest)
1237 1237
1238 1238 def dirs_of(self, files):
1239 1239 dirs = set()
1240 1240 for f in files:
1241 1241 if os.path.isdir(self.wjoin(f)):
1242 1242 dirs.add(f)
1243 1243 for i in strutil.rfindall(f, '/'):
1244 1244 dirs.add(f[:i])
1245 1245 return dirs
1246 1246
1247 1247 def add_dirs(self, files):
1248 1248 add_dirs = [d for d in sorted(self.dirs_of(files))
1249 1249 if d not in self.manifest]
1250 1250 if add_dirs:
1251 1251 self.manifest.update(add_dirs)
1252 1252 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1253 1253 return add_dirs
1254 1254
1255 1255 def add_files(self, files):
1256 1256 files = [f for f in files if f not in self.manifest]
1257 1257 if files:
1258 1258 self.manifest.update(files)
1259 1259 self.xargs(files, 'add', quiet=True)
1260 1260 return files
1261 1261
1262 1262 def addchild(self, parent, child):
1263 1263 self.childmap[parent] = child
1264 1264
1265 1265 def revid(self, rev):
1266 1266 return u"svn:%s@%s" % (self.uuid, rev)
1267 1267
1268 1268 def putcommit(self, files, copies, parents, commit, source, revmap, full,
1269 1269 cleanp2):
1270 1270 for parent in parents:
1271 1271 try:
1272 1272 return self.revid(self.childmap[parent])
1273 1273 except KeyError:
1274 1274 pass
1275 1275
1276 1276 # Apply changes to working copy
1277 1277 for f, v in files:
1278 1278 data, mode = source.getfile(f, v)
1279 1279 if data is None:
1280 1280 self.delete.append(f)
1281 1281 else:
1282 1282 self.putfile(f, mode, data)
1283 1283 if f in copies:
1284 1284 self.copies.append([copies[f], f])
1285 1285 if full:
1286 1286 self.delete.extend(sorted(self.manifest.difference(files)))
1287 1287 files = [f[0] for f in files]
1288 1288
1289 1289 entries = set(self.delete)
1290 1290 files = frozenset(files)
1291 1291 entries.update(self.add_dirs(files.difference(entries)))
1292 1292 if self.copies:
1293 1293 for s, d in self.copies:
1294 1294 self._copyfile(s, d)
1295 1295 self.copies = []
1296 1296 if self.delete:
1297 1297 self.xargs(self.delete, 'delete')
1298 1298 for f in self.delete:
1299 1299 self.manifest.remove(f)
1300 1300 self.delete = []
1301 1301 entries.update(self.add_files(files.difference(entries)))
1302 1302 if self.delexec:
1303 1303 self.xargs(self.delexec, 'propdel', 'svn:executable')
1304 1304 self.delexec = []
1305 1305 if self.setexec:
1306 1306 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1307 1307 self.setexec = []
1308 1308
1309 1309 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1310 1310 fp = os.fdopen(fd, 'w')
1311 1311 fp.write(commit.desc)
1312 1312 fp.close()
1313 1313 try:
1314 1314 output = self.run0('commit',
1315 1315 username=util.shortuser(commit.author),
1316 1316 file=messagefile,
1317 1317 encoding='utf-8')
1318 1318 try:
1319 1319 rev = self.commit_re.search(output).group(1)
1320 1320 except AttributeError:
1321 1321 if parents and not files:
1322 1322 return parents[0]
1323 1323 self.ui.warn(_('unexpected svn output:\n'))
1324 1324 self.ui.warn(output)
1325 1325 raise error.Abort(_('unable to cope with svn output'))
1326 1326 if commit.rev:
1327 1327 self.run('propset', 'hg:convert-rev', commit.rev,
1328 1328 revprop=True, revision=rev)
1329 1329 if commit.branch and commit.branch != 'default':
1330 1330 self.run('propset', 'hg:convert-branch', commit.branch,
1331 1331 revprop=True, revision=rev)
1332 1332 for parent in parents:
1333 1333 self.addchild(parent, rev)
1334 1334 return self.revid(rev)
1335 1335 finally:
1336 1336 os.unlink(messagefile)
1337 1337
1338 1338 def puttags(self, tags):
1339 1339 self.ui.warn(_('writing Subversion tags is not yet implemented\n'))
1340 1340 return None, None
1341 1341
1342 1342 def hascommitfrommap(self, rev):
1343 1343 # We trust that revisions referenced in a map still is present
1344 1344 # TODO: implement something better if necessary and feasible
1345 1345 return True
1346 1346
1347 1347 def hascommitforsplicemap(self, rev):
1348 1348 # This is not correct as one can convert to an existing subversion
1349 1349 # repository and childmap would not list all revisions. Too bad.
1350 1350 if rev in self.childmap:
1351 1351 return True
1352 1352 raise error.Abort(_('splice map revision %s not found in subversion '
1353 1353 'child map (revision lookups are not implemented)')
1354 1354 % rev)
@@ -1,48 +1,34 b''
1 1 #require test-repo
2 2
3 3 $ . "$TESTDIR/helpers-testrepo.sh"
4 4 $ cd "$TESTDIR"/..
5 5
6 6 $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py
7 7 hgext/fsmonitor/pywatchman/__init__.py not using absolute_import
8 8 hgext/fsmonitor/pywatchman/__init__.py requires print_function
9 9 hgext/fsmonitor/pywatchman/capabilities.py not using absolute_import
10 10 hgext/fsmonitor/pywatchman/pybser.py not using absolute_import
11 11 i18n/check-translation.py not using absolute_import
12 12 setup.py not using absolute_import
13 13 tests/test-demandimport.py not using absolute_import
14 14
15 15 #if py3exe
16 16 $ hg files 'set:(**.py) - grep(pygments)' | sed 's|\\|/|g' \
17 17 > | xargs $PYTHON3 contrib/check-py3-compat.py \
18 18 > | sed 's/[0-9][0-9]*)$/*)/'
19 hgext/convert/bzr.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
20 hgext/convert/convcmd.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
21 hgext/convert/subversion.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
22 hgext/convert/transport.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
19 hgext/convert/transport.py: error importing: <ImportError> No module named 'svn.client' (error at transport.py:*)
23 20 hgext/fsmonitor/pywatchman/capabilities.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
24 21 hgext/fsmonitor/pywatchman/pybser.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
25 22 hgext/fsmonitor/watchmanclient.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
26 hgext/largefiles/basestore.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
27 hgext/largefiles/lfcommands.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
28 hgext/largefiles/lfutil.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
29 hgext/largefiles/localstore.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
30 hgext/largefiles/overrides.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
31 hgext/largefiles/proto.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
32 hgext/largefiles/remotestore.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
33 hgext/largefiles/reposetup.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
34 hgext/largefiles/storefactory.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
35 hgext/largefiles/uisetup.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
36 hgext/largefiles/wirestore.py: error importing: <SyntaxError> cannot mix bytes and nonbytes literals (subversion.py, line 533) (error at convcmd.py:*)
37 23 hgext/mq.py: error importing: <TypeError> __import__() argument 1 must be str, not bytes (error at extensions.py:*)
38 24 mercurial/scmwindows.py: error importing: <ImportError> No module named 'winreg' (error at scmwindows.py:*)
39 25 mercurial/win32.py: error importing: <ImportError> No module named 'msvcrt' (error at win32.py:*)
40 26 mercurial/windows.py: error importing: <ImportError> No module named 'msvcrt' (error at windows.py:*)
41 27
42 28 #endif
43 29
44 30 #if py3exe py3pygments
45 31 $ hg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
46 32 > | xargs $PYTHON3 contrib/check-py3-compat.py \
47 33 > | sed 's/[0-9][0-9]*)$/*)/'
48 34 #endif
General Comments 0
You need to be logged in to leave comments. Login now