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