##// 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 receiver)
89 receiver)
90 except SubversionException, (inst, num):
90 except SubversionException, (inst, num):
91 pickle.dump(num, fp, protocol)
91 pickle.dump(num, fp, protocol)
92 except IOError:
93 # Caller may interrupt the iteration
94 pickle.dump(None, fp, protocol)
92 else:
95 else:
93 pickle.dump(None, fp, protocol)
96 pickle.dump(None, fp, protocol)
94 fp.close()
97 fp.close()
@@ -102,7 +105,53 b' def debugsvnlog(ui, **opts):'
102 args = decodeargs(sys.stdin.read())
105 args = decodeargs(sys.stdin.read())
103 get_log_child(sys.stdout, *args)
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 # SVN conversion code stolen from bzr-svn and tailor
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 class svn_source(converter_source):
155 class svn_source(converter_source):
107 def __init__(self, ui, url, rev=None):
156 def __init__(self, ui, url, rev=None):
108 super(svn_source, self).__init__(ui, url, rev=rev)
157 super(svn_source, self).__init__(ui, url, rev=rev)
@@ -133,7 +182,6 b' class svn_source(converter_source):'
133 self.ctx = self.transport.client
182 self.ctx = self.transport.client
134 self.base = svn.ra.get_repos_root(self.ra)
183 self.base = svn.ra.get_repos_root(self.ra)
135 self.module = self.url[len(self.base):]
184 self.module = self.url[len(self.base):]
136 self.modulemap = {} # revision, module
137 self.commits = {}
185 self.commits = {}
138 self.paths = {}
186 self.paths = {}
139 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
187 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
@@ -257,45 +305,26 b' class svn_source(converter_source):'
257 uuid, module, revnum = self.revsplit(rev)
305 uuid, module, revnum = self.revsplit(rev)
258 self.module = module
306 self.module = module
259 self.reparent(module)
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 stop = self.lastrevs.get(module, 0)
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 commit = self.commits[rev]
318 commit = self.commits[rev]
263 # caller caches the result, so free it here to release memory
319 # caller caches the result, so free it here to release memory
264 del self.commits[rev]
320 del self.commits[rev]
265 return commit
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 def gettags(self):
323 def gettags(self):
295 tags = {}
324 tags = {}
296 start = self.revnum(self.head)
325 start = self.revnum(self.head)
297 try:
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 orig_paths, revnum, author, date, message = entry
328 orig_paths, revnum, author, date, message = entry
300 for path in orig_paths:
329 for path in orig_paths:
301 if not path.startswith(self.tags+'/'):
330 if not path.startswith(self.tags+'/'):
@@ -400,13 +429,11 b' class svn_source(converter_source):'
400 entries = []
429 entries = []
401 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
430 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
402 copies = {}
431 copies = {}
403 revnum = self.revnum(rev)
404
432
405 if revnum in self.modulemap:
433 new_module, revnum = self.revsplit(rev)[1:]
406 new_module = self.modulemap[revnum]
434 if new_module != self.module:
407 if new_module != self.module:
435 self.module = new_module
408 self.module = new_module
436 self.reparent(self.module)
409 self.reparent(self.module)
410
437
411 for path, ent in paths:
438 for path, ent in paths:
412 entrypath = get_entry_from_path(path, module=self.module)
439 entrypath = get_entry_from_path(path, module=self.module)
@@ -432,12 +459,9 b' class svn_source(converter_source):'
432
459
433 # if a branch is created but entries are removed in the same
460 # if a branch is created but entries are removed in the same
434 # changeset, get the right fromrev
461 # changeset, get the right fromrev
435 if parents:
462 # parents cannot be empty here, you cannot remove things from
436 uuid, old_module, fromrev = self.revsplit(parents[0])
463 # a root revision.
437 else:
464 uuid, old_module, fromrev = self.revsplit(parents[0])
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)
441
465
442 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
466 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
443 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
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 # If the directory just had a prop change,
533 # If the directory just had a prop change,
510 # then we shouldn't need to look for its children.
534 # then we shouldn't need to look for its children.
535 if ent.action == 'M':
536 continue
537
511 # Also this could create duplicate entries. Not sure
538 # Also this could create duplicate entries. Not sure
512 # whether this will matter. Maybe should make entries a set.
539 # whether this will matter. Maybe should make entries a set.
513 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
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 return (entries, copies)
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 self.child_cset = None
602 self.child_cset = None
573 def parselogentry(orig_paths, revnum, author, date, message):
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 self.ui.debug("parsing revision %d (%d changes)\n" %
607 self.ui.debug("parsing revision %d (%d changes)\n" %
575 (revnum, len(orig_paths)))
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 rev = self.revid(revnum)
610 rev = self.revid(revnum)
584 # branch log might return entries for a parent we already have
611 # branch log might return entries for a parent we already have
585 if (rev in self.commits or
612
586 (revnum < self.lastrevs.get(self.module, 0))):
613 if (rev in self.commits or revnum < to_revnum):
587 return
614 return None, False
588
615
589 parents = []
616 parents = []
590 # check whether this revision is the start of a branch
617 # check whether this revision is the start of a branch
@@ -593,15 +620,12 b' class svn_source(converter_source):'
593 if ent.copyfrom_path:
620 if ent.copyfrom_path:
594 # ent.copyfrom_rev may not be the actual last revision
621 # ent.copyfrom_rev may not be the actual last revision
595 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
622 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
596 self.modulemap[prev] = ent.copyfrom_path
597 parents = [self.revid(prev, ent.copyfrom_path)]
623 parents = [self.revid(prev, ent.copyfrom_path)]
598 self.ui.note('found parent of branch %s at %d: %s\n' % \
624 self.ui.note('found parent of branch %s at %d: %s\n' % \
599 (self.module, prev, ent.copyfrom_path))
625 (self.module, prev, ent.copyfrom_path))
600 else:
626 else:
601 self.ui.debug("No copyfrom path, don't know what to do.\n")
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 orig_paths = orig_paths.items()
629 orig_paths = orig_paths.items()
606 orig_paths.sort()
630 orig_paths.sort()
607 paths = []
631 paths = []
@@ -612,8 +636,6 b' class svn_source(converter_source):'
612 continue
636 continue
613 paths.append((path, ent))
637 paths.append((path, ent))
614
638
615 self.paths[rev] = (paths, parents)
616
617 # Example SVN datetime. Includes microseconds.
639 # Example SVN datetime. Includes microseconds.
618 # ISO-8601 conformant
640 # ISO-8601 conformant
619 # '2007-01-04T17:35:00.902377Z'
641 # '2007-01-04T17:35:00.902377Z'
@@ -636,23 +658,50 b' class svn_source(converter_source):'
636 rev=rev.encode('utf-8'))
658 rev=rev.encode('utf-8'))
637
659
638 self.commits[rev] = cset
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 if self.child_cset and not self.child_cset.parents:
664 if self.child_cset and not self.child_cset.parents:
640 self.child_cset.parents = [rev]
665 self.child_cset.parents[:] = [rev]
641 self.child_cset = cset
666 self.child_cset = cset
667 return cset, len(parents) > 0
642
668
643 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
669 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
644 (self.module, from_revnum, to_revnum))
670 (self.module, from_revnum, to_revnum))
645
671
646 try:
672 try:
647 for entry in self.get_log([self.module], from_revnum, to_revnum):
673 firstcset = None
648 orig_paths, revnum, author, date, message = entry
674 stream = get_log(self.url, [self.module], from_revnum, to_revnum)
649 if self.is_blacklisted(revnum):
675 try:
650 self.ui.note('skipping blacklisted revision %d\n' % revnum)
676 for entry in stream:
651 continue
677 paths, revnum, author, date, message = entry
652 if orig_paths is None:
678 if self.is_blacklisted(revnum):
653 self.ui.debug('revision %d has no entries\n' % revnum)
679 self.ui.note('skipping blacklisted revision %d\n'
654 continue
680 % revnum)
655 parselogentry(orig_paths, revnum, author, date, message)
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 except SubversionException, (inst, num):
705 except SubversionException, (inst, num):
657 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
706 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
658 raise NoSuchRevision(branch=self,
707 raise NoSuchRevision(branch=self,
@@ -664,9 +713,9 b' class svn_source(converter_source):'
664 # TODO: ra.get_file transmits the whole file instead of diffs.
713 # TODO: ra.get_file transmits the whole file instead of diffs.
665 mode = ''
714 mode = ''
666 try:
715 try:
667 revnum = self.revnum(rev)
716 new_module, revnum = self.revsplit(rev)[1:]
668 if self.module != self.modulemap[revnum]:
717 if self.module != new_module:
669 self.module = self.modulemap[revnum]
718 self.module = new_module
670 self.reparent(self.module)
719 self.reparent(self.module)
671 info = svn.ra.get_file(self.ra, file, revnum, io)
720 info = svn.ra.get_file(self.ra, file, revnum, io)
672 if isinstance(info, list):
721 if isinstance(info, list):
General Comments 0
You need to be logged in to leave comments. Login now