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. |
|
|
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,10 +429,8 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] | |
|
433 | new_module, revnum = self.revsplit(rev)[1:] | |
|
407 | 434 |
|
|
408 | 435 |
|
|
409 | 436 |
|
@@ -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: | |
|
462 | # parents cannot be empty here, you cannot remove things from | |
|
463 | # a root revision. | |
|
436 | 464 |
|
|
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) | |
|
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 |
|
|
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 | |
|
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 | |
|
649 | 678 | if self.is_blacklisted(revnum): |
|
650 |
self.ui.note('skipping blacklisted revision %d\n' |
|
|
679 | self.ui.note('skipping blacklisted revision %d\n' | |
|
680 | % revnum) | |
|
651 | 681 | continue |
|
652 |
if |
|
|
682 | if paths is None: | |
|
653 | 683 | self.ui.debug('revision %d has no entries\n' % revnum) |
|
654 | 684 | continue |
|
655 |
parselogentry( |
|
|
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.rev |
|
|
668 |
if self.module != |
|
|
669 |
self.module = |
|
|
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