##// END OF EJS Templates
Merge with crew
Matt Mackall -
r5877:5692bed8 merge default
parent child Browse files
Show More
@@ -0,0 +1,89 b''
1 #!/bin/sh
2
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
4
5 fix_path()
6 {
7 tr '\\' /
8 }
9
10 echo "[extensions]" >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
13
14 svnadmin create svn-repo
15
16 svnpath=`pwd | fix_path`
17 # SVN wants all paths to start with a slash. Unfortunately,
18 # Windows ones don't. Handle that.
19 expr $svnpath : "\/" > /dev/null
20 if [ $? -ne 0 ]; then
21 svnpath='/'$svnpath
22 fi
23
24 echo % initial svn import
25 mkdir projA
26 cd projA
27 mkdir trunk
28 mkdir branches
29 mkdir tags
30 cd ..
31
32 svnurl=file://$svnpath/svn-repo/projA
33 svn import -m "init projA" projA $svnurl | fix_path
34
35 echo % update svn repository
36 svn co $svnurl A | fix_path
37 cd A
38 echo hello > trunk/letter.txt
39 echo hey > trunk/letter2.txt
40 echo ho > trunk/letter3.txt
41 svn add trunk/letter.txt trunk/letter2.txt trunk/letter3.txt
42 svn ci -m hello
43
44 echo % branch to old letters
45 svn copy trunk branches/old
46 svn rm branches/old/letter3.txt
47 svn ci -m "branch trunk, remove letter3"
48 svn up
49
50 echo % update trunk
51 echo "what can I say ?" >> trunk/letter.txt
52 svn ci -m "change letter"
53
54 echo % update old branch
55 echo "what's up ?" >> branches/old/letter2.txt
56 svn ci -m "change letter2"
57
58 echo % create a cross-branch revision
59 svn move -m "move letter2" trunk/letter2.txt \
60 branches/old/letter3.txt
61 echo "I am fine" >> branches/old/letter3.txt
62 svn ci -m "move and update letter3.txt"
63
64 echo % update old branch again
65 echo "bye" >> branches/old/letter2.txt
66 svn ci -m "change letter2 again"
67
68 echo % update trunk again
69 echo "how are you ?" >> trunk/letter.txt
70 svn ci -m "last change to letter"
71 cd ..
72
73 echo % convert trunk and branches
74 hg convert --datesort $svnurl A-hg
75
76 echo % branch again from a converted revision
77 cd A
78 svn copy -r 1 $svnurl/trunk branches/old2
79 svn ci -m "branch trunk@1 into old2"
80 cd ..
81
82 echo % convert again
83 hg convert --datesort $svnurl A-hg
84
85 cd A-hg
86 hg glog --template '#rev# #desc|firstline# files: #files#\n'
87 hg branches | sed 's/:.*/:/'
88 hg tags -q
89 cd ..
@@ -0,0 +1,101 b''
1 % initial svn import
2 Adding projA/trunk
3 Adding projA/branches
4 Adding projA/tags
5
6 Committed revision 1.
7 % update svn repository
8 A A/trunk
9 A A/branches
10 A A/tags
11 Checked out revision 1.
12 A trunk/letter.txt
13 A trunk/letter2.txt
14 A trunk/letter3.txt
15 Adding trunk/letter.txt
16 Adding trunk/letter2.txt
17 Adding trunk/letter3.txt
18 Transmitting file data ...
19 Committed revision 2.
20 % branch to old letters
21 A branches/old
22 D branches/old/letter3.txt
23 Adding branches/old
24 Adding branches/old/letter.txt
25 Adding branches/old/letter2.txt
26 Deleting branches/old/letter3.txt
27
28 Committed revision 3.
29 At revision 3.
30 % update trunk
31 Sending trunk/letter.txt
32 Transmitting file data .
33 Committed revision 4.
34 % update old branch
35 Sending branches/old/letter2.txt
36 Transmitting file data .
37 Committed revision 5.
38 % create a cross-branch revision
39 A branches/old/letter3.txt
40 D trunk/letter2.txt
41 Adding branches/old/letter3.txt
42 Deleting trunk/letter2.txt
43 Transmitting file data .
44 Committed revision 6.
45 % update old branch again
46 Sending branches/old/letter2.txt
47 Transmitting file data .
48 Committed revision 7.
49 % update trunk again
50 Sending trunk/letter.txt
51 Transmitting file data .
52 Committed revision 8.
53 % convert trunk and branches
54 initializing destination A-hg repository
55 scanning source...
56 sorting...
57 converting...
58 8 init projA
59 7 hello
60 6 branch trunk, remove letter3
61 5 change letter
62 4 change letter2
63 3 move and update letter3.txt
64 2 move and update letter3.txt
65 1 change letter2 again
66 0 last change to letter
67 % branch again from a converted revision
68 Checked out revision 1.
69 A branches/old2
70 Adding branches/old2
71
72 Committed revision 9.
73 % convert again
74 scanning source...
75 sorting...
76 converting...
77 0 branch trunk@1 into old2
78 o 9 branch trunk@1 into old2 files:
79 |
80 | o 8 last change to letter files: letter.txt
81 | |
82 | | o 7 change letter2 again files: letter2.txt
83 | | |
84 | o | 6 move and update letter3.txt files: letter2.txt
85 | | |
86 | | o 5 move and update letter3.txt files: letter3.txt
87 | | |
88 | | o 4 change letter2 files: letter2.txt
89 | | |
90 | o | 3 change letter files: letter.txt
91 | | |
92 +---o 2 branch trunk, remove letter3 files: letter.txt letter.txt letter2.txt letter2.txt
93 | |
94 | o 1 hello files: letter.txt letter2.txt letter3.txt
95 |/
96 o 0 init projA files:
97
98 old2 9:
99 default 8:
100 old 7:
101 tip
@@ -89,6 +89,9 b' def get_log_child(fp, url, paths, start,'
89 89 receiver)
90 90 except SubversionException, (inst, num):
91 91 pickle.dump(num, fp, protocol)
92 except IOError:
93 # Caller may interrupt the iteration
94 pickle.dump(None, fp, protocol)
92 95 else:
93 96 pickle.dump(None, fp, protocol)
94 97 fp.close()
@@ -102,7 +105,53 b' def debugsvnlog(ui, **opts):'
102 105 args = decodeargs(sys.stdin.read())
103 106 get_log_child(sys.stdout, *args)
104 107
108 class logstream:
109 """Interruptible revision log iterator."""
110 def __init__(self, stdout):
111 self._stdout = stdout
112
113 def __iter__(self):
114 while True:
115 entry = pickle.load(self._stdout)
116 try:
117 orig_paths, revnum, author, date, message = entry
118 except:
119 if entry is None:
120 break
121 raise SubversionException("child raised exception", entry)
122 yield entry
123
124 def close(self):
125 if self._stdout:
126 self._stdout.close()
127 self._stdout = None
128
129 def get_log(url, paths, start, end, limit=0, discover_changed_paths=True,
130 strict_node_history=False):
131 args = [url, paths, start, end, limit, discover_changed_paths,
132 strict_node_history]
133 arg = encodeargs(args)
134 hgexe = util.hgexecutable()
135 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
136 stdin, stdout = os.popen2(cmd, 'b')
137 stdin.write(arg)
138 stdin.close()
139 return logstream(stdout)
140
105 141 # SVN conversion code stolen from bzr-svn and tailor
142 #
143 # Subversion looks like a versioned filesystem, branches structures
144 # are defined by conventions and not enforced by the tool. First,
145 # we define the potential branches (modules) as "trunk" and "branches"
146 # children directories. Revisions are then identified by their
147 # module and revision number (and a repository identifier).
148 #
149 # The revision graph is really a tree (or a forest). By default, a
150 # revision parent is the previous revision in the same module. If the
151 # module directory is copied/moved from another module then the
152 # revision is the module root and its parent the source revision in
153 # the parent module. A revision has at most one parent.
154 #
106 155 class svn_source(converter_source):
107 156 def __init__(self, ui, url, rev=None):
108 157 super(svn_source, self).__init__(ui, url, rev=rev)
@@ -133,7 +182,6 b' class svn_source(converter_source):'
133 182 self.ctx = self.transport.client
134 183 self.base = svn.ra.get_repos_root(self.ra)
135 184 self.module = self.url[len(self.base):]
136 self.modulemap = {} # revision, module
137 185 self.commits = {}
138 186 self.paths = {}
139 187 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
@@ -257,45 +305,26 b' class svn_source(converter_source):'
257 305 uuid, module, revnum = self.revsplit(rev)
258 306 self.module = module
259 307 self.reparent(module)
308 # We assume that:
309 # - requests for revisions after "stop" come from the
310 # revision graph backward traversal. Cache all of them
311 # down to stop, they will be used eventually.
312 # - requests for revisions before "stop" come to get
313 # isolated branches parents. Just fetch what is needed.
260 314 stop = self.lastrevs.get(module, 0)
261 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
315 if revnum < stop:
316 stop = revnum + 1
317 self._fetch_revisions(revnum, stop)
262 318 commit = self.commits[rev]
263 319 # caller caches the result, so free it here to release memory
264 320 del self.commits[rev]
265 321 return commit
266 322
267 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
268 strict_node_history=False):
269
270 def parent(fp):
271 while True:
272 entry = pickle.load(fp)
273 try:
274 orig_paths, revnum, author, date, message = entry
275 except:
276 if entry is None:
277 break
278 raise SubversionException("child raised exception", entry)
279 yield entry
280
281 args = [self.url, paths, start, end, limit, discover_changed_paths,
282 strict_node_history]
283 arg = encodeargs(args)
284 hgexe = util.hgexecutable()
285 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
286 stdin, stdout = os.popen2(cmd, 'b')
287
288 stdin.write(arg)
289 stdin.close()
290
291 for p in parent(stdout):
292 yield p
293
294 323 def gettags(self):
295 324 tags = {}
296 325 start = self.revnum(self.head)
297 326 try:
298 for entry in self.get_log([self.tags], 0, start):
327 for entry in get_log(self.url, [self.tags], 0, start):
299 328 orig_paths, revnum, author, date, message = entry
300 329 for path in orig_paths:
301 330 if not path.startswith(self.tags+'/'):
@@ -400,13 +429,11 b' class svn_source(converter_source):'
400 429 entries = []
401 430 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
402 431 copies = {}
403 revnum = self.revnum(rev)
404 432
405 if revnum in self.modulemap:
406 new_module = self.modulemap[revnum]
407 if new_module != self.module:
408 self.module = new_module
409 self.reparent(self.module)
433 new_module, revnum = self.revsplit(rev)[1:]
434 if new_module != self.module:
435 self.module = new_module
436 self.reparent(self.module)
410 437
411 438 for path, ent in paths:
412 439 entrypath = get_entry_from_path(path, module=self.module)
@@ -432,12 +459,9 b' class svn_source(converter_source):'
432 459
433 460 # if a branch is created but entries are removed in the same
434 461 # changeset, get the right fromrev
435 if parents:
436 uuid, old_module, fromrev = self.revsplit(parents[0])
437 else:
438 fromrev = revnum - 1
439 # might always need to be revnum - 1 in these 3 lines?
440 old_module = self.modulemap.get(fromrev, self.module)
462 # parents cannot be empty here, you cannot remove things from
463 # a root revision.
464 uuid, old_module, fromrev = self.revsplit(parents[0])
441 465
442 466 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
443 467 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
@@ -508,6 +532,9 b' class svn_source(converter_source):'
508 532
509 533 # If the directory just had a prop change,
510 534 # then we shouldn't need to look for its children.
535 if ent.action == 'M':
536 continue
537
511 538 # Also this could create duplicate entries. Not sure
512 539 # whether this will matter. Maybe should make entries a set.
513 540 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
@@ -568,23 +595,23 b' class svn_source(converter_source):'
568 595
569 596 return (entries, copies)
570 597
571 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
598 def _fetch_revisions(self, from_revnum, to_revnum):
599 if from_revnum < to_revnum:
600 from_revnum, to_revnum = to_revnum, from_revnum
601
572 602 self.child_cset = None
573 603 def parselogentry(orig_paths, revnum, author, date, message):
604 """Return the parsed commit object or None, and True if
605 the revision is a branch root.
606 """
574 607 self.ui.debug("parsing revision %d (%d changes)\n" %
575 608 (revnum, len(orig_paths)))
576 609
577 if revnum in self.modulemap:
578 new_module = self.modulemap[revnum]
579 if new_module != self.module:
580 self.module = new_module
581 self.reparent(self.module)
582
583 610 rev = self.revid(revnum)
584 611 # branch log might return entries for a parent we already have
585 if (rev in self.commits or
586 (revnum < self.lastrevs.get(self.module, 0))):
587 return
612
613 if (rev in self.commits or revnum < to_revnum):
614 return None, False
588 615
589 616 parents = []
590 617 # check whether this revision is the start of a branch
@@ -593,15 +620,12 b' class svn_source(converter_source):'
593 620 if ent.copyfrom_path:
594 621 # ent.copyfrom_rev may not be the actual last revision
595 622 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
596 self.modulemap[prev] = ent.copyfrom_path
597 623 parents = [self.revid(prev, ent.copyfrom_path)]
598 624 self.ui.note('found parent of branch %s at %d: %s\n' % \
599 625 (self.module, prev, ent.copyfrom_path))
600 626 else:
601 627 self.ui.debug("No copyfrom path, don't know what to do.\n")
602 628
603 self.modulemap[revnum] = self.module # track backwards in time
604
605 629 orig_paths = orig_paths.items()
606 630 orig_paths.sort()
607 631 paths = []
@@ -612,8 +636,6 b' class svn_source(converter_source):'
612 636 continue
613 637 paths.append((path, ent))
614 638
615 self.paths[rev] = (paths, parents)
616
617 639 # Example SVN datetime. Includes microseconds.
618 640 # ISO-8601 conformant
619 641 # '2007-01-04T17:35:00.902377Z'
@@ -636,23 +658,50 b' class svn_source(converter_source):'
636 658 rev=rev.encode('utf-8'))
637 659
638 660 self.commits[rev] = cset
661 # The parents list is *shared* among self.paths and the
662 # commit object. Both will be updated below.
663 self.paths[rev] = (paths, cset.parents)
639 664 if self.child_cset and not self.child_cset.parents:
640 self.child_cset.parents = [rev]
665 self.child_cset.parents[:] = [rev]
641 666 self.child_cset = cset
667 return cset, len(parents) > 0
642 668
643 669 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
644 670 (self.module, from_revnum, to_revnum))
645 671
646 672 try:
647 for entry in self.get_log([self.module], from_revnum, to_revnum):
648 orig_paths, revnum, author, date, message = entry
649 if self.is_blacklisted(revnum):
650 self.ui.note('skipping blacklisted revision %d\n' % revnum)
651 continue
652 if orig_paths is None:
653 self.ui.debug('revision %d has no entries\n' % revnum)
654 continue
655 parselogentry(orig_paths, revnum, author, date, message)
673 firstcset = None
674 stream = get_log(self.url, [self.module], from_revnum, to_revnum)
675 try:
676 for entry in stream:
677 paths, revnum, author, date, message = entry
678 if self.is_blacklisted(revnum):
679 self.ui.note('skipping blacklisted revision %d\n'
680 % revnum)
681 continue
682 if paths is None:
683 self.ui.debug('revision %d has no entries\n' % revnum)
684 continue
685 cset, branched = parselogentry(paths, revnum, author,
686 date, message)
687 if cset:
688 firstcset = cset
689 if branched:
690 break
691 finally:
692 stream.close()
693
694 if firstcset and not firstcset.parents:
695 # The first revision of the sequence (the last fetched one)
696 # has invalid parents if not a branch root. Find the parent
697 # revision now, if any.
698 try:
699 firstrevnum = self.revnum(firstcset.rev)
700 if firstrevnum > 1:
701 latest = self.latest(self.module, firstrevnum - 1)
702 firstcset.parents.append(self.revid(latest))
703 except util.Abort:
704 pass
656 705 except SubversionException, (inst, num):
657 706 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
658 707 raise NoSuchRevision(branch=self,
@@ -664,9 +713,9 b' class svn_source(converter_source):'
664 713 # TODO: ra.get_file transmits the whole file instead of diffs.
665 714 mode = ''
666 715 try:
667 revnum = self.revnum(rev)
668 if self.module != self.modulemap[revnum]:
669 self.module = self.modulemap[revnum]
716 new_module, revnum = self.revsplit(rev)[1:]
717 if self.module != new_module:
718 self.module = new_module
670 719 self.reparent(self.module)
671 720 info = svn.ra.get_file(self.ra, file, revnum, io)
672 721 if isinstance(info, list):
General Comments 0
You need to be logged in to leave comments. Login now