##// END OF EJS Templates
convert: svn -- fix tags handling...
Kirill Smelkov -
r5462:91a522a6 default
parent child Browse files
Show More
@@ -1,655 +1,666 b''
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 # convert.svn.tags
12 # Relative path to tree of tags (default: "tags")
11 13 #
12 14 # Set these in a hgrc, or on the command line as follows:
13 15 #
14 16 # hg convert --config convert.svn.trunk=wackoname [...]
15 17
16 18 import locale
17 19 import os
18 20 import sys
19 21 import cPickle as pickle
20 22 from mercurial import util
21 23
22 24 # Subversion stuff. Works best with very recent Python SVN bindings
23 25 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
24 26 # these bindings.
25 27
26 28 from cStringIO import StringIO
27 29
28 30 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
29 31
30 32 try:
31 33 from svn.core import SubversionException, Pool
32 34 import svn
33 35 import svn.client
34 36 import svn.core
35 37 import svn.ra
36 38 import svn.delta
37 39 import transport
38 40 except ImportError:
39 41 pass
40 42
41 43 def geturl(path):
42 44 try:
43 45 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
44 46 except SubversionException:
45 47 pass
46 48 if os.path.isdir(path):
47 49 return 'file://%s' % os.path.normpath(os.path.abspath(path))
48 50 return path
49 51
50 52 def optrev(number):
51 53 optrev = svn.core.svn_opt_revision_t()
52 54 optrev.kind = svn.core.svn_opt_revision_number
53 55 optrev.value.number = number
54 56 return optrev
55 57
56 58 class changedpath(object):
57 59 def __init__(self, p):
58 60 self.copyfrom_path = p.copyfrom_path
59 61 self.copyfrom_rev = p.copyfrom_rev
60 62 self.action = p.action
61 63
62 64 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
63 65 strict_node_history=False):
64 66 protocol = -1
65 67 def receiver(orig_paths, revnum, author, date, message, pool):
66 68 if orig_paths is not None:
67 69 for k, v in orig_paths.iteritems():
68 70 orig_paths[k] = changedpath(v)
69 71 pickle.dump((orig_paths, revnum, author, date, message),
70 72 fp, protocol)
71 73
72 74 try:
73 75 # Use an ra of our own so that our parent can consume
74 76 # our results without confusing the server.
75 77 t = transport.SvnRaTransport(url=url)
76 78 svn.ra.get_log(t.ra, paths, start, end, limit,
77 79 discover_changed_paths,
78 80 strict_node_history,
79 81 receiver)
80 82 except SubversionException, (inst, num):
81 83 pickle.dump(num, fp, protocol)
82 84 else:
83 85 pickle.dump(None, fp, protocol)
84 86 fp.close()
85 87
86 88 def debugsvnlog(ui, **opts):
87 89 """Fetch SVN log in a subprocess and channel them back to parent to
88 90 avoid memory collection issues.
89 91 """
90 92 util.set_binary(sys.stdin)
91 93 util.set_binary(sys.stdout)
92 94 args = decodeargs(sys.stdin.read())
93 95 get_log_child(sys.stdout, *args)
94 96
95 97 # SVN conversion code stolen from bzr-svn and tailor
96 98 class svn_source(converter_source):
97 99 def __init__(self, ui, url, rev=None):
98 100 super(svn_source, self).__init__(ui, url, rev=rev)
99 101
100 102 try:
101 103 SubversionException
102 104 except NameError:
103 105 raise NoRepo('subversion python bindings could not be loaded')
104 106
105 107 self.encoding = locale.getpreferredencoding()
106 108 self.lastrevs = {}
107 109
108 110 latest = None
109 111 try:
110 112 # Support file://path@rev syntax. Useful e.g. to convert
111 113 # deleted branches.
112 114 at = url.rfind('@')
113 115 if at >= 0:
114 116 latest = int(url[at+1:])
115 117 url = url[:at]
116 118 except ValueError, e:
117 119 pass
118 120 self.url = geturl(url)
119 121 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
120 122 try:
121 123 self.transport = transport.SvnRaTransport(url=self.url)
122 124 self.ra = self.transport.ra
123 125 self.ctx = self.transport.client
124 126 self.base = svn.ra.get_repos_root(self.ra)
125 127 self.module = self.url[len(self.base):]
126 128 self.modulemap = {} # revision, module
127 129 self.commits = {}
128 130 self.paths = {}
129 131 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
130 132 except SubversionException, e:
131 133 ui.print_exc()
132 134 raise NoRepo("couldn't open SVN repo %s" % self.url)
133 135
134 136 if rev:
135 137 try:
136 138 latest = int(rev)
137 139 except ValueError:
138 140 raise util.Abort('svn: revision %s is not an integer' % rev)
139 141
140 142 try:
141 143 self.get_blacklist()
142 144 except IOError, e:
143 145 pass
144 146
145 147 self.last_changed = self.latest(self.module, latest)
146 148
147 149 self.head = self.revid(self.last_changed)
148 150 self._changescache = None
149 151
150 152 def setrevmap(self, revmap, order):
151 153 lastrevs = {}
152 154 for revid in revmap.keys():
153 155 uuid, module, revnum = self.revsplit(revid)
154 156 lastrevnum = lastrevs.setdefault(module, revnum)
155 157 if revnum > lastrevnum:
156 158 lastrevs[module] = revnum
157 159 self.lastrevs = lastrevs
158 160
159 161 def exists(self, path, optrev):
160 162 try:
161 163 svn.client.ls(self.url.rstrip('/') + '/' + path,
162 164 optrev, False, self.ctx)
163 165 return True
164 166 except SubversionException, err:
165 167 return False
166 168
167 169 def getheads(self):
168 170 # detect standard /branches, /tags, /trunk layout
169 171 rev = optrev(self.last_changed)
170 172 rpath = self.url.strip('/')
171 173 cfgtrunk = self.ui.config('convert', 'svn.trunk')
172 174 cfgbranches = self.ui.config('convert', 'svn.branches')
175 cfgtags = self.ui.config('convert', 'svn.tags')
173 176 trunk = (cfgtrunk or 'trunk').strip('/')
174 177 branches = (cfgbranches or 'branches').strip('/')
175 if self.exists(trunk, rev) and self.exists(branches, rev):
176 self.ui.note('found trunk at %r and branches at %r\n' %
177 (trunk, branches))
178 tags = (cfgtags or 'tags').strip('/')
179 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
180 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
181 (trunk, branches, tags))
178 182 oldmodule = self.module
179 183 self.module += '/' + trunk
180 184 lt = self.latest(self.module, self.last_changed)
181 185 self.head = self.revid(lt)
182 186 self.heads = [self.head]
183 187 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
184 188 self.ctx)
185 189 for branch in branchnames.keys():
186 190 if oldmodule:
187 module = '/' + oldmodule + '/' + branches + '/' + branch
191 module = oldmodule + '/' + branches + '/' + branch
188 192 else:
189 193 module = '/' + branches + '/' + branch
190 194 brevnum = self.latest(module, self.last_changed)
191 195 brev = self.revid(brevnum, module)
192 196 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
193 197 self.heads.append(brev)
194 elif cfgtrunk or cfgbranches:
195 raise util.Abort('trunk/branch layout expected, but not found')
198
199 if oldmodule:
200 self.tags = '%s/%s' % (oldmodule, tags)
201 else:
202 self.tags = '/%s' % tags
203
204 elif cfgtrunk or cfgbranches or cfgtags:
205 raise util.Abort('trunk/branch/tags layout expected, but not found')
196 206 else:
197 207 self.ui.note('working with one branch\n')
198 208 self.heads = [self.head]
209 self.tags = tags
199 210 return self.heads
200 211
201 212 def getfile(self, file, rev):
202 213 data, mode = self._getfile(file, rev)
203 214 self.modecache[(file, rev)] = mode
204 215 return data
205 216
206 217 def getmode(self, file, rev):
207 218 return self.modecache[(file, rev)]
208 219
209 220 def getchanges(self, rev):
210 221 if self._changescache and self._changescache[0] == rev:
211 222 return self._changescache[1]
212 223 self._changescache = None
213 224 self.modecache = {}
214 225 (paths, parents) = self.paths[rev]
215 226 files, copies = self.expandpaths(rev, paths, parents)
216 227 files.sort()
217 228 files = zip(files, [rev] * len(files))
218 229
219 230 # caller caches the result, so free it here to release memory
220 231 del self.paths[rev]
221 232 return (files, copies)
222 233
223 234 def getchangedfiles(self, rev, i):
224 235 changes = self.getchanges(rev)
225 236 self._changescache = (rev, changes)
226 237 return [f[0] for f in changes[0]]
227 238
228 239 def getcommit(self, rev):
229 240 if rev not in self.commits:
230 241 uuid, module, revnum = self.revsplit(rev)
231 242 self.module = module
232 243 self.reparent(module)
233 244 stop = self.lastrevs.get(module, 0)
234 245 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
235 246 commit = self.commits[rev]
236 247 # caller caches the result, so free it here to release memory
237 248 del self.commits[rev]
238 249 return commit
239 250
240 251 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
241 252 strict_node_history=False):
242 253
243 254 def parent(fp):
244 255 while True:
245 256 entry = pickle.load(fp)
246 257 try:
247 258 orig_paths, revnum, author, date, message = entry
248 259 except:
249 260 if entry is None:
250 261 break
251 262 raise SubversionException("child raised exception", entry)
252 263 yield entry
253 264
254 265 args = [self.url, paths, start, end, limit, discover_changed_paths,
255 266 strict_node_history]
256 267 arg = encodeargs(args)
257 268 hgexe = util.hgexecutable()
258 269 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
259 270 stdin, stdout = os.popen2(cmd, 'b')
260 271
261 272 stdin.write(arg)
262 273 stdin.close()
263 274
264 275 for p in parent(stdout):
265 276 yield p
266 277
267 278 def gettags(self):
268 279 tags = {}
269 280 start = self.revnum(self.head)
270 281 try:
271 for entry in self.get_log(['/tags'], 0, start):
282 for entry in self.get_log([self.tags], 0, start):
272 283 orig_paths, revnum, author, date, message = entry
273 284 for path in orig_paths:
274 if not path.startswith('/tags/'):
285 if not path.startswith(self.tags+'/'):
275 286 continue
276 287 ent = orig_paths[path]
277 288 source = ent.copyfrom_path
278 289 rev = ent.copyfrom_rev
279 tag = path.split('/', 2)[2]
290 tag = path.split('/')[-1]
280 291 tags[tag] = self.revid(rev, module=source)
281 292 except SubversionException, (inst, num):
282 293 self.ui.note('no tags found at revision %d\n' % start)
283 294 return tags
284 295
285 296 # -- helper functions --
286 297
287 298 def revid(self, revnum, module=None):
288 299 if not module:
289 300 module = self.module
290 301 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
291 302 revnum)
292 303
293 304 def revnum(self, rev):
294 305 return int(rev.split('@')[-1])
295 306
296 307 def revsplit(self, rev):
297 308 url, revnum = rev.encode(self.encoding).split('@', 1)
298 309 revnum = int(revnum)
299 310 parts = url.split('/', 1)
300 311 uuid = parts.pop(0)[4:]
301 312 mod = ''
302 313 if parts:
303 314 mod = '/' + parts[0]
304 315 return uuid, mod, revnum
305 316
306 317 def latest(self, path, stop=0):
307 318 'find the latest revision affecting path, up to stop'
308 319 if not stop:
309 320 stop = svn.ra.get_latest_revnum(self.ra)
310 321 try:
311 322 self.reparent('')
312 323 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
313 324 self.reparent(self.module)
314 325 except SubversionException:
315 326 dirent = None
316 327 if not dirent:
317 328 raise util.Abort('%s not found up to revision %d' % (path, stop))
318 329
319 330 return dirent.created_rev
320 331
321 332 def get_blacklist(self):
322 333 """Avoid certain revision numbers.
323 334 It is not uncommon for two nearby revisions to cancel each other
324 335 out, e.g. 'I copied trunk into a subdirectory of itself instead
325 336 of making a branch'. The converted repository is significantly
326 337 smaller if we ignore such revisions."""
327 338 self.blacklist = util.set()
328 339 blacklist = self.blacklist
329 340 for line in file("blacklist.txt", "r"):
330 341 if not line.startswith("#"):
331 342 try:
332 343 svn_rev = int(line.strip())
333 344 blacklist.add(svn_rev)
334 345 except ValueError, e:
335 346 pass # not an integer or a comment
336 347
337 348 def is_blacklisted(self, svn_rev):
338 349 return svn_rev in self.blacklist
339 350
340 351 def reparent(self, module):
341 352 svn_url = self.base + module
342 353 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
343 354 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
344 355
345 356 def expandpaths(self, rev, paths, parents):
346 357 def get_entry_from_path(path, module=self.module):
347 358 # Given the repository url of this wc, say
348 359 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
349 360 # extract the "entry" portion (a relative path) from what
350 361 # svn log --xml says, ie
351 362 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
352 363 # that is to say "tests/PloneTestCase.py"
353 364 if path.startswith(module):
354 365 relative = path[len(module):]
355 366 if relative.startswith('/'):
356 367 return relative[1:]
357 368 else:
358 369 return relative
359 370
360 371 # The path is outside our tracked tree...
361 372 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
362 373 return None
363 374
364 375 entries = []
365 376 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
366 377 copies = {}
367 378 revnum = self.revnum(rev)
368 379
369 380 if revnum in self.modulemap:
370 381 new_module = self.modulemap[revnum]
371 382 if new_module != self.module:
372 383 self.module = new_module
373 384 self.reparent(self.module)
374 385
375 386 for path, ent in paths:
376 387 entrypath = get_entry_from_path(path, module=self.module)
377 388 entry = entrypath.decode(self.encoding)
378 389
379 390 kind = svn.ra.check_path(self.ra, entrypath, revnum)
380 391 if kind == svn.core.svn_node_file:
381 392 if ent.copyfrom_path:
382 393 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
383 394 if copyfrom_path:
384 395 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
385 396 # It's probably important for hg that the source
386 397 # exists in the revision's parent, not just the
387 398 # ent.copyfrom_rev
388 399 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
389 400 if fromkind != 0:
390 401 copies[self.recode(entry)] = self.recode(copyfrom_path)
391 402 entries.append(self.recode(entry))
392 403 elif kind == 0: # gone, but had better be a deleted *file*
393 404 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
394 405
395 406 # if a branch is created but entries are removed in the same
396 407 # changeset, get the right fromrev
397 408 if parents:
398 409 uuid, old_module, fromrev = self.revsplit(parents[0])
399 410 else:
400 411 fromrev = revnum - 1
401 412 # might always need to be revnum - 1 in these 3 lines?
402 413 old_module = self.modulemap.get(fromrev, self.module)
403 414
404 415 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
405 416 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
406 417
407 418 def lookup_parts(p):
408 419 rc = None
409 420 parts = p.split("/")
410 421 for i in range(len(parts)):
411 422 part = "/".join(parts[:i])
412 423 info = part, copyfrom.get(part, None)
413 424 if info[1] is not None:
414 425 self.ui.debug("Found parent directory %s\n" % info[1])
415 426 rc = info
416 427 return rc
417 428
418 429 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
419 430
420 431 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
421 432
422 433 # need to remove fragment from lookup_parts and replace with copyfrom_path
423 434 if frompath is not None:
424 435 self.ui.debug("munge-o-matic\n")
425 436 self.ui.debug(entrypath + '\n')
426 437 self.ui.debug(entrypath[len(frompath):] + '\n')
427 438 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
428 439 fromrev = froment.copyfrom_rev
429 440 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
430 441
431 442 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
432 443 if fromkind == svn.core.svn_node_file: # a deleted file
433 444 entries.append(self.recode(entry))
434 445 elif fromkind == svn.core.svn_node_dir:
435 446 # print "Deleted/moved non-file:", revnum, path, ent
436 447 # children = self._find_children(path, revnum - 1)
437 448 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
438 449 # Sometimes this is tricky. For example: in
439 450 # The Subversion Repository revision 6940 a dir
440 451 # was copied and one of its files was deleted
441 452 # from the new location in the same commit. This
442 453 # code can't deal with that yet.
443 454 if ent.action == 'C':
444 455 children = self._find_children(path, fromrev)
445 456 else:
446 457 oroot = entrypath.strip('/')
447 458 nroot = path.strip('/')
448 459 children = self._find_children(oroot, fromrev)
449 460 children = [s.replace(oroot,nroot) for s in children]
450 461 # Mark all [files, not directories] as deleted.
451 462 for child in children:
452 463 # Can we move a child directory and its
453 464 # parent in the same commit? (probably can). Could
454 465 # cause problems if instead of revnum -1,
455 466 # we have to look in (copyfrom_path, revnum - 1)
456 467 entrypath = get_entry_from_path("/" + child, module=old_module)
457 468 if entrypath:
458 469 entry = self.recode(entrypath.decode(self.encoding))
459 470 if entry in copies:
460 471 # deleted file within a copy
461 472 del copies[entry]
462 473 else:
463 474 entries.append(entry)
464 475 else:
465 476 self.ui.debug('unknown path in revision %d: %s\n' % \
466 477 (revnum, path))
467 478 elif kind == svn.core.svn_node_dir:
468 479 # Should probably synthesize normal file entries
469 480 # and handle as above to clean up copy/rename handling.
470 481
471 482 # If the directory just had a prop change,
472 483 # then we shouldn't need to look for its children.
473 484 # Also this could create duplicate entries. Not sure
474 485 # whether this will matter. Maybe should make entries a set.
475 486 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
476 487 # This will fail if a directory was copied
477 488 # from another branch and then some of its files
478 489 # were deleted in the same transaction.
479 490 children = self._find_children(path, revnum)
480 491 children.sort()
481 492 for child in children:
482 493 # Can we move a child directory and its
483 494 # parent in the same commit? (probably can). Could
484 495 # cause problems if instead of revnum -1,
485 496 # we have to look in (copyfrom_path, revnum - 1)
486 497 entrypath = get_entry_from_path("/" + child, module=self.module)
487 498 # print child, self.module, entrypath
488 499 if entrypath:
489 500 # Need to filter out directories here...
490 501 kind = svn.ra.check_path(self.ra, entrypath, revnum)
491 502 if kind != svn.core.svn_node_dir:
492 503 entries.append(self.recode(entrypath))
493 504
494 505 # Copies here (must copy all from source)
495 506 # Probably not a real problem for us if
496 507 # source does not exist
497 508
498 509 # Can do this with the copy command "hg copy"
499 510 # if ent.copyfrom_path:
500 511 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
501 512 # module=self.module)
502 513 # copyto_entry = entrypath
503 514 #
504 515 # print "copy directory", copyfrom_entry, 'to', copyto_entry
505 516 #
506 517 # copies.append((copyfrom_entry, copyto_entry))
507 518
508 519 if ent.copyfrom_path:
509 520 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
510 521 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
511 522 if copyfrom_entry:
512 523 copyfrom[path] = ent
513 524 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
514 525
515 526 # Good, /probably/ a regular copy. Really should check
516 527 # to see whether the parent revision actually contains
517 528 # the directory in question.
518 529 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
519 530 children.sort()
520 531 for child in children:
521 532 entrypath = get_entry_from_path("/" + child, module=self.module)
522 533 if entrypath:
523 534 entry = entrypath.decode(self.encoding)
524 535 # print "COPY COPY From", copyfrom_entry, entry
525 536 copyto_path = path + entry[len(copyfrom_entry):]
526 537 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
527 538 # print "COPY", entry, "COPY To", copyto_entry
528 539 copies[self.recode(copyto_entry)] = self.recode(entry)
529 540 # copy from quux splort/quuxfile
530 541
531 542 return (entries, copies)
532 543
533 544 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
534 545 self.child_cset = None
535 546 def parselogentry(orig_paths, revnum, author, date, message):
536 547 self.ui.debug("parsing revision %d (%d changes)\n" %
537 548 (revnum, len(orig_paths)))
538 549
539 550 if revnum in self.modulemap:
540 551 new_module = self.modulemap[revnum]
541 552 if new_module != self.module:
542 553 self.module = new_module
543 554 self.reparent(self.module)
544 555
545 556 rev = self.revid(revnum)
546 557 # branch log might return entries for a parent we already have
547 558 if (rev in self.commits or
548 559 (revnum < self.lastrevs.get(self.module, 0))):
549 560 return
550 561
551 562 parents = []
552 563 # check whether this revision is the start of a branch
553 564 if self.module in orig_paths:
554 565 ent = orig_paths[self.module]
555 566 if ent.copyfrom_path:
556 567 # ent.copyfrom_rev may not be the actual last revision
557 568 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
558 569 self.modulemap[prev] = ent.copyfrom_path
559 570 parents = [self.revid(prev, ent.copyfrom_path)]
560 571 self.ui.note('found parent of branch %s at %d: %s\n' % \
561 572 (self.module, prev, ent.copyfrom_path))
562 573 else:
563 574 self.ui.debug("No copyfrom path, don't know what to do.\n")
564 575
565 576 self.modulemap[revnum] = self.module # track backwards in time
566 577
567 578 orig_paths = orig_paths.items()
568 579 orig_paths.sort()
569 580 paths = []
570 581 # filter out unrelated paths
571 582 for path, ent in orig_paths:
572 583 if not path.startswith(self.module):
573 584 self.ui.debug("boring@%s: %s\n" % (revnum, path))
574 585 continue
575 586 paths.append((path, ent))
576 587
577 588 self.paths[rev] = (paths, parents)
578 589
579 590 # Example SVN datetime. Includes microseconds.
580 591 # ISO-8601 conformant
581 592 # '2007-01-04T17:35:00.902377Z'
582 593 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
583 594
584 595 log = message and self.recode(message)
585 596 author = author and self.recode(author) or ''
586 597 try:
587 598 branch = self.module.split("/")[-1]
588 599 if branch == 'trunk':
589 600 branch = ''
590 601 except IndexError:
591 602 branch = None
592 603
593 604 cset = commit(author=author,
594 605 date=util.datestr(date),
595 606 desc=log,
596 607 parents=parents,
597 608 branch=branch,
598 609 rev=rev.encode('utf-8'))
599 610
600 611 self.commits[rev] = cset
601 612 if self.child_cset and not self.child_cset.parents:
602 613 self.child_cset.parents = [rev]
603 614 self.child_cset = cset
604 615
605 616 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
606 617 (self.module, from_revnum, to_revnum))
607 618
608 619 try:
609 620 for entry in self.get_log([self.module], from_revnum, to_revnum):
610 621 orig_paths, revnum, author, date, message = entry
611 622 if self.is_blacklisted(revnum):
612 623 self.ui.note('skipping blacklisted revision %d\n' % revnum)
613 624 continue
614 625 if orig_paths is None:
615 626 self.ui.debug('revision %d has no entries\n' % revnum)
616 627 continue
617 628 parselogentry(orig_paths, revnum, author, date, message)
618 629 except SubversionException, (inst, num):
619 630 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
620 631 raise NoSuchRevision(branch=self,
621 632 revision="Revision number %d" % to_revnum)
622 633 raise
623 634
624 635 def _getfile(self, file, rev):
625 636 io = StringIO()
626 637 # TODO: ra.get_file transmits the whole file instead of diffs.
627 638 mode = ''
628 639 try:
629 640 revnum = self.revnum(rev)
630 641 if self.module != self.modulemap[revnum]:
631 642 self.module = self.modulemap[revnum]
632 643 self.reparent(self.module)
633 644 info = svn.ra.get_file(self.ra, file, revnum, io)
634 645 if isinstance(info, list):
635 646 info = info[-1]
636 647 mode = ("svn:executable" in info) and 'x' or ''
637 648 mode = ("svn:special" in info) and 'l' or mode
638 649 except SubversionException, e:
639 650 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
640 651 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
641 652 if e.apr_err in notfound: # File not found
642 653 raise IOError()
643 654 raise
644 655 data = io.getvalue()
645 656 if mode == 'l':
646 657 link_prefix = "link "
647 658 if data.startswith(link_prefix):
648 659 data = data[len(link_prefix):]
649 660 return data, mode
650 661
651 662 def _find_children(self, path, revnum):
652 663 path = path.strip('/')
653 664 pool = Pool()
654 665 rpath = '/'.join([self.base, path]).strip('/')
655 666 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
@@ -1,60 +1,115 b''
1 1 #!/bin/sh
2 2
3 3 "$TESTDIR/hghave" svn svn-bindings || exit 80
4 4
5 5 fix_path()
6 6 {
7 7 tr '\\' /
8 8 }
9 9
10 10 echo "[extensions]" >> $HGRCPATH
11 11 echo "convert = " >> $HGRCPATH
12 12
13 13 svnadmin create svn-repo
14 14
15 15 echo % initial svn import
16 16 mkdir t
17 17 cd t
18 18 echo a > a
19 19 cd ..
20 20
21 21 svnpath=`pwd | fix_path`
22 22 # SVN wants all paths to start with a slash. Unfortunately,
23 23 # Windows ones don't. Handle that.
24 24 expr $svnpath : "\/" > /dev/null
25 25 if [ $? -ne 0 ]; then
26 26 svnpath='/'$svnpath
27 27 fi
28 28
29 29 svnurl=file://$svnpath/svn-repo/trunk
30 30 svn import -m init t $svnurl | fix_path
31 31
32 32 echo % update svn repository
33 33 svn co $svnurl t2 | fix_path
34 34 cd t2
35 35 echo b >> a
36 36 echo b > b
37 37 svn add b
38 38 svn ci -m changea
39 39 cd ..
40 40
41 41 echo % convert to hg once
42 42 hg convert $svnurl
43 43
44 44 echo % update svn repository again
45 45 cd t2
46 46 echo c >> a
47 47 echo c >> b
48 48 svn ci -m changeb
49 49 cd ..
50 50
51 51 echo % test incremental conversion
52 52 hg convert $svnurl
53 53
54 54 echo % test filemap
55 55 echo 'include b' > filemap
56 56 hg convert --filemap filemap $svnurl fmap
57 57 echo '[extensions]' >> $HGRCPATH
58 58 echo 'hgext.graphlog =' >> $HGRCPATH
59 59 hg glog -R fmap --template '#rev# #desc|firstline# files: #files#\n'
60 60
61 ########################################
62
63 echo "# now tests that it works with trunk/branches/tags layout"
64 echo
65 echo % initial svn import
66 mkdir projA
67 cd projA
68 mkdir trunk
69 mkdir branches
70 mkdir tags
71 cd ..
72
73 svnurl=file://$svnpath/svn-repo/projA
74 svn import -m "init projA" projA $svnurl | fix_path
75
76
77 echo % update svn repository
78 svn co $svnurl/trunk A | fix_path
79 cd A
80 echo hello > letter.txt
81 svn add letter.txt
82 svn ci -m hello
83
84 echo world >> letter.txt
85 svn ci -m world
86
87 svn copy -m "tag v0.1" $svnurl/trunk $svnurl/tags/v0.1
88
89 echo 'nice day today!' >> letter.txt
90 svn ci -m "nice day"
91 cd ..
92
93 echo % convert to hg once
94 hg convert $svnurl A-hg
95
96 echo % update svn repository again
97 cd A
98 echo "see second letter" >> letter.txt
99 echo "nice to meet you" > letter2.txt
100 svn add letter2.txt
101 svn ci -m "second letter"
102
103 svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
104
105 echo "blah-blah-blah" >> letter2.txt
106 svn ci -m "work in progress"
107 cd ..
108
109 echo % test incremental conversion
110 hg convert $svnurl A-hg
111
112 cd A-hg
113 hg glog --template '#rev# #desc|firstline# files: #files#\n'
114 hg tags -q
115 cd ..
@@ -1,44 +1,114 b''
1 1 % initial svn import
2 2 Adding t/a
3 3
4 4 Committed revision 1.
5 5 % update svn repository
6 6 A t2/a
7 7 Checked out revision 1.
8 8 A b
9 9 Sending a
10 10 Adding b
11 11 Transmitting file data ..
12 12 Committed revision 2.
13 13 % convert to hg once
14 14 assuming destination trunk-hg
15 15 initializing destination trunk-hg repository
16 16 scanning source...
17 17 sorting...
18 18 converting...
19 19 1 init
20 20 0 changea
21 21 % update svn repository again
22 22 Sending a
23 23 Sending b
24 24 Transmitting file data ..
25 25 Committed revision 3.
26 26 % test incremental conversion
27 27 assuming destination trunk-hg
28 28 destination trunk-hg is a Mercurial repository
29 29 scanning source...
30 30 sorting...
31 31 converting...
32 32 0 changeb
33 33 % test filemap
34 34 initializing destination fmap repository
35 35 scanning source...
36 36 sorting...
37 37 converting...
38 38 2 init
39 39 1 changea
40 40 0 changeb
41 41 o 1 changeb files: b
42 42 |
43 43 o 0 changea files: b
44 44
45 # now tests that it works with trunk/branches/tags layout
46
47 % initial svn import
48 Adding projA/trunk
49 Adding projA/branches
50 Adding projA/tags
51
52 Committed revision 4.
53 % update svn repository
54 Checked out revision 4.
55 A letter.txt
56 Adding letter.txt
57 Transmitting file data .
58 Committed revision 5.
59 Sending letter.txt
60 Transmitting file data .
61 Committed revision 6.
62
63 Committed revision 7.
64 Sending letter.txt
65 Transmitting file data .
66 Committed revision 8.
67 % convert to hg once
68 initializing destination A-hg repository
69 scanning source...
70 sorting...
71 converting...
72 3 init projA
73 2 hello
74 1 world
75 0 nice day
76 updating tags
77 % update svn repository again
78 A letter2.txt
79 Sending letter.txt
80 Adding letter2.txt
81 Transmitting file data ..
82 Committed revision 9.
83
84 Committed revision 10.
85 Sending letter2.txt
86 Transmitting file data .
87 Committed revision 11.
88 % test incremental conversion
89 destination A-hg is a Mercurial repository
90 scanning source...
91 sorting...
92 converting...
93 1 second letter
94 0 work in progress
95 updating tags
96 o 7 update tags files: .hgtags
97 |
98 o 6 work in progress files: letter2.txt
99 |
100 o 5 second letter files: letter.txt letter2.txt
101 |
102 o 4 update tags files: .hgtags
103 |
104 o 3 nice day files: letter.txt
105 |
106 o 2 world files: letter.txt
107 |
108 o 1 hello files: letter.txt
109 |
110 o 0 init projA files:
111
112 tip
113 v0.2
114 v0.1
General Comments 0
You need to be logged in to leave comments. Login now