##// END OF EJS Templates
convert: add support for Subversion as a sink
Bryan O'Sullivan -
r5513:f0c58fd4 default
parent child Browse files
Show More
@@ -0,0 +1,91 b''
1 #!/bin/sh
2
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
4
5 echo "[extensions]" >> $HGRCPATH
6 echo "convert = " >> $HGRCPATH
7
8 hg init a
9
10 echo a > a/a
11 echo % add
12 hg --cwd a ci -d '0 0' -A -m 'add a file'
13
14 echo a >> a/a
15 echo % modify
16 hg --cwd a ci -d '1 0' -m 'modify a file'
17 hg --cwd a tip -q
18
19 hg convert -d svn a
20 (cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=2 | sed 's,<date>.*,<date/>,')
21 ls a a-hg-wc
22 cmp a/a a-hg-wc/a && echo same || echo different
23
24 hg --cwd a mv a b
25 echo % rename
26 hg --cwd a ci -d '2 0' -m 'rename a file'
27 hg --cwd a tip -q
28
29 hg convert -d svn a
30 (cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
31 ls a a-hg-wc
32
33 hg --cwd a cp b c
34 echo % copy
35 hg --cwd a ci -d '3 0' -m 'copy a file'
36 hg --cwd a tip -q
37
38 hg convert -d svn a
39 (cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
40 ls a a-hg-wc
41
42 hg --cwd a rm b
43 echo % remove
44 hg --cwd a ci -d '4 0' -m 'remove a file'
45 hg --cwd a tip -q
46
47 hg convert -d svn a
48 (cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
49 ls a a-hg-wc
50
51 chmod +x a/c
52 echo % executable
53 hg --cwd a ci -d '5 0' -m 'make a file executable'
54 hg --cwd a tip -q
55
56 hg convert -d svn a
57 (cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
58 test -x a-hg-wc/c && echo executable || echo not executable
59
60 echo % branchy history
61
62 hg init b
63 echo base > b/b
64 hg --cwd b ci -d '0 0' -Ambase
65
66 echo left-1 >> b/b
67 echo left-1 > b/left-1
68 hg --cwd b ci -d '1 0' -Amleft-1
69
70 echo left-2 >> b/b
71 echo left-2 > b/left-2
72 hg --cwd b ci -d '2 0' -Amleft-2
73
74 hg --cwd b up 0
75
76 echo right-1 >> b/b
77 echo right-1 > b/right-1
78 hg --cwd b ci -d '3 0' -Amright-1
79
80 echo right-2 >> b/b
81 echo right-2 > b/right-2
82 hg --cwd b ci -d '4 0' -Amright-2
83
84 hg --cwd b up -C 2
85 hg --cwd b merge
86 hg --cwd b revert -r 2 b
87 hg --cwd b ci -d '5 0' -m 'merge'
88
89 hg convert -d svn b
90 echo % expect 4 changes
91 (cd b-hg-wc; svn up; svn st -v; svn log --xml -v | sed 's,<date>.*,<date/>,')
@@ -0,0 +1,251 b''
1 % add
2 adding a
3 % modify
4 1:10307c220ed9
5 assuming destination a-hg
6 initializing svn repo 'a-hg'
7 initializing svn wc 'a-hg-wc'
8 scanning source...
9 sorting...
10 converting...
11 1 add a file
12 0 modify a file
13 At revision 2.
14 2 2 test .
15 2 2 test a
16 <?xml version="1.0"?>
17 <log>
18 <logentry
19 revision="2">
20 <author>test</author>
21 <date/>
22 <paths>
23 <path
24 action="M">/a</path>
25 </paths>
26 <msg>modify a file</msg>
27 </logentry>
28 <logentry
29 revision="1">
30 <author>test</author>
31 <date/>
32 <paths>
33 <path
34 action="A">/a</path>
35 </paths>
36 <msg>add a file</msg>
37 </logentry>
38 </log>
39 a:
40 a
41
42 a-hg-wc:
43 a
44 same
45 % rename
46 2:6e45a219686e
47 assuming destination a-hg
48 initializing svn wc 'a-hg-wc'
49 scanning source...
50 sorting...
51 converting...
52 0 rename a file
53 At revision 3.
54 3 3 test .
55 3 3 test b
56 <?xml version="1.0"?>
57 <log>
58 <logentry
59 revision="3">
60 <author>test</author>
61 <date/>
62 <paths>
63 <path
64 action="D">/a</path>
65 <path
66 copyfrom-path="/a"
67 copyfrom-rev="2"
68 action="A">/b</path>
69 </paths>
70 <msg>rename a file</msg>
71 </logentry>
72 </log>
73 a:
74 b
75
76 a-hg-wc:
77 b
78 % copy
79 3:d811dc81efbb
80 assuming destination a-hg
81 initializing svn wc 'a-hg-wc'
82 scanning source...
83 sorting...
84 converting...
85 0 copy a file
86 At revision 4.
87 4 4 test .
88 4 3 test b
89 4 4 test c
90 <?xml version="1.0"?>
91 <log>
92 <logentry
93 revision="4">
94 <author>test</author>
95 <date/>
96 <paths>
97 <path
98 copyfrom-path="/b"
99 copyfrom-rev="3"
100 action="A">/c</path>
101 </paths>
102 <msg>copy a file</msg>
103 </logentry>
104 </log>
105 a:
106 b
107 c
108
109 a-hg-wc:
110 b
111 c
112 % remove
113 4:045e93063aca
114 assuming destination a-hg
115 initializing svn wc 'a-hg-wc'
116 scanning source...
117 sorting...
118 converting...
119 0 remove a file
120 At revision 5.
121 5 5 test .
122 5 4 test c
123 <?xml version="1.0"?>
124 <log>
125 <logentry
126 revision="5">
127 <author>test</author>
128 <date/>
129 <paths>
130 <path
131 action="D">/b</path>
132 </paths>
133 <msg>remove a file</msg>
134 </logentry>
135 </log>
136 a:
137 c
138
139 a-hg-wc:
140 c
141 % executable
142 5:7eda3f4b5331
143 svn: Path 'b' does not exist
144 assuming destination a-hg
145 initializing svn wc 'a-hg-wc'
146 scanning source...
147 sorting...
148 converting...
149 0 make a file executable
150 abort: svn exited with status 1
151 At revision 5.
152 5 5 test .
153 M 5 4 test c
154 <?xml version="1.0"?>
155 <log>
156 <logentry
157 revision="5">
158 <author>test</author>
159 <date/>
160 <paths>
161 <path
162 action="D">/b</path>
163 </paths>
164 <msg>remove a file</msg>
165 </logentry>
166 </log>
167 executable
168 % branchy history
169 adding b
170 adding left-1
171 adding left-2
172 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
173 adding right-1
174 adding right-2
175 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
176 warning: conflicts during merge.
177 merging b
178 merging b failed!
179 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
180 There are unresolved merges, you can redo the full merge using:
181 hg update -C 2
182 hg merge 4
183 assuming destination b-hg
184 initializing svn repo 'b-hg'
185 initializing svn wc 'b-hg-wc'
186 scanning source...
187 sorting...
188 converting...
189 5 base
190 4 left-1
191 3 left-2
192 2 right-1
193 1 right-2
194 0 merge
195 % expect 4 changes
196 At revision 4.
197 4 4 test .
198 4 3 test b
199 4 2 test left-1
200 4 3 test left-2
201 4 4 test right-1
202 4 4 test right-2
203 <?xml version="1.0"?>
204 <log>
205 <logentry
206 revision="4">
207 <author>test</author>
208 <date/>
209 <paths>
210 <path
211 action="A">/right-1</path>
212 <path
213 action="A">/right-2</path>
214 </paths>
215 <msg>merge</msg>
216 </logentry>
217 <logentry
218 revision="3">
219 <author>test</author>
220 <date/>
221 <paths>
222 <path
223 action="M">/b</path>
224 <path
225 action="A">/left-2</path>
226 </paths>
227 <msg>left-2</msg>
228 </logentry>
229 <logentry
230 revision="2">
231 <author>test</author>
232 <date/>
233 <paths>
234 <path
235 action="M">/b</path>
236 <path
237 action="A">/left-1</path>
238 </paths>
239 <msg>left-1</msg>
240 </logentry>
241 <logentry
242 revision="1">
243 <author>test</author>
244 <date/>
245 <paths>
246 <path
247 action="A">/b</path>
248 </paths>
249 <msg>base</msg>
250 </logentry>
251 </log>
@@ -10,7 +10,7 b' from cvs import convert_cvs'
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import svn_source, debugsvnlog
13 from subversion import debugsvnlog, svn_source, svn_sink
14 import filemap
14 import filemap
15
15
16 import os, shutil
16 import os, shutil
@@ -29,6 +29,7 b' source_converters = ['
29
29
30 sink_converters = [
30 sink_converters = [
31 ('hg', mercurial_sink),
31 ('hg', mercurial_sink),
32 ('svn', svn_sink),
32 ]
33 ]
33
34
34 def convertsource(ui, path, type, rev):
35 def convertsource(ui, path, type, rev):
@@ -283,6 +284,7 b' def convert(ui, src, dest=None, revmapfi'
283
284
284 Accepted destination formats:
285 Accepted destination formats:
285 - Mercurial
286 - Mercurial
287 - Subversion (history on branches is not preserved)
286
288
287 If no revision is given, all revisions will be converted. Otherwise,
289 If no revision is given, all revisions will be converted. Otherwise,
288 convert will only import up to the named revision (given in a format
290 convert will only import up to the named revision (given in a format
@@ -2,6 +2,7 b''
2 import base64, errno
2 import base64, errno
3 import cPickle as pickle
3 import cPickle as pickle
4 from mercurial import util
4 from mercurial import util
5 from mercurial.i18n import _
5
6
6 def encodeargs(args):
7 def encodeargs(args):
7 def encodearg(s):
8 def encodearg(s):
@@ -17,9 +17,13 b''
17
17
18 import locale
18 import locale
19 import os
19 import os
20 import re
20 import sys
21 import sys
21 import cPickle as pickle
22 import cPickle as pickle
22 from mercurial import util
23 import tempfile
24
25 from mercurial import strutil, util
26 from mercurial.i18n import _
23
27
24 # Subversion stuff. Works best with very recent Python SVN bindings
28 # Subversion stuff. Works best with very recent Python SVN bindings
25 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
29 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
@@ -28,6 +32,7 b' from mercurial import util'
28 from cStringIO import StringIO
32 from cStringIO import StringIO
29
33
30 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
34 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
35 from common import commandline, converter_sink, mapfile
31
36
32 try:
37 try:
33 from svn.core import SubversionException, Pool
38 from svn.core import SubversionException, Pool
@@ -664,3 +669,198 b' class svn_source(converter_source):'
664 pool = Pool()
669 pool = Pool()
665 rpath = '/'.join([self.base, path]).strip('/')
670 rpath = '/'.join([self.base, path]).strip('/')
666 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
671 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
672
673 pre_revprop_change = '''#!/bin/sh
674
675 REPOS="$1"
676 REV="$2"
677 USER="$3"
678 PROPNAME="$4"
679 ACTION="$5"
680
681 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
682 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
683 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
684
685 echo "Changing prohibited revision property" >&2
686 exit 1
687 '''
688
689 class svn_sink(converter_sink, commandline):
690 commit_re = re.compile(r'Committed revision (\d+).', re.M)
691
692 def prerun(self):
693 if self.wc:
694 os.chdir(self.wc)
695
696 def postrun(self):
697 if self.wc:
698 os.chdir(self.cwd)
699
700 def join(self, name):
701 return os.path.join(self.wc, '.svn', name)
702
703 def revmapfile(self):
704 return self.join('hg-shamap')
705
706 def authorfile(self):
707 return self.join('hg-authormap')
708
709 def __init__(self, ui, path):
710 converter_sink.__init__(self, ui, path)
711 commandline.__init__(self, ui, 'svn')
712 self.delete = []
713 self.wc = None
714 self.cwd = os.getcwd()
715
716 path = os.path.realpath(path)
717
718 created = False
719 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
720 self.wc = path
721 self.run0('update')
722 else:
723 if os.path.isdir(os.path.dirname(path)):
724 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
725 ui.status(_('initializing svn repo %r\n') %
726 os.path.basename(path))
727 commandline(ui, 'svnadmin').run0('create', path)
728 created = path
729 path = 'file://' + path
730 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
731 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
732 self.run0('checkout', path, wcpath)
733
734 self.wc = wcpath
735 self.opener = util.opener(self.wc)
736 self.wopener = util.opener(self.wc)
737 self.childmap = mapfile(ui, self.join('hg-childmap'))
738
739 if created:
740 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
741 fp = open(hook, 'w')
742 fp.write(pre_revprop_change)
743 fp.close()
744 util.set_exec(hook, True)
745
746 def wjoin(self, *names):
747 return os.path.join(self.wc, *names)
748
749 def putfile(self, filename, flags, data):
750 if 'l' in flags:
751 self.wopener.symlink(data, filename)
752 else:
753 try:
754 if os.path.islink(self.wjoin(filename)):
755 os.unlink(filename)
756 except OSError:
757 pass
758 self.wopener(filename, 'w').write(data)
759 was_exec = util.is_exec(self.wjoin(filename))
760 util.set_exec(self.wjoin(filename), 'x' in flags)
761 if was_exec:
762 if 'x' not in flags:
763 self.run0('propdel', 'svn:executable', filename)
764 else:
765 if 'x' in flags:
766 self.run0('propset', 'svn:executable', '*', filename)
767
768 def delfile(self, name):
769 self.delete.append(name)
770
771 def copyfile(self, source, dest):
772 # SVN's copy command pukes if the destination file exists, but
773 # our copyfile method expects to record a copy that has
774 # already occurred. Cross the semantic gap.
775 wdest = self.wjoin(dest)
776 exists = os.path.exists(wdest)
777 if exists:
778 fd, tempname = tempfile.mkstemp(
779 prefix='hg-copy-', dir=os.path.dirname(wdest))
780 os.close(fd)
781 os.unlink(tempname)
782 os.rename(wdest, tempname)
783 try:
784 self.run0('copy', source, dest)
785 finally:
786 if exists:
787 try:
788 os.unlink(wdest)
789 except OSError:
790 pass
791 os.rename(tempname, wdest)
792
793 def dirs_of(self, files):
794 dirs = set()
795 for f in files:
796 if os.path.isdir(self.wjoin(f)):
797 dirs.add(f)
798 for i in strutil.rfindall(f, '/'):
799 dirs.add(f[:i])
800 return dirs
801
802 def add_files(self, files):
803 add_dirs = [d for d in self.dirs_of(files)
804 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
805 if add_dirs:
806 self.run('add', non_recursive=True, quiet=True, *add)
807 if files:
808 self.run('add', quiet=True, *files)
809 return files.union(add_dirs)
810
811 def tidy_dirs(self, names):
812 dirs = list(self.dirs_of(names))
813 dirs.sort(reverse=True)
814 deleted = []
815 for d in dirs:
816 wd = self.wjoin(d)
817 if os.listdir(wd) == '.svn':
818 self.run0('delete', d)
819 deleted.append(d)
820 return deleted
821
822 def addchild(self, parent, child):
823 self.childmap[parent] = child
824
825 def putcommit(self, files, parents, commit):
826 for parent in parents:
827 try:
828 return self.childmap[parent]
829 except KeyError:
830 pass
831 entries = set(self.delete)
832 if self.delete:
833 self.run0('delete', *self.delete)
834 self.delete = []
835 files = util.frozenset(files)
836 entries.update(self.add_files(files.difference(entries)))
837 entries.update(self.tidy_dirs(entries))
838 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
839 fp = os.fdopen(fd, 'w')
840 fp.write(commit.desc)
841 fp.close()
842 try:
843 output = self.run0('commit',
844 username=util.shortuser(commit.author),
845 file=messagefile,
846 *list(entries))
847 try:
848 rev = self.commit_re.search(output).group(1)
849 except AttributeError:
850 self.ui.warn(_('unexpected svn output:\n'))
851 self.ui.warn(output)
852 raise util.Abort(_('unable to cope with svn output'))
853 if commit.rev:
854 self.run('propset', 'hg:convert-rev', commit.rev,
855 revprop=True, revision=rev)
856 if commit.branch and commit.branch != 'default':
857 self.run('propset', 'hg:convert-branch', commit.branch,
858 revprop=True, revision=rev)
859 for parent in parents:
860 self.addchild(parent, rev)
861 return rev
862 finally:
863 os.unlink(messagefile)
864
865 def puttags(self, tags):
866 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
1 NO CONTENT: file renamed from tests/test-convert-svn to tests/test-convert-svn-source
NO CONTENT: file renamed from tests/test-convert-svn to tests/test-convert-svn-source
1 NO CONTENT: file renamed from tests/test-convert-svn.out to tests/test-convert-svn-source.out
NO CONTENT: file renamed from tests/test-convert-svn.out to tests/test-convert-svn-source.out
@@ -11,6 +11,7 b' Convert a foreign SCM repository to a Me'
11
11
12 Accepted destination formats:
12 Accepted destination formats:
13 - Mercurial
13 - Mercurial
14 - Subversion (history on branches is not preserved)
14
15
15 If no revision is given, all revisions will be converted. Otherwise,
16 If no revision is given, all revisions will be converted. Otherwise,
16 convert will only import up to the named revision (given in a format
17 convert will only import up to the named revision (given in a format
General Comments 0
You need to be logged in to leave comments. Login now