##// END OF EJS Templates
convert/svn: do not try converting empty head revisions (issue3347)...
Patrick Mezard -
r16466:c53a49c3 stable
parent child Browse files
Show More
@@ -0,0 +1,129 b''
1 SVN-fs-dump-format-version: 2
2
3 UUID: b70c45d5-2b76-4722-a373-d9babae61626
4
5 Revision-number: 0
6 Prop-content-length: 260
7 Content-length: 260
8
9 K 8
10 svn:date
11 V 27
12 2012-04-18T11:35:14.752409Z
13 K 17
14 svn:sync-from-url
15 V 73
16 file:///Users/pmezard/dev/hg/hg-pmezard/tests/svn/temp/svn-repo/trunk/dir
17 K 18
18 svn:sync-from-uuid
19 V 36
20 56625b9e-e7e9-45be-ab61-052d41f0e1dd
21 K 24
22 svn:sync-last-merged-rev
23 V 1
24 4
25 PROPS-END
26
27 Revision-number: 1
28 Prop-content-length: 112
29 Content-length: 112
30
31 K 10
32 svn:author
33 V 7
34 pmezard
35 K 8
36 svn:date
37 V 27
38 2012-04-18T11:35:14.769622Z
39 K 7
40 svn:log
41 V 10
42 init projA
43 PROPS-END
44
45 Node-path: trunk
46 Node-kind: dir
47 Node-action: add
48 Prop-content-length: 10
49 Content-length: 10
50
51 PROPS-END
52
53
54 Revision-number: 2
55 Prop-content-length: 107
56 Content-length: 107
57
58 K 10
59 svn:author
60 V 7
61 pmezard
62 K 8
63 svn:date
64 V 27
65 2012-04-18T11:35:15.052989Z
66 K 7
67 svn:log
68 V 6
69 adddir
70 PROPS-END
71
72 Node-path: trunk/dir
73 Node-kind: dir
74 Node-action: add
75 Prop-content-length: 10
76 Content-length: 10
77
78 PROPS-END
79
80
81 Node-path: trunk/dir/a
82 Node-kind: file
83 Node-action: add
84 Prop-content-length: 10
85 Text-content-length: 2
86 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
87 Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
88 Content-length: 12
89
90 PROPS-END
91 a
92
93
94 Revision-number: 3
95 Prop-content-length: 105
96 Content-length: 105
97
98 K 10
99 svn:author
100 V 7
101 pmezard
102 K 8
103 svn:date
104 V 27
105 2012-04-18T11:35:16.050353Z
106 K 7
107 svn:log
108 V 4
109 addb
110 PROPS-END
111
112 Revision-number: 4
113 Prop-content-length: 105
114 Content-length: 105
115
116 K 10
117 svn:author
118 V 7
119 pmezard
120 K 8
121 svn:date
122 V 27
123 2012-04-18T11:35:17.050768Z
124 K 7
125 svn:log
126 V 4
127 addc
128 PROPS-END
129
@@ -0,0 +1,47 b''
1 #!/bin/sh
2 #
3 # Use this script to generate empty.svndump
4 #
5
6 mkdir temp
7 cd temp
8
9 mkdir project-orig
10 cd project-orig
11 mkdir trunk
12 mkdir branches
13 mkdir tags
14 cd ..
15
16 svnadmin create svn-repo
17 svnurl=file://`pwd`/svn-repo
18 svn import project-orig $svnurl -m "init projA"
19
20 svn co $svnurl project
21 cd project
22 mkdir trunk/dir
23 echo a > trunk/dir/a
24 svn add trunk/dir
25 svn ci -m adddir
26
27 echo b > trunk/b
28 svn add trunk/b
29 svn ci -m addb
30
31 echo c > c
32 svn add c
33 svn ci -m addc
34 cd ..
35
36 # svnsync repo/trunk/dir only so the last two revisions are empty
37 svnadmin create svn-empty
38 cat > svn-empty/hooks/pre-revprop-change <<EOF
39 #!/bin/sh
40 exit 0
41 EOF
42 chmod +x svn-empty/hooks/pre-revprop-change
43 svnsync init --username svnsync file://`pwd`/svn-empty file://`pwd`/svn-repo/trunk/dir
44 svnsync sync file://`pwd`/svn-empty
45 svn log -v file://`pwd`/svn-empty
46
47 svnadmin dump svn-empty > ../empty.svndump
@@ -1,1204 +1,1223 b''
1 # Subversion 1.4/1.5 Python API backend
1 # Subversion 1.4/1.5 Python API backend
2 #
2 #
3 # Copyright(C) 2007 Daniel Holth et al
3 # Copyright(C) 2007 Daniel Holth et al
4
4
5 import os
5 import os
6 import re
6 import re
7 import sys
7 import sys
8 import cPickle as pickle
8 import cPickle as pickle
9 import tempfile
9 import tempfile
10 import urllib
10 import urllib
11 import urllib2
11 import urllib2
12
12
13 from mercurial import strutil, scmutil, util, encoding
13 from mercurial import strutil, scmutil, util, encoding
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 # Subversion stuff. Works best with very recent Python SVN bindings
16 # Subversion stuff. Works best with very recent Python SVN bindings
17 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
17 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
18 # these bindings.
18 # these bindings.
19
19
20 from cStringIO import StringIO
20 from cStringIO import StringIO
21
21
22 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
22 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
23 from common import commandline, converter_source, converter_sink, mapfile
23 from common import commandline, converter_source, converter_sink, mapfile
24
24
25 try:
25 try:
26 from svn.core import SubversionException, Pool
26 from svn.core import SubversionException, Pool
27 import svn
27 import svn
28 import svn.client
28 import svn.client
29 import svn.core
29 import svn.core
30 import svn.ra
30 import svn.ra
31 import svn.delta
31 import svn.delta
32 import transport
32 import transport
33 import warnings
33 import warnings
34 warnings.filterwarnings('ignore',
34 warnings.filterwarnings('ignore',
35 module='svn.core',
35 module='svn.core',
36 category=DeprecationWarning)
36 category=DeprecationWarning)
37
37
38 except ImportError:
38 except ImportError:
39 svn = None
39 svn = None
40
40
41 class SvnPathNotFound(Exception):
41 class SvnPathNotFound(Exception):
42 pass
42 pass
43
43
44 def revsplit(rev):
44 def revsplit(rev):
45 """Parse a revision string and return (uuid, path, revnum)."""
45 """Parse a revision string and return (uuid, path, revnum)."""
46 url, revnum = rev.rsplit('@', 1)
46 url, revnum = rev.rsplit('@', 1)
47 parts = url.split('/', 1)
47 parts = url.split('/', 1)
48 mod = ''
48 mod = ''
49 if len(parts) > 1:
49 if len(parts) > 1:
50 mod = '/' + parts[1]
50 mod = '/' + parts[1]
51 return parts[0][4:], mod, int(revnum)
51 return parts[0][4:], mod, int(revnum)
52
52
53 def quote(s):
53 def quote(s):
54 # As of svn 1.7, many svn calls expect "canonical" paths. In
54 # As of svn 1.7, many svn calls expect "canonical" paths. In
55 # theory, we should call svn.core.*canonicalize() on all paths
55 # theory, we should call svn.core.*canonicalize() on all paths
56 # before passing them to the API. Instead, we assume the base url
56 # before passing them to the API. Instead, we assume the base url
57 # is canonical and copy the behaviour of svn URL encoding function
57 # is canonical and copy the behaviour of svn URL encoding function
58 # so we can extend it safely with new components. The "safe"
58 # so we can extend it safely with new components. The "safe"
59 # characters were taken from the "svn_uri__char_validity" table in
59 # characters were taken from the "svn_uri__char_validity" table in
60 # libsvn_subr/path.c.
60 # libsvn_subr/path.c.
61 return urllib.quote(s, "!$&'()*+,-./:=@_~")
61 return urllib.quote(s, "!$&'()*+,-./:=@_~")
62
62
63 def geturl(path):
63 def geturl(path):
64 try:
64 try:
65 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
65 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
66 except SubversionException:
66 except SubversionException:
67 # svn.client.url_from_path() fails with local repositories
67 # svn.client.url_from_path() fails with local repositories
68 pass
68 pass
69 if os.path.isdir(path):
69 if os.path.isdir(path):
70 path = os.path.normpath(os.path.abspath(path))
70 path = os.path.normpath(os.path.abspath(path))
71 if os.name == 'nt':
71 if os.name == 'nt':
72 path = '/' + util.normpath(path)
72 path = '/' + util.normpath(path)
73 # Module URL is later compared with the repository URL returned
73 # Module URL is later compared with the repository URL returned
74 # by svn API, which is UTF-8.
74 # by svn API, which is UTF-8.
75 path = encoding.tolocal(path)
75 path = encoding.tolocal(path)
76 path = 'file://%s' % quote(path)
76 path = 'file://%s' % quote(path)
77 return svn.core.svn_path_canonicalize(path)
77 return svn.core.svn_path_canonicalize(path)
78
78
79 def optrev(number):
79 def optrev(number):
80 optrev = svn.core.svn_opt_revision_t()
80 optrev = svn.core.svn_opt_revision_t()
81 optrev.kind = svn.core.svn_opt_revision_number
81 optrev.kind = svn.core.svn_opt_revision_number
82 optrev.value.number = number
82 optrev.value.number = number
83 return optrev
83 return optrev
84
84
85 class changedpath(object):
85 class changedpath(object):
86 def __init__(self, p):
86 def __init__(self, p):
87 self.copyfrom_path = p.copyfrom_path
87 self.copyfrom_path = p.copyfrom_path
88 self.copyfrom_rev = p.copyfrom_rev
88 self.copyfrom_rev = p.copyfrom_rev
89 self.action = p.action
89 self.action = p.action
90
90
91 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
91 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
92 strict_node_history=False):
92 strict_node_history=False):
93 protocol = -1
93 protocol = -1
94 def receiver(orig_paths, revnum, author, date, message, pool):
94 def receiver(orig_paths, revnum, author, date, message, pool):
95 if orig_paths is not None:
95 if orig_paths is not None:
96 for k, v in orig_paths.iteritems():
96 for k, v in orig_paths.iteritems():
97 orig_paths[k] = changedpath(v)
97 orig_paths[k] = changedpath(v)
98 pickle.dump((orig_paths, revnum, author, date, message),
98 pickle.dump((orig_paths, revnum, author, date, message),
99 fp, protocol)
99 fp, protocol)
100
100
101 try:
101 try:
102 # Use an ra of our own so that our parent can consume
102 # Use an ra of our own so that our parent can consume
103 # our results without confusing the server.
103 # our results without confusing the server.
104 t = transport.SvnRaTransport(url=url)
104 t = transport.SvnRaTransport(url=url)
105 svn.ra.get_log(t.ra, paths, start, end, limit,
105 svn.ra.get_log(t.ra, paths, start, end, limit,
106 discover_changed_paths,
106 discover_changed_paths,
107 strict_node_history,
107 strict_node_history,
108 receiver)
108 receiver)
109 except IOError:
109 except IOError:
110 # Caller may interrupt the iteration
110 # Caller may interrupt the iteration
111 pickle.dump(None, fp, protocol)
111 pickle.dump(None, fp, protocol)
112 except Exception, inst:
112 except Exception, inst:
113 pickle.dump(str(inst), fp, protocol)
113 pickle.dump(str(inst), fp, protocol)
114 else:
114 else:
115 pickle.dump(None, fp, protocol)
115 pickle.dump(None, fp, protocol)
116 fp.close()
116 fp.close()
117 # With large history, cleanup process goes crazy and suddenly
117 # With large history, cleanup process goes crazy and suddenly
118 # consumes *huge* amount of memory. The output file being closed,
118 # consumes *huge* amount of memory. The output file being closed,
119 # there is no need for clean termination.
119 # there is no need for clean termination.
120 os._exit(0)
120 os._exit(0)
121
121
122 def debugsvnlog(ui, **opts):
122 def debugsvnlog(ui, **opts):
123 """Fetch SVN log in a subprocess and channel them back to parent to
123 """Fetch SVN log in a subprocess and channel them back to parent to
124 avoid memory collection issues.
124 avoid memory collection issues.
125 """
125 """
126 util.setbinary(sys.stdin)
126 util.setbinary(sys.stdin)
127 util.setbinary(sys.stdout)
127 util.setbinary(sys.stdout)
128 args = decodeargs(sys.stdin.read())
128 args = decodeargs(sys.stdin.read())
129 get_log_child(sys.stdout, *args)
129 get_log_child(sys.stdout, *args)
130
130
131 class logstream(object):
131 class logstream(object):
132 """Interruptible revision log iterator."""
132 """Interruptible revision log iterator."""
133 def __init__(self, stdout):
133 def __init__(self, stdout):
134 self._stdout = stdout
134 self._stdout = stdout
135
135
136 def __iter__(self):
136 def __iter__(self):
137 while True:
137 while True:
138 try:
138 try:
139 entry = pickle.load(self._stdout)
139 entry = pickle.load(self._stdout)
140 except EOFError:
140 except EOFError:
141 raise util.Abort(_('Mercurial failed to run itself, check'
141 raise util.Abort(_('Mercurial failed to run itself, check'
142 ' hg executable is in PATH'))
142 ' hg executable is in PATH'))
143 try:
143 try:
144 orig_paths, revnum, author, date, message = entry
144 orig_paths, revnum, author, date, message = entry
145 except:
145 except:
146 if entry is None:
146 if entry is None:
147 break
147 break
148 raise util.Abort(_("log stream exception '%s'") % entry)
148 raise util.Abort(_("log stream exception '%s'") % entry)
149 yield entry
149 yield entry
150
150
151 def close(self):
151 def close(self):
152 if self._stdout:
152 if self._stdout:
153 self._stdout.close()
153 self._stdout.close()
154 self._stdout = None
154 self._stdout = None
155
155
156
156
157 # Check to see if the given path is a local Subversion repo. Verify this by
157 # Check to see if the given path is a local Subversion repo. Verify this by
158 # looking for several svn-specific files and directories in the given
158 # looking for several svn-specific files and directories in the given
159 # directory.
159 # directory.
160 def filecheck(ui, path, proto):
160 def filecheck(ui, path, proto):
161 for x in ('locks', 'hooks', 'format', 'db'):
161 for x in ('locks', 'hooks', 'format', 'db'):
162 if not os.path.exists(os.path.join(path, x)):
162 if not os.path.exists(os.path.join(path, x)):
163 return False
163 return False
164 return True
164 return True
165
165
166 # Check to see if a given path is the root of an svn repo over http. We verify
166 # Check to see if a given path is the root of an svn repo over http. We verify
167 # this by requesting a version-controlled URL we know can't exist and looking
167 # this by requesting a version-controlled URL we know can't exist and looking
168 # for the svn-specific "not found" XML.
168 # for the svn-specific "not found" XML.
169 def httpcheck(ui, path, proto):
169 def httpcheck(ui, path, proto):
170 try:
170 try:
171 opener = urllib2.build_opener()
171 opener = urllib2.build_opener()
172 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
172 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
173 data = rsp.read()
173 data = rsp.read()
174 except urllib2.HTTPError, inst:
174 except urllib2.HTTPError, inst:
175 if inst.code != 404:
175 if inst.code != 404:
176 # Except for 404 we cannot know for sure this is not an svn repo
176 # Except for 404 we cannot know for sure this is not an svn repo
177 ui.warn(_('svn: cannot probe remote repository, assume it could '
177 ui.warn(_('svn: cannot probe remote repository, assume it could '
178 'be a subversion repository. Use --source-type if you '
178 'be a subversion repository. Use --source-type if you '
179 'know better.\n'))
179 'know better.\n'))
180 return True
180 return True
181 data = inst.fp.read()
181 data = inst.fp.read()
182 except:
182 except:
183 # Could be urllib2.URLError if the URL is invalid or anything else.
183 # Could be urllib2.URLError if the URL is invalid or anything else.
184 return False
184 return False
185 return '<m:human-readable errcode="160013">' in data
185 return '<m:human-readable errcode="160013">' in data
186
186
187 protomap = {'http': httpcheck,
187 protomap = {'http': httpcheck,
188 'https': httpcheck,
188 'https': httpcheck,
189 'file': filecheck,
189 'file': filecheck,
190 }
190 }
191 def issvnurl(ui, url):
191 def issvnurl(ui, url):
192 try:
192 try:
193 proto, path = url.split('://', 1)
193 proto, path = url.split('://', 1)
194 if proto == 'file':
194 if proto == 'file':
195 path = urllib.url2pathname(path)
195 path = urllib.url2pathname(path)
196 except ValueError:
196 except ValueError:
197 proto = 'file'
197 proto = 'file'
198 path = os.path.abspath(url)
198 path = os.path.abspath(url)
199 if proto == 'file':
199 if proto == 'file':
200 path = util.pconvert(path)
200 path = util.pconvert(path)
201 check = protomap.get(proto, lambda *args: False)
201 check = protomap.get(proto, lambda *args: False)
202 while '/' in path:
202 while '/' in path:
203 if check(ui, path, proto):
203 if check(ui, path, proto):
204 return True
204 return True
205 path = path.rsplit('/', 1)[0]
205 path = path.rsplit('/', 1)[0]
206 return False
206 return False
207
207
208 # SVN conversion code stolen from bzr-svn and tailor
208 # SVN conversion code stolen from bzr-svn and tailor
209 #
209 #
210 # Subversion looks like a versioned filesystem, branches structures
210 # Subversion looks like a versioned filesystem, branches structures
211 # are defined by conventions and not enforced by the tool. First,
211 # are defined by conventions and not enforced by the tool. First,
212 # we define the potential branches (modules) as "trunk" and "branches"
212 # we define the potential branches (modules) as "trunk" and "branches"
213 # children directories. Revisions are then identified by their
213 # children directories. Revisions are then identified by their
214 # module and revision number (and a repository identifier).
214 # module and revision number (and a repository identifier).
215 #
215 #
216 # The revision graph is really a tree (or a forest). By default, a
216 # The revision graph is really a tree (or a forest). By default, a
217 # revision parent is the previous revision in the same module. If the
217 # revision parent is the previous revision in the same module. If the
218 # module directory is copied/moved from another module then the
218 # module directory is copied/moved from another module then the
219 # revision is the module root and its parent the source revision in
219 # revision is the module root and its parent the source revision in
220 # the parent module. A revision has at most one parent.
220 # the parent module. A revision has at most one parent.
221 #
221 #
222 class svn_source(converter_source):
222 class svn_source(converter_source):
223 def __init__(self, ui, url, rev=None):
223 def __init__(self, ui, url, rev=None):
224 super(svn_source, self).__init__(ui, url, rev=rev)
224 super(svn_source, self).__init__(ui, url, rev=rev)
225
225
226 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
226 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
227 (os.path.exists(url) and
227 (os.path.exists(url) and
228 os.path.exists(os.path.join(url, '.svn'))) or
228 os.path.exists(os.path.join(url, '.svn'))) or
229 issvnurl(ui, url)):
229 issvnurl(ui, url)):
230 raise NoRepo(_("%s does not look like a Subversion repository")
230 raise NoRepo(_("%s does not look like a Subversion repository")
231 % url)
231 % url)
232 if svn is None:
232 if svn is None:
233 raise MissingTool(_('Could not load Subversion python bindings'))
233 raise MissingTool(_('Could not load Subversion python bindings'))
234
234
235 try:
235 try:
236 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
236 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
237 if version < (1, 4):
237 if version < (1, 4):
238 raise MissingTool(_('Subversion python bindings %d.%d found, '
238 raise MissingTool(_('Subversion python bindings %d.%d found, '
239 '1.4 or later required') % version)
239 '1.4 or later required') % version)
240 except AttributeError:
240 except AttributeError:
241 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
241 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
242 'or later required'))
242 'or later required'))
243
243
244 self.lastrevs = {}
244 self.lastrevs = {}
245
245
246 latest = None
246 latest = None
247 try:
247 try:
248 # Support file://path@rev syntax. Useful e.g. to convert
248 # Support file://path@rev syntax. Useful e.g. to convert
249 # deleted branches.
249 # deleted branches.
250 at = url.rfind('@')
250 at = url.rfind('@')
251 if at >= 0:
251 if at >= 0:
252 latest = int(url[at + 1:])
252 latest = int(url[at + 1:])
253 url = url[:at]
253 url = url[:at]
254 except ValueError:
254 except ValueError:
255 pass
255 pass
256 self.url = geturl(url)
256 self.url = geturl(url)
257 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
257 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
258 try:
258 try:
259 self.transport = transport.SvnRaTransport(url=self.url)
259 self.transport = transport.SvnRaTransport(url=self.url)
260 self.ra = self.transport.ra
260 self.ra = self.transport.ra
261 self.ctx = self.transport.client
261 self.ctx = self.transport.client
262 self.baseurl = svn.ra.get_repos_root(self.ra)
262 self.baseurl = svn.ra.get_repos_root(self.ra)
263 # Module is either empty or a repository path starting with
263 # Module is either empty or a repository path starting with
264 # a slash and not ending with a slash.
264 # a slash and not ending with a slash.
265 self.module = urllib.unquote(self.url[len(self.baseurl):])
265 self.module = urllib.unquote(self.url[len(self.baseurl):])
266 self.prevmodule = None
266 self.prevmodule = None
267 self.rootmodule = self.module
267 self.rootmodule = self.module
268 self.commits = {}
268 self.commits = {}
269 self.paths = {}
269 self.paths = {}
270 self.uuid = svn.ra.get_uuid(self.ra)
270 self.uuid = svn.ra.get_uuid(self.ra)
271 except SubversionException:
271 except SubversionException:
272 ui.traceback()
272 ui.traceback()
273 raise NoRepo(_("%s does not look like a Subversion repository")
273 raise NoRepo(_("%s does not look like a Subversion repository")
274 % self.url)
274 % self.url)
275
275
276 if rev:
276 if rev:
277 try:
277 try:
278 latest = int(rev)
278 latest = int(rev)
279 except ValueError:
279 except ValueError:
280 raise util.Abort(_('svn: revision %s is not an integer') % rev)
280 raise util.Abort(_('svn: revision %s is not an integer') % rev)
281
281
282 self.trunkname = self.ui.config('convert', 'svn.trunk', 'trunk').strip('/')
282 self.trunkname = self.ui.config('convert', 'svn.trunk', 'trunk').strip('/')
283 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
283 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
284 try:
284 try:
285 self.startrev = int(self.startrev)
285 self.startrev = int(self.startrev)
286 if self.startrev < 0:
286 if self.startrev < 0:
287 self.startrev = 0
287 self.startrev = 0
288 except ValueError:
288 except ValueError:
289 raise util.Abort(_('svn: start revision %s is not an integer')
289 raise util.Abort(_('svn: start revision %s is not an integer')
290 % self.startrev)
290 % self.startrev)
291
291
292 try:
292 try:
293 self.head = self.latest(self.module, latest)
293 self.head = self.latest(self.module, latest)
294 except SvnPathNotFound:
294 except SvnPathNotFound:
295 self.head = None
295 self.head = None
296 if not self.head:
296 if not self.head:
297 raise util.Abort(_('no revision found in module %s')
297 raise util.Abort(_('no revision found in module %s')
298 % self.module)
298 % self.module)
299 self.last_changed = self.revnum(self.head)
299 self.last_changed = self.revnum(self.head)
300
300
301 self._changescache = None
301 self._changescache = None
302
302
303 if os.path.exists(os.path.join(url, '.svn/entries')):
303 if os.path.exists(os.path.join(url, '.svn/entries')):
304 self.wc = url
304 self.wc = url
305 else:
305 else:
306 self.wc = None
306 self.wc = None
307 self.convertfp = None
307 self.convertfp = None
308
308
309 def setrevmap(self, revmap):
309 def setrevmap(self, revmap):
310 lastrevs = {}
310 lastrevs = {}
311 for revid in revmap.iterkeys():
311 for revid in revmap.iterkeys():
312 uuid, module, revnum = revsplit(revid)
312 uuid, module, revnum = revsplit(revid)
313 lastrevnum = lastrevs.setdefault(module, revnum)
313 lastrevnum = lastrevs.setdefault(module, revnum)
314 if revnum > lastrevnum:
314 if revnum > lastrevnum:
315 lastrevs[module] = revnum
315 lastrevs[module] = revnum
316 self.lastrevs = lastrevs
316 self.lastrevs = lastrevs
317
317
318 def exists(self, path, optrev):
318 def exists(self, path, optrev):
319 try:
319 try:
320 svn.client.ls(self.url.rstrip('/') + '/' + quote(path),
320 svn.client.ls(self.url.rstrip('/') + '/' + quote(path),
321 optrev, False, self.ctx)
321 optrev, False, self.ctx)
322 return True
322 return True
323 except SubversionException:
323 except SubversionException:
324 return False
324 return False
325
325
326 def getheads(self):
326 def getheads(self):
327
327
328 def isdir(path, revnum):
328 def isdir(path, revnum):
329 kind = self._checkpath(path, revnum)
329 kind = self._checkpath(path, revnum)
330 return kind == svn.core.svn_node_dir
330 return kind == svn.core.svn_node_dir
331
331
332 def getcfgpath(name, rev):
332 def getcfgpath(name, rev):
333 cfgpath = self.ui.config('convert', 'svn.' + name)
333 cfgpath = self.ui.config('convert', 'svn.' + name)
334 if cfgpath is not None and cfgpath.strip() == '':
334 if cfgpath is not None and cfgpath.strip() == '':
335 return None
335 return None
336 path = (cfgpath or name).strip('/')
336 path = (cfgpath or name).strip('/')
337 if not self.exists(path, rev):
337 if not self.exists(path, rev):
338 if self.module.endswith(path) and name == 'trunk':
338 if self.module.endswith(path) and name == 'trunk':
339 # we are converting from inside this directory
339 # we are converting from inside this directory
340 return None
340 return None
341 if cfgpath:
341 if cfgpath:
342 raise util.Abort(_('expected %s to be at %r, but not found')
342 raise util.Abort(_('expected %s to be at %r, but not found')
343 % (name, path))
343 % (name, path))
344 return None
344 return None
345 self.ui.note(_('found %s at %r\n') % (name, path))
345 self.ui.note(_('found %s at %r\n') % (name, path))
346 return path
346 return path
347
347
348 rev = optrev(self.last_changed)
348 rev = optrev(self.last_changed)
349 oldmodule = ''
349 oldmodule = ''
350 trunk = getcfgpath('trunk', rev)
350 trunk = getcfgpath('trunk', rev)
351 self.tags = getcfgpath('tags', rev)
351 self.tags = getcfgpath('tags', rev)
352 branches = getcfgpath('branches', rev)
352 branches = getcfgpath('branches', rev)
353
353
354 # If the project has a trunk or branches, we will extract heads
354 # If the project has a trunk or branches, we will extract heads
355 # from them. We keep the project root otherwise.
355 # from them. We keep the project root otherwise.
356 if trunk:
356 if trunk:
357 oldmodule = self.module or ''
357 oldmodule = self.module or ''
358 self.module += '/' + trunk
358 self.module += '/' + trunk
359 self.head = self.latest(self.module, self.last_changed)
359 self.head = self.latest(self.module, self.last_changed)
360 if not self.head:
360 if not self.head:
361 raise util.Abort(_('no revision found in module %s')
361 raise util.Abort(_('no revision found in module %s')
362 % self.module)
362 % self.module)
363
363
364 # First head in the list is the module's head
364 # First head in the list is the module's head
365 self.heads = [self.head]
365 self.heads = [self.head]
366 if self.tags is not None:
366 if self.tags is not None:
367 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
367 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
368
368
369 # Check if branches bring a few more heads to the list
369 # Check if branches bring a few more heads to the list
370 if branches:
370 if branches:
371 rpath = self.url.strip('/')
371 rpath = self.url.strip('/')
372 branchnames = svn.client.ls(rpath + '/' + quote(branches),
372 branchnames = svn.client.ls(rpath + '/' + quote(branches),
373 rev, False, self.ctx)
373 rev, False, self.ctx)
374 for branch in branchnames.keys():
374 for branch in branchnames.keys():
375 module = '%s/%s/%s' % (oldmodule, branches, branch)
375 module = '%s/%s/%s' % (oldmodule, branches, branch)
376 if not isdir(module, self.last_changed):
376 if not isdir(module, self.last_changed):
377 continue
377 continue
378 brevid = self.latest(module, self.last_changed)
378 brevid = self.latest(module, self.last_changed)
379 if not brevid:
379 if not brevid:
380 self.ui.note(_('ignoring empty branch %s\n') % branch)
380 self.ui.note(_('ignoring empty branch %s\n') % branch)
381 continue
381 continue
382 self.ui.note(_('found branch %s at %d\n') %
382 self.ui.note(_('found branch %s at %d\n') %
383 (branch, self.revnum(brevid)))
383 (branch, self.revnum(brevid)))
384 self.heads.append(brevid)
384 self.heads.append(brevid)
385
385
386 if self.startrev and self.heads:
386 if self.startrev and self.heads:
387 if len(self.heads) > 1:
387 if len(self.heads) > 1:
388 raise util.Abort(_('svn: start revision is not supported '
388 raise util.Abort(_('svn: start revision is not supported '
389 'with more than one branch'))
389 'with more than one branch'))
390 revnum = self.revnum(self.heads[0])
390 revnum = self.revnum(self.heads[0])
391 if revnum < self.startrev:
391 if revnum < self.startrev:
392 raise util.Abort(
392 raise util.Abort(
393 _('svn: no revision found after start revision %d')
393 _('svn: no revision found after start revision %d')
394 % self.startrev)
394 % self.startrev)
395
395
396 return self.heads
396 return self.heads
397
397
398 def getchanges(self, rev):
398 def getchanges(self, rev):
399 if self._changescache and self._changescache[0] == rev:
399 if self._changescache and self._changescache[0] == rev:
400 return self._changescache[1]
400 return self._changescache[1]
401 self._changescache = None
401 self._changescache = None
402 (paths, parents) = self.paths[rev]
402 (paths, parents) = self.paths[rev]
403 if parents:
403 if parents:
404 files, self.removed, copies = self.expandpaths(rev, paths, parents)
404 files, self.removed, copies = self.expandpaths(rev, paths, parents)
405 else:
405 else:
406 # Perform a full checkout on roots
406 # Perform a full checkout on roots
407 uuid, module, revnum = revsplit(rev)
407 uuid, module, revnum = revsplit(rev)
408 entries = svn.client.ls(self.baseurl + quote(module),
408 entries = svn.client.ls(self.baseurl + quote(module),
409 optrev(revnum), True, self.ctx)
409 optrev(revnum), True, self.ctx)
410 files = [n for n, e in entries.iteritems()
410 files = [n for n, e in entries.iteritems()
411 if e.kind == svn.core.svn_node_file]
411 if e.kind == svn.core.svn_node_file]
412 copies = {}
412 copies = {}
413 self.removed = set()
413 self.removed = set()
414
414
415 files.sort()
415 files.sort()
416 files = zip(files, [rev] * len(files))
416 files = zip(files, [rev] * len(files))
417
417
418 # caller caches the result, so free it here to release memory
418 # caller caches the result, so free it here to release memory
419 del self.paths[rev]
419 del self.paths[rev]
420 return (files, copies)
420 return (files, copies)
421
421
422 def getchangedfiles(self, rev, i):
422 def getchangedfiles(self, rev, i):
423 changes = self.getchanges(rev)
423 changes = self.getchanges(rev)
424 self._changescache = (rev, changes)
424 self._changescache = (rev, changes)
425 return [f[0] for f in changes[0]]
425 return [f[0] for f in changes[0]]
426
426
427 def getcommit(self, rev):
427 def getcommit(self, rev):
428 if rev not in self.commits:
428 if rev not in self.commits:
429 uuid, module, revnum = revsplit(rev)
429 uuid, module, revnum = revsplit(rev)
430 self.module = module
430 self.module = module
431 self.reparent(module)
431 self.reparent(module)
432 # We assume that:
432 # We assume that:
433 # - requests for revisions after "stop" come from the
433 # - requests for revisions after "stop" come from the
434 # revision graph backward traversal. Cache all of them
434 # revision graph backward traversal. Cache all of them
435 # down to stop, they will be used eventually.
435 # down to stop, they will be used eventually.
436 # - requests for revisions before "stop" come to get
436 # - requests for revisions before "stop" come to get
437 # isolated branches parents. Just fetch what is needed.
437 # isolated branches parents. Just fetch what is needed.
438 stop = self.lastrevs.get(module, 0)
438 stop = self.lastrevs.get(module, 0)
439 if revnum < stop:
439 if revnum < stop:
440 stop = revnum + 1
440 stop = revnum + 1
441 self._fetch_revisions(revnum, stop)
441 self._fetch_revisions(revnum, stop)
442 if rev not in self.commits:
442 if rev not in self.commits:
443 raise util.Abort(_('svn: revision %s not found') % revnum)
443 raise util.Abort(_('svn: revision %s not found') % revnum)
444 commit = self.commits[rev]
444 commit = self.commits[rev]
445 # caller caches the result, so free it here to release memory
445 # caller caches the result, so free it here to release memory
446 del self.commits[rev]
446 del self.commits[rev]
447 return commit
447 return commit
448
448
449 def gettags(self):
449 def gettags(self):
450 tags = {}
450 tags = {}
451 if self.tags is None:
451 if self.tags is None:
452 return tags
452 return tags
453
453
454 # svn tags are just a convention, project branches left in a
454 # svn tags are just a convention, project branches left in a
455 # 'tags' directory. There is no other relationship than
455 # 'tags' directory. There is no other relationship than
456 # ancestry, which is expensive to discover and makes them hard
456 # ancestry, which is expensive to discover and makes them hard
457 # to update incrementally. Worse, past revisions may be
457 # to update incrementally. Worse, past revisions may be
458 # referenced by tags far away in the future, requiring a deep
458 # referenced by tags far away in the future, requiring a deep
459 # history traversal on every calculation. Current code
459 # history traversal on every calculation. Current code
460 # performs a single backward traversal, tracking moves within
460 # performs a single backward traversal, tracking moves within
461 # the tags directory (tag renaming) and recording a new tag
461 # the tags directory (tag renaming) and recording a new tag
462 # everytime a project is copied from outside the tags
462 # everytime a project is copied from outside the tags
463 # directory. It also lists deleted tags, this behaviour may
463 # directory. It also lists deleted tags, this behaviour may
464 # change in the future.
464 # change in the future.
465 pendings = []
465 pendings = []
466 tagspath = self.tags
466 tagspath = self.tags
467 start = svn.ra.get_latest_revnum(self.ra)
467 start = svn.ra.get_latest_revnum(self.ra)
468 stream = self._getlog([self.tags], start, self.startrev)
468 stream = self._getlog([self.tags], start, self.startrev)
469 try:
469 try:
470 for entry in stream:
470 for entry in stream:
471 origpaths, revnum, author, date, message = entry
471 origpaths, revnum, author, date, message = entry
472 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
472 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
473 in origpaths.iteritems() if e.copyfrom_path]
473 in origpaths.iteritems() if e.copyfrom_path]
474 # Apply moves/copies from more specific to general
474 # Apply moves/copies from more specific to general
475 copies.sort(reverse=True)
475 copies.sort(reverse=True)
476
476
477 srctagspath = tagspath
477 srctagspath = tagspath
478 if copies and copies[-1][2] == tagspath:
478 if copies and copies[-1][2] == tagspath:
479 # Track tags directory moves
479 # Track tags directory moves
480 srctagspath = copies.pop()[0]
480 srctagspath = copies.pop()[0]
481
481
482 for source, sourcerev, dest in copies:
482 for source, sourcerev, dest in copies:
483 if not dest.startswith(tagspath + '/'):
483 if not dest.startswith(tagspath + '/'):
484 continue
484 continue
485 for tag in pendings:
485 for tag in pendings:
486 if tag[0].startswith(dest):
486 if tag[0].startswith(dest):
487 tagpath = source + tag[0][len(dest):]
487 tagpath = source + tag[0][len(dest):]
488 tag[:2] = [tagpath, sourcerev]
488 tag[:2] = [tagpath, sourcerev]
489 break
489 break
490 else:
490 else:
491 pendings.append([source, sourcerev, dest])
491 pendings.append([source, sourcerev, dest])
492
492
493 # Filter out tags with children coming from different
493 # Filter out tags with children coming from different
494 # parts of the repository like:
494 # parts of the repository like:
495 # /tags/tag.1 (from /trunk:10)
495 # /tags/tag.1 (from /trunk:10)
496 # /tags/tag.1/foo (from /branches/foo:12)
496 # /tags/tag.1/foo (from /branches/foo:12)
497 # Here/tags/tag.1 discarded as well as its children.
497 # Here/tags/tag.1 discarded as well as its children.
498 # It happens with tools like cvs2svn. Such tags cannot
498 # It happens with tools like cvs2svn. Such tags cannot
499 # be represented in mercurial.
499 # be represented in mercurial.
500 addeds = dict((p, e.copyfrom_path) for p, e
500 addeds = dict((p, e.copyfrom_path) for p, e
501 in origpaths.iteritems()
501 in origpaths.iteritems()
502 if e.action == 'A' and e.copyfrom_path)
502 if e.action == 'A' and e.copyfrom_path)
503 badroots = set()
503 badroots = set()
504 for destroot in addeds:
504 for destroot in addeds:
505 for source, sourcerev, dest in pendings:
505 for source, sourcerev, dest in pendings:
506 if (not dest.startswith(destroot + '/')
506 if (not dest.startswith(destroot + '/')
507 or source.startswith(addeds[destroot] + '/')):
507 or source.startswith(addeds[destroot] + '/')):
508 continue
508 continue
509 badroots.add(destroot)
509 badroots.add(destroot)
510 break
510 break
511
511
512 for badroot in badroots:
512 for badroot in badroots:
513 pendings = [p for p in pendings if p[2] != badroot
513 pendings = [p for p in pendings if p[2] != badroot
514 and not p[2].startswith(badroot + '/')]
514 and not p[2].startswith(badroot + '/')]
515
515
516 # Tell tag renamings from tag creations
516 # Tell tag renamings from tag creations
517 renamings = []
517 renamings = []
518 for source, sourcerev, dest in pendings:
518 for source, sourcerev, dest in pendings:
519 tagname = dest.split('/')[-1]
519 tagname = dest.split('/')[-1]
520 if source.startswith(srctagspath):
520 if source.startswith(srctagspath):
521 renamings.append([source, sourcerev, tagname])
521 renamings.append([source, sourcerev, tagname])
522 continue
522 continue
523 if tagname in tags:
523 if tagname in tags:
524 # Keep the latest tag value
524 # Keep the latest tag value
525 continue
525 continue
526 # From revision may be fake, get one with changes
526 # From revision may be fake, get one with changes
527 try:
527 try:
528 tagid = self.latest(source, sourcerev)
528 tagid = self.latest(source, sourcerev)
529 if tagid and tagname not in tags:
529 if tagid and tagname not in tags:
530 tags[tagname] = tagid
530 tags[tagname] = tagid
531 except SvnPathNotFound:
531 except SvnPathNotFound:
532 # It happens when we are following directories
532 # It happens when we are following directories
533 # we assumed were copied with their parents
533 # we assumed were copied with their parents
534 # but were really created in the tag
534 # but were really created in the tag
535 # directory.
535 # directory.
536 pass
536 pass
537 pendings = renamings
537 pendings = renamings
538 tagspath = srctagspath
538 tagspath = srctagspath
539 finally:
539 finally:
540 stream.close()
540 stream.close()
541 return tags
541 return tags
542
542
543 def converted(self, rev, destrev):
543 def converted(self, rev, destrev):
544 if not self.wc:
544 if not self.wc:
545 return
545 return
546 if self.convertfp is None:
546 if self.convertfp is None:
547 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
547 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
548 'a')
548 'a')
549 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
549 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
550 self.convertfp.flush()
550 self.convertfp.flush()
551
551
552 def revid(self, revnum, module=None):
552 def revid(self, revnum, module=None):
553 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
553 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
554
554
555 def revnum(self, rev):
555 def revnum(self, rev):
556 return int(rev.split('@')[-1])
556 return int(rev.split('@')[-1])
557
557
558 def latest(self, path, stop=None):
558 def latest(self, path, stop=None):
559 """Find the latest revid affecting path, up to stop revision
559 """Find the latest revid affecting path, up to stop revision
560 number. If stop is None, default to repository latest
560 number. If stop is None, default to repository latest
561 revision. It may return a revision in a different module,
561 revision. It may return a revision in a different module,
562 since a branch may be moved without a change being
562 since a branch may be moved without a change being
563 reported. Return None if computed module does not belong to
563 reported. Return None if computed module does not belong to
564 rootmodule subtree.
564 rootmodule subtree.
565 """
565 """
566 def findchanges(path, start, stop):
566 def findchanges(path, start, stop=None):
567 stream = self._getlog([path], start, stop)
567 stream = self._getlog([path], start, stop or 1)
568 try:
568 try:
569 for entry in stream:
569 for entry in stream:
570 paths, revnum, author, date, message = entry
570 paths, revnum, author, date, message = entry
571 if stop is None and paths:
572 # We do not know the latest changed revision,
573 # keep the first one with changed paths.
574 break
571 if revnum <= stop:
575 if revnum <= stop:
572 break
576 break
573
577
574 for p in paths:
578 for p in paths:
575 if (not path.startswith(p) or
579 if (not path.startswith(p) or
576 not paths[p].copyfrom_path):
580 not paths[p].copyfrom_path):
577 continue
581 continue
578 newpath = paths[p].copyfrom_path + path[len(p):]
582 newpath = paths[p].copyfrom_path + path[len(p):]
579 self.ui.debug("branch renamed from %s to %s at %d\n" %
583 self.ui.debug("branch renamed from %s to %s at %d\n" %
580 (path, newpath, revnum))
584 (path, newpath, revnum))
581 path = newpath
585 path = newpath
582 break
586 break
587 if not paths:
588 revnum = None
583 return revnum, path
589 return revnum, path
584 finally:
590 finally:
585 stream.close()
591 stream.close()
586
592
587 if not path.startswith(self.rootmodule):
593 if not path.startswith(self.rootmodule):
588 # Requests on foreign branches may be forbidden at server level
594 # Requests on foreign branches may be forbidden at server level
589 self.ui.debug('ignoring foreign branch %r\n' % path)
595 self.ui.debug('ignoring foreign branch %r\n' % path)
590 return None
596 return None
591
597
592 if stop is None:
598 if stop is None:
593 stop = svn.ra.get_latest_revnum(self.ra)
599 stop = svn.ra.get_latest_revnum(self.ra)
594 try:
600 try:
595 prevmodule = self.reparent('')
601 prevmodule = self.reparent('')
596 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
602 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
597 self.reparent(prevmodule)
603 self.reparent(prevmodule)
598 except SubversionException:
604 except SubversionException:
599 dirent = None
605 dirent = None
600 if not dirent:
606 if not dirent:
601 raise SvnPathNotFound(_('%s not found up to revision %d')
607 raise SvnPathNotFound(_('%s not found up to revision %d')
602 % (path, stop))
608 % (path, stop))
603
609
604 # stat() gives us the previous revision on this line of
610 # stat() gives us the previous revision on this line of
605 # development, but it might be in *another module*. Fetch the
611 # development, but it might be in *another module*. Fetch the
606 # log and detect renames down to the latest revision.
612 # log and detect renames down to the latest revision.
607 revnum, realpath = findchanges(path, stop, dirent.created_rev)
613 revnum, realpath = findchanges(path, stop, dirent.created_rev)
614 if revnum is None:
615 # Tools like svnsync can create empty revision, when
616 # synchronizing only a subtree for instance. These empty
617 # revisions created_rev still have their original values
618 # despite all changes having disappeared and can be
619 # returned by ra.stat(), at least when stating the root
620 # module. In that case, do not trust created_rev and scan
621 # the whole history.
622 revnum, realpath = findchanges(path, stop)
623 if revnum is None:
624 self.ui.debug('ignoring empty branch %r\n' % realpath)
625 return None
626
608 if not realpath.startswith(self.rootmodule):
627 if not realpath.startswith(self.rootmodule):
609 self.ui.debug('ignoring foreign branch %r\n' % realpath)
628 self.ui.debug('ignoring foreign branch %r\n' % realpath)
610 return None
629 return None
611 return self.revid(revnum, realpath)
630 return self.revid(revnum, realpath)
612
631
613 def reparent(self, module):
632 def reparent(self, module):
614 """Reparent the svn transport and return the previous parent."""
633 """Reparent the svn transport and return the previous parent."""
615 if self.prevmodule == module:
634 if self.prevmodule == module:
616 return module
635 return module
617 svnurl = self.baseurl + quote(module)
636 svnurl = self.baseurl + quote(module)
618 prevmodule = self.prevmodule
637 prevmodule = self.prevmodule
619 if prevmodule is None:
638 if prevmodule is None:
620 prevmodule = ''
639 prevmodule = ''
621 self.ui.debug("reparent to %s\n" % svnurl)
640 self.ui.debug("reparent to %s\n" % svnurl)
622 svn.ra.reparent(self.ra, svnurl)
641 svn.ra.reparent(self.ra, svnurl)
623 self.prevmodule = module
642 self.prevmodule = module
624 return prevmodule
643 return prevmodule
625
644
626 def expandpaths(self, rev, paths, parents):
645 def expandpaths(self, rev, paths, parents):
627 changed, removed = set(), set()
646 changed, removed = set(), set()
628 copies = {}
647 copies = {}
629
648
630 new_module, revnum = revsplit(rev)[1:]
649 new_module, revnum = revsplit(rev)[1:]
631 if new_module != self.module:
650 if new_module != self.module:
632 self.module = new_module
651 self.module = new_module
633 self.reparent(self.module)
652 self.reparent(self.module)
634
653
635 for i, (path, ent) in enumerate(paths):
654 for i, (path, ent) in enumerate(paths):
636 self.ui.progress(_('scanning paths'), i, item=path,
655 self.ui.progress(_('scanning paths'), i, item=path,
637 total=len(paths))
656 total=len(paths))
638 entrypath = self.getrelpath(path)
657 entrypath = self.getrelpath(path)
639
658
640 kind = self._checkpath(entrypath, revnum)
659 kind = self._checkpath(entrypath, revnum)
641 if kind == svn.core.svn_node_file:
660 if kind == svn.core.svn_node_file:
642 changed.add(self.recode(entrypath))
661 changed.add(self.recode(entrypath))
643 if not ent.copyfrom_path or not parents:
662 if not ent.copyfrom_path or not parents:
644 continue
663 continue
645 # Copy sources not in parent revisions cannot be
664 # Copy sources not in parent revisions cannot be
646 # represented, ignore their origin for now
665 # represented, ignore their origin for now
647 pmodule, prevnum = revsplit(parents[0])[1:]
666 pmodule, prevnum = revsplit(parents[0])[1:]
648 if ent.copyfrom_rev < prevnum:
667 if ent.copyfrom_rev < prevnum:
649 continue
668 continue
650 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
669 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
651 if not copyfrom_path:
670 if not copyfrom_path:
652 continue
671 continue
653 self.ui.debug("copied to %s from %s@%s\n" %
672 self.ui.debug("copied to %s from %s@%s\n" %
654 (entrypath, copyfrom_path, ent.copyfrom_rev))
673 (entrypath, copyfrom_path, ent.copyfrom_rev))
655 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
674 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
656 elif kind == 0: # gone, but had better be a deleted *file*
675 elif kind == 0: # gone, but had better be a deleted *file*
657 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
676 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
658 pmodule, prevnum = revsplit(parents[0])[1:]
677 pmodule, prevnum = revsplit(parents[0])[1:]
659 parentpath = pmodule + "/" + entrypath
678 parentpath = pmodule + "/" + entrypath
660 fromkind = self._checkpath(entrypath, prevnum, pmodule)
679 fromkind = self._checkpath(entrypath, prevnum, pmodule)
661
680
662 if fromkind == svn.core.svn_node_file:
681 if fromkind == svn.core.svn_node_file:
663 removed.add(self.recode(entrypath))
682 removed.add(self.recode(entrypath))
664 elif fromkind == svn.core.svn_node_dir:
683 elif fromkind == svn.core.svn_node_dir:
665 oroot = parentpath.strip('/')
684 oroot = parentpath.strip('/')
666 nroot = path.strip('/')
685 nroot = path.strip('/')
667 children = self._iterfiles(oroot, prevnum)
686 children = self._iterfiles(oroot, prevnum)
668 for childpath in children:
687 for childpath in children:
669 childpath = childpath.replace(oroot, nroot)
688 childpath = childpath.replace(oroot, nroot)
670 childpath = self.getrelpath("/" + childpath, pmodule)
689 childpath = self.getrelpath("/" + childpath, pmodule)
671 if childpath:
690 if childpath:
672 removed.add(self.recode(childpath))
691 removed.add(self.recode(childpath))
673 else:
692 else:
674 self.ui.debug('unknown path in revision %d: %s\n' % \
693 self.ui.debug('unknown path in revision %d: %s\n' % \
675 (revnum, path))
694 (revnum, path))
676 elif kind == svn.core.svn_node_dir:
695 elif kind == svn.core.svn_node_dir:
677 if ent.action == 'M':
696 if ent.action == 'M':
678 # If the directory just had a prop change,
697 # If the directory just had a prop change,
679 # then we shouldn't need to look for its children.
698 # then we shouldn't need to look for its children.
680 continue
699 continue
681 if ent.action == 'R' and parents:
700 if ent.action == 'R' and parents:
682 # If a directory is replacing a file, mark the previous
701 # If a directory is replacing a file, mark the previous
683 # file as deleted
702 # file as deleted
684 pmodule, prevnum = revsplit(parents[0])[1:]
703 pmodule, prevnum = revsplit(parents[0])[1:]
685 pkind = self._checkpath(entrypath, prevnum, pmodule)
704 pkind = self._checkpath(entrypath, prevnum, pmodule)
686 if pkind == svn.core.svn_node_file:
705 if pkind == svn.core.svn_node_file:
687 removed.add(self.recode(entrypath))
706 removed.add(self.recode(entrypath))
688 elif pkind == svn.core.svn_node_dir:
707 elif pkind == svn.core.svn_node_dir:
689 # We do not know what files were kept or removed,
708 # We do not know what files were kept or removed,
690 # mark them all as changed.
709 # mark them all as changed.
691 for childpath in self._iterfiles(pmodule, prevnum):
710 for childpath in self._iterfiles(pmodule, prevnum):
692 childpath = self.getrelpath("/" + childpath)
711 childpath = self.getrelpath("/" + childpath)
693 if childpath:
712 if childpath:
694 changed.add(self.recode(childpath))
713 changed.add(self.recode(childpath))
695
714
696 for childpath in self._iterfiles(path, revnum):
715 for childpath in self._iterfiles(path, revnum):
697 childpath = self.getrelpath("/" + childpath)
716 childpath = self.getrelpath("/" + childpath)
698 if childpath:
717 if childpath:
699 changed.add(self.recode(childpath))
718 changed.add(self.recode(childpath))
700
719
701 # Handle directory copies
720 # Handle directory copies
702 if not ent.copyfrom_path or not parents:
721 if not ent.copyfrom_path or not parents:
703 continue
722 continue
704 # Copy sources not in parent revisions cannot be
723 # Copy sources not in parent revisions cannot be
705 # represented, ignore their origin for now
724 # represented, ignore their origin for now
706 pmodule, prevnum = revsplit(parents[0])[1:]
725 pmodule, prevnum = revsplit(parents[0])[1:]
707 if ent.copyfrom_rev < prevnum:
726 if ent.copyfrom_rev < prevnum:
708 continue
727 continue
709 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
728 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
710 if not copyfrompath:
729 if not copyfrompath:
711 continue
730 continue
712 self.ui.debug("mark %s came from %s:%d\n"
731 self.ui.debug("mark %s came from %s:%d\n"
713 % (path, copyfrompath, ent.copyfrom_rev))
732 % (path, copyfrompath, ent.copyfrom_rev))
714 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
733 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
715 for childpath in children:
734 for childpath in children:
716 childpath = self.getrelpath("/" + childpath, pmodule)
735 childpath = self.getrelpath("/" + childpath, pmodule)
717 if not childpath:
736 if not childpath:
718 continue
737 continue
719 copytopath = path + childpath[len(copyfrompath):]
738 copytopath = path + childpath[len(copyfrompath):]
720 copytopath = self.getrelpath(copytopath)
739 copytopath = self.getrelpath(copytopath)
721 copies[self.recode(copytopath)] = self.recode(childpath)
740 copies[self.recode(copytopath)] = self.recode(childpath)
722
741
723 self.ui.progress(_('scanning paths'), None)
742 self.ui.progress(_('scanning paths'), None)
724 changed.update(removed)
743 changed.update(removed)
725 return (list(changed), removed, copies)
744 return (list(changed), removed, copies)
726
745
727 def _fetch_revisions(self, from_revnum, to_revnum):
746 def _fetch_revisions(self, from_revnum, to_revnum):
728 if from_revnum < to_revnum:
747 if from_revnum < to_revnum:
729 from_revnum, to_revnum = to_revnum, from_revnum
748 from_revnum, to_revnum = to_revnum, from_revnum
730
749
731 self.child_cset = None
750 self.child_cset = None
732
751
733 def parselogentry(orig_paths, revnum, author, date, message):
752 def parselogentry(orig_paths, revnum, author, date, message):
734 """Return the parsed commit object or None, and True if
753 """Return the parsed commit object or None, and True if
735 the revision is a branch root.
754 the revision is a branch root.
736 """
755 """
737 self.ui.debug("parsing revision %d (%d changes)\n" %
756 self.ui.debug("parsing revision %d (%d changes)\n" %
738 (revnum, len(orig_paths)))
757 (revnum, len(orig_paths)))
739
758
740 branched = False
759 branched = False
741 rev = self.revid(revnum)
760 rev = self.revid(revnum)
742 # branch log might return entries for a parent we already have
761 # branch log might return entries for a parent we already have
743
762
744 if rev in self.commits or revnum < to_revnum:
763 if rev in self.commits or revnum < to_revnum:
745 return None, branched
764 return None, branched
746
765
747 parents = []
766 parents = []
748 # check whether this revision is the start of a branch or part
767 # check whether this revision is the start of a branch or part
749 # of a branch renaming
768 # of a branch renaming
750 orig_paths = sorted(orig_paths.iteritems())
769 orig_paths = sorted(orig_paths.iteritems())
751 root_paths = [(p, e) for p, e in orig_paths
770 root_paths = [(p, e) for p, e in orig_paths
752 if self.module.startswith(p)]
771 if self.module.startswith(p)]
753 if root_paths:
772 if root_paths:
754 path, ent = root_paths[-1]
773 path, ent = root_paths[-1]
755 if ent.copyfrom_path:
774 if ent.copyfrom_path:
756 branched = True
775 branched = True
757 newpath = ent.copyfrom_path + self.module[len(path):]
776 newpath = ent.copyfrom_path + self.module[len(path):]
758 # ent.copyfrom_rev may not be the actual last revision
777 # ent.copyfrom_rev may not be the actual last revision
759 previd = self.latest(newpath, ent.copyfrom_rev)
778 previd = self.latest(newpath, ent.copyfrom_rev)
760 if previd is not None:
779 if previd is not None:
761 prevmodule, prevnum = revsplit(previd)[1:]
780 prevmodule, prevnum = revsplit(previd)[1:]
762 if prevnum >= self.startrev:
781 if prevnum >= self.startrev:
763 parents = [previd]
782 parents = [previd]
764 self.ui.note(
783 self.ui.note(
765 _('found parent of branch %s at %d: %s\n') %
784 _('found parent of branch %s at %d: %s\n') %
766 (self.module, prevnum, prevmodule))
785 (self.module, prevnum, prevmodule))
767 else:
786 else:
768 self.ui.debug("no copyfrom path, don't know what to do.\n")
787 self.ui.debug("no copyfrom path, don't know what to do.\n")
769
788
770 paths = []
789 paths = []
771 # filter out unrelated paths
790 # filter out unrelated paths
772 for path, ent in orig_paths:
791 for path, ent in orig_paths:
773 if self.getrelpath(path) is None:
792 if self.getrelpath(path) is None:
774 continue
793 continue
775 paths.append((path, ent))
794 paths.append((path, ent))
776
795
777 # Example SVN datetime. Includes microseconds.
796 # Example SVN datetime. Includes microseconds.
778 # ISO-8601 conformant
797 # ISO-8601 conformant
779 # '2007-01-04T17:35:00.902377Z'
798 # '2007-01-04T17:35:00.902377Z'
780 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
799 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
781
800
782 log = message and self.recode(message) or ''
801 log = message and self.recode(message) or ''
783 author = author and self.recode(author) or ''
802 author = author and self.recode(author) or ''
784 try:
803 try:
785 branch = self.module.split("/")[-1]
804 branch = self.module.split("/")[-1]
786 if branch == self.trunkname:
805 if branch == self.trunkname:
787 branch = None
806 branch = None
788 except IndexError:
807 except IndexError:
789 branch = None
808 branch = None
790
809
791 cset = commit(author=author,
810 cset = commit(author=author,
792 date=util.datestr(date),
811 date=util.datestr(date),
793 desc=log,
812 desc=log,
794 parents=parents,
813 parents=parents,
795 branch=branch,
814 branch=branch,
796 rev=rev)
815 rev=rev)
797
816
798 self.commits[rev] = cset
817 self.commits[rev] = cset
799 # The parents list is *shared* among self.paths and the
818 # The parents list is *shared* among self.paths and the
800 # commit object. Both will be updated below.
819 # commit object. Both will be updated below.
801 self.paths[rev] = (paths, cset.parents)
820 self.paths[rev] = (paths, cset.parents)
802 if self.child_cset and not self.child_cset.parents:
821 if self.child_cset and not self.child_cset.parents:
803 self.child_cset.parents[:] = [rev]
822 self.child_cset.parents[:] = [rev]
804 self.child_cset = cset
823 self.child_cset = cset
805 return cset, branched
824 return cset, branched
806
825
807 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
826 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
808 (self.module, from_revnum, to_revnum))
827 (self.module, from_revnum, to_revnum))
809
828
810 try:
829 try:
811 firstcset = None
830 firstcset = None
812 lastonbranch = False
831 lastonbranch = False
813 stream = self._getlog([self.module], from_revnum, to_revnum)
832 stream = self._getlog([self.module], from_revnum, to_revnum)
814 try:
833 try:
815 for entry in stream:
834 for entry in stream:
816 paths, revnum, author, date, message = entry
835 paths, revnum, author, date, message = entry
817 if revnum < self.startrev:
836 if revnum < self.startrev:
818 lastonbranch = True
837 lastonbranch = True
819 break
838 break
820 if not paths:
839 if not paths:
821 self.ui.debug('revision %d has no entries\n' % revnum)
840 self.ui.debug('revision %d has no entries\n' % revnum)
822 # If we ever leave the loop on an empty
841 # If we ever leave the loop on an empty
823 # revision, do not try to get a parent branch
842 # revision, do not try to get a parent branch
824 lastonbranch = lastonbranch or revnum == 0
843 lastonbranch = lastonbranch or revnum == 0
825 continue
844 continue
826 cset, lastonbranch = parselogentry(paths, revnum, author,
845 cset, lastonbranch = parselogentry(paths, revnum, author,
827 date, message)
846 date, message)
828 if cset:
847 if cset:
829 firstcset = cset
848 firstcset = cset
830 if lastonbranch:
849 if lastonbranch:
831 break
850 break
832 finally:
851 finally:
833 stream.close()
852 stream.close()
834
853
835 if not lastonbranch and firstcset and not firstcset.parents:
854 if not lastonbranch and firstcset and not firstcset.parents:
836 # The first revision of the sequence (the last fetched one)
855 # The first revision of the sequence (the last fetched one)
837 # has invalid parents if not a branch root. Find the parent
856 # has invalid parents if not a branch root. Find the parent
838 # revision now, if any.
857 # revision now, if any.
839 try:
858 try:
840 firstrevnum = self.revnum(firstcset.rev)
859 firstrevnum = self.revnum(firstcset.rev)
841 if firstrevnum > 1:
860 if firstrevnum > 1:
842 latest = self.latest(self.module, firstrevnum - 1)
861 latest = self.latest(self.module, firstrevnum - 1)
843 if latest:
862 if latest:
844 firstcset.parents.append(latest)
863 firstcset.parents.append(latest)
845 except SvnPathNotFound:
864 except SvnPathNotFound:
846 pass
865 pass
847 except SubversionException, (inst, num):
866 except SubversionException, (inst, num):
848 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
867 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
849 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
868 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
850 raise
869 raise
851
870
852 def getfile(self, file, rev):
871 def getfile(self, file, rev):
853 # TODO: ra.get_file transmits the whole file instead of diffs.
872 # TODO: ra.get_file transmits the whole file instead of diffs.
854 if file in self.removed:
873 if file in self.removed:
855 raise IOError()
874 raise IOError()
856 mode = ''
875 mode = ''
857 try:
876 try:
858 new_module, revnum = revsplit(rev)[1:]
877 new_module, revnum = revsplit(rev)[1:]
859 if self.module != new_module:
878 if self.module != new_module:
860 self.module = new_module
879 self.module = new_module
861 self.reparent(self.module)
880 self.reparent(self.module)
862 io = StringIO()
881 io = StringIO()
863 info = svn.ra.get_file(self.ra, file, revnum, io)
882 info = svn.ra.get_file(self.ra, file, revnum, io)
864 data = io.getvalue()
883 data = io.getvalue()
865 # ra.get_files() seems to keep a reference on the input buffer
884 # ra.get_files() seems to keep a reference on the input buffer
866 # preventing collection. Release it explicitely.
885 # preventing collection. Release it explicitely.
867 io.close()
886 io.close()
868 if isinstance(info, list):
887 if isinstance(info, list):
869 info = info[-1]
888 info = info[-1]
870 mode = ("svn:executable" in info) and 'x' or ''
889 mode = ("svn:executable" in info) and 'x' or ''
871 mode = ("svn:special" in info) and 'l' or mode
890 mode = ("svn:special" in info) and 'l' or mode
872 except SubversionException, e:
891 except SubversionException, e:
873 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
892 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
874 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
893 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
875 if e.apr_err in notfound: # File not found
894 if e.apr_err in notfound: # File not found
876 raise IOError()
895 raise IOError()
877 raise
896 raise
878 if mode == 'l':
897 if mode == 'l':
879 link_prefix = "link "
898 link_prefix = "link "
880 if data.startswith(link_prefix):
899 if data.startswith(link_prefix):
881 data = data[len(link_prefix):]
900 data = data[len(link_prefix):]
882 return data, mode
901 return data, mode
883
902
884 def _iterfiles(self, path, revnum):
903 def _iterfiles(self, path, revnum):
885 """Enumerate all files in path at revnum, recursively."""
904 """Enumerate all files in path at revnum, recursively."""
886 path = path.strip('/')
905 path = path.strip('/')
887 pool = Pool()
906 pool = Pool()
888 rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
907 rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
889 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
908 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
890 if path:
909 if path:
891 path += '/'
910 path += '/'
892 return ((path + p) for p, e in entries.iteritems()
911 return ((path + p) for p, e in entries.iteritems()
893 if e.kind == svn.core.svn_node_file)
912 if e.kind == svn.core.svn_node_file)
894
913
895 def getrelpath(self, path, module=None):
914 def getrelpath(self, path, module=None):
896 if module is None:
915 if module is None:
897 module = self.module
916 module = self.module
898 # Given the repository url of this wc, say
917 # Given the repository url of this wc, say
899 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
918 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
900 # extract the "entry" portion (a relative path) from what
919 # extract the "entry" portion (a relative path) from what
901 # svn log --xml says, ie
920 # svn log --xml says, ie
902 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
921 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
903 # that is to say "tests/PloneTestCase.py"
922 # that is to say "tests/PloneTestCase.py"
904 if path.startswith(module):
923 if path.startswith(module):
905 relative = path.rstrip('/')[len(module):]
924 relative = path.rstrip('/')[len(module):]
906 if relative.startswith('/'):
925 if relative.startswith('/'):
907 return relative[1:]
926 return relative[1:]
908 elif relative == '':
927 elif relative == '':
909 return relative
928 return relative
910
929
911 # The path is outside our tracked tree...
930 # The path is outside our tracked tree...
912 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
931 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
913 return None
932 return None
914
933
915 def _checkpath(self, path, revnum, module=None):
934 def _checkpath(self, path, revnum, module=None):
916 if module is not None:
935 if module is not None:
917 prevmodule = self.reparent('')
936 prevmodule = self.reparent('')
918 path = module + '/' + path
937 path = module + '/' + path
919 try:
938 try:
920 # ra.check_path does not like leading slashes very much, it leads
939 # ra.check_path does not like leading slashes very much, it leads
921 # to PROPFIND subversion errors
940 # to PROPFIND subversion errors
922 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
941 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
923 finally:
942 finally:
924 if module is not None:
943 if module is not None:
925 self.reparent(prevmodule)
944 self.reparent(prevmodule)
926
945
927 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
946 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
928 strict_node_history=False):
947 strict_node_history=False):
929 # Normalize path names, svn >= 1.5 only wants paths relative to
948 # Normalize path names, svn >= 1.5 only wants paths relative to
930 # supplied URL
949 # supplied URL
931 relpaths = []
950 relpaths = []
932 for p in paths:
951 for p in paths:
933 if not p.startswith('/'):
952 if not p.startswith('/'):
934 p = self.module + '/' + p
953 p = self.module + '/' + p
935 relpaths.append(p.strip('/'))
954 relpaths.append(p.strip('/'))
936 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
955 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
937 strict_node_history]
956 strict_node_history]
938 arg = encodeargs(args)
957 arg = encodeargs(args)
939 hgexe = util.hgexecutable()
958 hgexe = util.hgexecutable()
940 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
959 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
941 stdin, stdout = util.popen2(util.quotecommand(cmd))
960 stdin, stdout = util.popen2(util.quotecommand(cmd))
942 stdin.write(arg)
961 stdin.write(arg)
943 try:
962 try:
944 stdin.close()
963 stdin.close()
945 except IOError:
964 except IOError:
946 raise util.Abort(_('Mercurial failed to run itself, check'
965 raise util.Abort(_('Mercurial failed to run itself, check'
947 ' hg executable is in PATH'))
966 ' hg executable is in PATH'))
948 return logstream(stdout)
967 return logstream(stdout)
949
968
950 pre_revprop_change = '''#!/bin/sh
969 pre_revprop_change = '''#!/bin/sh
951
970
952 REPOS="$1"
971 REPOS="$1"
953 REV="$2"
972 REV="$2"
954 USER="$3"
973 USER="$3"
955 PROPNAME="$4"
974 PROPNAME="$4"
956 ACTION="$5"
975 ACTION="$5"
957
976
958 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
977 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
959 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
978 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
960 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
979 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
961
980
962 echo "Changing prohibited revision property" >&2
981 echo "Changing prohibited revision property" >&2
963 exit 1
982 exit 1
964 '''
983 '''
965
984
966 class svn_sink(converter_sink, commandline):
985 class svn_sink(converter_sink, commandline):
967 commit_re = re.compile(r'Committed revision (\d+).', re.M)
986 commit_re = re.compile(r'Committed revision (\d+).', re.M)
968 uuid_re = re.compile(r'Repository UUID:\s*(\S+)', re.M)
987 uuid_re = re.compile(r'Repository UUID:\s*(\S+)', re.M)
969
988
970 def prerun(self):
989 def prerun(self):
971 if self.wc:
990 if self.wc:
972 os.chdir(self.wc)
991 os.chdir(self.wc)
973
992
974 def postrun(self):
993 def postrun(self):
975 if self.wc:
994 if self.wc:
976 os.chdir(self.cwd)
995 os.chdir(self.cwd)
977
996
978 def join(self, name):
997 def join(self, name):
979 return os.path.join(self.wc, '.svn', name)
998 return os.path.join(self.wc, '.svn', name)
980
999
981 def revmapfile(self):
1000 def revmapfile(self):
982 return self.join('hg-shamap')
1001 return self.join('hg-shamap')
983
1002
984 def authorfile(self):
1003 def authorfile(self):
985 return self.join('hg-authormap')
1004 return self.join('hg-authormap')
986
1005
987 def __init__(self, ui, path):
1006 def __init__(self, ui, path):
988
1007
989 converter_sink.__init__(self, ui, path)
1008 converter_sink.__init__(self, ui, path)
990 commandline.__init__(self, ui, 'svn')
1009 commandline.__init__(self, ui, 'svn')
991 self.delete = []
1010 self.delete = []
992 self.setexec = []
1011 self.setexec = []
993 self.delexec = []
1012 self.delexec = []
994 self.copies = []
1013 self.copies = []
995 self.wc = None
1014 self.wc = None
996 self.cwd = os.getcwd()
1015 self.cwd = os.getcwd()
997
1016
998 path = os.path.realpath(path)
1017 path = os.path.realpath(path)
999
1018
1000 created = False
1019 created = False
1001 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
1020 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
1002 self.wc = path
1021 self.wc = path
1003 self.run0('update')
1022 self.run0('update')
1004 else:
1023 else:
1005 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
1024 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
1006
1025
1007 if os.path.isdir(os.path.dirname(path)):
1026 if os.path.isdir(os.path.dirname(path)):
1008 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
1027 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
1009 ui.status(_('initializing svn repository %r\n') %
1028 ui.status(_('initializing svn repository %r\n') %
1010 os.path.basename(path))
1029 os.path.basename(path))
1011 commandline(ui, 'svnadmin').run0('create', path)
1030 commandline(ui, 'svnadmin').run0('create', path)
1012 created = path
1031 created = path
1013 path = util.normpath(path)
1032 path = util.normpath(path)
1014 if not path.startswith('/'):
1033 if not path.startswith('/'):
1015 path = '/' + path
1034 path = '/' + path
1016 path = 'file://' + path
1035 path = 'file://' + path
1017
1036
1018 ui.status(_('initializing svn working copy %r\n')
1037 ui.status(_('initializing svn working copy %r\n')
1019 % os.path.basename(wcpath))
1038 % os.path.basename(wcpath))
1020 self.run0('checkout', path, wcpath)
1039 self.run0('checkout', path, wcpath)
1021
1040
1022 self.wc = wcpath
1041 self.wc = wcpath
1023 self.opener = scmutil.opener(self.wc)
1042 self.opener = scmutil.opener(self.wc)
1024 self.wopener = scmutil.opener(self.wc)
1043 self.wopener = scmutil.opener(self.wc)
1025 self.childmap = mapfile(ui, self.join('hg-childmap'))
1044 self.childmap = mapfile(ui, self.join('hg-childmap'))
1026 self.is_exec = util.checkexec(self.wc) and util.isexec or None
1045 self.is_exec = util.checkexec(self.wc) and util.isexec or None
1027
1046
1028 if created:
1047 if created:
1029 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1048 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1030 fp = open(hook, 'w')
1049 fp = open(hook, 'w')
1031 fp.write(pre_revprop_change)
1050 fp.write(pre_revprop_change)
1032 fp.close()
1051 fp.close()
1033 util.setflags(hook, False, True)
1052 util.setflags(hook, False, True)
1034
1053
1035 output = self.run0('info')
1054 output = self.run0('info')
1036 self.uuid = self.uuid_re.search(output).group(1).strip()
1055 self.uuid = self.uuid_re.search(output).group(1).strip()
1037
1056
1038 def wjoin(self, *names):
1057 def wjoin(self, *names):
1039 return os.path.join(self.wc, *names)
1058 return os.path.join(self.wc, *names)
1040
1059
1041 def putfile(self, filename, flags, data):
1060 def putfile(self, filename, flags, data):
1042 if 'l' in flags:
1061 if 'l' in flags:
1043 self.wopener.symlink(data, filename)
1062 self.wopener.symlink(data, filename)
1044 else:
1063 else:
1045 try:
1064 try:
1046 if os.path.islink(self.wjoin(filename)):
1065 if os.path.islink(self.wjoin(filename)):
1047 os.unlink(filename)
1066 os.unlink(filename)
1048 except OSError:
1067 except OSError:
1049 pass
1068 pass
1050 self.wopener.write(filename, data)
1069 self.wopener.write(filename, data)
1051
1070
1052 if self.is_exec:
1071 if self.is_exec:
1053 was_exec = self.is_exec(self.wjoin(filename))
1072 was_exec = self.is_exec(self.wjoin(filename))
1054 else:
1073 else:
1055 # On filesystems not supporting execute-bit, there is no way
1074 # On filesystems not supporting execute-bit, there is no way
1056 # to know if it is set but asking subversion. Setting it
1075 # to know if it is set but asking subversion. Setting it
1057 # systematically is just as expensive and much simpler.
1076 # systematically is just as expensive and much simpler.
1058 was_exec = 'x' not in flags
1077 was_exec = 'x' not in flags
1059
1078
1060 util.setflags(self.wjoin(filename), False, 'x' in flags)
1079 util.setflags(self.wjoin(filename), False, 'x' in flags)
1061 if was_exec:
1080 if was_exec:
1062 if 'x' not in flags:
1081 if 'x' not in flags:
1063 self.delexec.append(filename)
1082 self.delexec.append(filename)
1064 else:
1083 else:
1065 if 'x' in flags:
1084 if 'x' in flags:
1066 self.setexec.append(filename)
1085 self.setexec.append(filename)
1067
1086
1068 def _copyfile(self, source, dest):
1087 def _copyfile(self, source, dest):
1069 # SVN's copy command pukes if the destination file exists, but
1088 # SVN's copy command pukes if the destination file exists, but
1070 # our copyfile method expects to record a copy that has
1089 # our copyfile method expects to record a copy that has
1071 # already occurred. Cross the semantic gap.
1090 # already occurred. Cross the semantic gap.
1072 wdest = self.wjoin(dest)
1091 wdest = self.wjoin(dest)
1073 exists = os.path.lexists(wdest)
1092 exists = os.path.lexists(wdest)
1074 if exists:
1093 if exists:
1075 fd, tempname = tempfile.mkstemp(
1094 fd, tempname = tempfile.mkstemp(
1076 prefix='hg-copy-', dir=os.path.dirname(wdest))
1095 prefix='hg-copy-', dir=os.path.dirname(wdest))
1077 os.close(fd)
1096 os.close(fd)
1078 os.unlink(tempname)
1097 os.unlink(tempname)
1079 os.rename(wdest, tempname)
1098 os.rename(wdest, tempname)
1080 try:
1099 try:
1081 self.run0('copy', source, dest)
1100 self.run0('copy', source, dest)
1082 finally:
1101 finally:
1083 if exists:
1102 if exists:
1084 try:
1103 try:
1085 os.unlink(wdest)
1104 os.unlink(wdest)
1086 except OSError:
1105 except OSError:
1087 pass
1106 pass
1088 os.rename(tempname, wdest)
1107 os.rename(tempname, wdest)
1089
1108
1090 def dirs_of(self, files):
1109 def dirs_of(self, files):
1091 dirs = set()
1110 dirs = set()
1092 for f in files:
1111 for f in files:
1093 if os.path.isdir(self.wjoin(f)):
1112 if os.path.isdir(self.wjoin(f)):
1094 dirs.add(f)
1113 dirs.add(f)
1095 for i in strutil.rfindall(f, '/'):
1114 for i in strutil.rfindall(f, '/'):
1096 dirs.add(f[:i])
1115 dirs.add(f[:i])
1097 return dirs
1116 return dirs
1098
1117
1099 def add_dirs(self, files):
1118 def add_dirs(self, files):
1100 add_dirs = [d for d in sorted(self.dirs_of(files))
1119 add_dirs = [d for d in sorted(self.dirs_of(files))
1101 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1120 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1102 if add_dirs:
1121 if add_dirs:
1103 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1122 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1104 return add_dirs
1123 return add_dirs
1105
1124
1106 def add_files(self, files):
1125 def add_files(self, files):
1107 if files:
1126 if files:
1108 self.xargs(files, 'add', quiet=True)
1127 self.xargs(files, 'add', quiet=True)
1109 return files
1128 return files
1110
1129
1111 def tidy_dirs(self, names):
1130 def tidy_dirs(self, names):
1112 deleted = []
1131 deleted = []
1113 for d in sorted(self.dirs_of(names), reverse=True):
1132 for d in sorted(self.dirs_of(names), reverse=True):
1114 wd = self.wjoin(d)
1133 wd = self.wjoin(d)
1115 if os.listdir(wd) == '.svn':
1134 if os.listdir(wd) == '.svn':
1116 self.run0('delete', d)
1135 self.run0('delete', d)
1117 deleted.append(d)
1136 deleted.append(d)
1118 return deleted
1137 return deleted
1119
1138
1120 def addchild(self, parent, child):
1139 def addchild(self, parent, child):
1121 self.childmap[parent] = child
1140 self.childmap[parent] = child
1122
1141
1123 def revid(self, rev):
1142 def revid(self, rev):
1124 return u"svn:%s@%s" % (self.uuid, rev)
1143 return u"svn:%s@%s" % (self.uuid, rev)
1125
1144
1126 def putcommit(self, files, copies, parents, commit, source, revmap):
1145 def putcommit(self, files, copies, parents, commit, source, revmap):
1127 for parent in parents:
1146 for parent in parents:
1128 try:
1147 try:
1129 return self.revid(self.childmap[parent])
1148 return self.revid(self.childmap[parent])
1130 except KeyError:
1149 except KeyError:
1131 pass
1150 pass
1132
1151
1133 # Apply changes to working copy
1152 # Apply changes to working copy
1134 for f, v in files:
1153 for f, v in files:
1135 try:
1154 try:
1136 data, mode = source.getfile(f, v)
1155 data, mode = source.getfile(f, v)
1137 except IOError:
1156 except IOError:
1138 self.delete.append(f)
1157 self.delete.append(f)
1139 else:
1158 else:
1140 self.putfile(f, mode, data)
1159 self.putfile(f, mode, data)
1141 if f in copies:
1160 if f in copies:
1142 self.copies.append([copies[f], f])
1161 self.copies.append([copies[f], f])
1143 files = [f[0] for f in files]
1162 files = [f[0] for f in files]
1144
1163
1145 entries = set(self.delete)
1164 entries = set(self.delete)
1146 files = frozenset(files)
1165 files = frozenset(files)
1147 entries.update(self.add_dirs(files.difference(entries)))
1166 entries.update(self.add_dirs(files.difference(entries)))
1148 if self.copies:
1167 if self.copies:
1149 for s, d in self.copies:
1168 for s, d in self.copies:
1150 self._copyfile(s, d)
1169 self._copyfile(s, d)
1151 self.copies = []
1170 self.copies = []
1152 if self.delete:
1171 if self.delete:
1153 self.xargs(self.delete, 'delete')
1172 self.xargs(self.delete, 'delete')
1154 self.delete = []
1173 self.delete = []
1155 entries.update(self.add_files(files.difference(entries)))
1174 entries.update(self.add_files(files.difference(entries)))
1156 entries.update(self.tidy_dirs(entries))
1175 entries.update(self.tidy_dirs(entries))
1157 if self.delexec:
1176 if self.delexec:
1158 self.xargs(self.delexec, 'propdel', 'svn:executable')
1177 self.xargs(self.delexec, 'propdel', 'svn:executable')
1159 self.delexec = []
1178 self.delexec = []
1160 if self.setexec:
1179 if self.setexec:
1161 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1180 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1162 self.setexec = []
1181 self.setexec = []
1163
1182
1164 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1183 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1165 fp = os.fdopen(fd, 'w')
1184 fp = os.fdopen(fd, 'w')
1166 fp.write(commit.desc)
1185 fp.write(commit.desc)
1167 fp.close()
1186 fp.close()
1168 try:
1187 try:
1169 output = self.run0('commit',
1188 output = self.run0('commit',
1170 username=util.shortuser(commit.author),
1189 username=util.shortuser(commit.author),
1171 file=messagefile,
1190 file=messagefile,
1172 encoding='utf-8')
1191 encoding='utf-8')
1173 try:
1192 try:
1174 rev = self.commit_re.search(output).group(1)
1193 rev = self.commit_re.search(output).group(1)
1175 except AttributeError:
1194 except AttributeError:
1176 if not files:
1195 if not files:
1177 return parents[0]
1196 return parents[0]
1178 self.ui.warn(_('unexpected svn output:\n'))
1197 self.ui.warn(_('unexpected svn output:\n'))
1179 self.ui.warn(output)
1198 self.ui.warn(output)
1180 raise util.Abort(_('unable to cope with svn output'))
1199 raise util.Abort(_('unable to cope with svn output'))
1181 if commit.rev:
1200 if commit.rev:
1182 self.run('propset', 'hg:convert-rev', commit.rev,
1201 self.run('propset', 'hg:convert-rev', commit.rev,
1183 revprop=True, revision=rev)
1202 revprop=True, revision=rev)
1184 if commit.branch and commit.branch != 'default':
1203 if commit.branch and commit.branch != 'default':
1185 self.run('propset', 'hg:convert-branch', commit.branch,
1204 self.run('propset', 'hg:convert-branch', commit.branch,
1186 revprop=True, revision=rev)
1205 revprop=True, revision=rev)
1187 for parent in parents:
1206 for parent in parents:
1188 self.addchild(parent, rev)
1207 self.addchild(parent, rev)
1189 return self.revid(rev)
1208 return self.revid(rev)
1190 finally:
1209 finally:
1191 os.unlink(messagefile)
1210 os.unlink(messagefile)
1192
1211
1193 def puttags(self, tags):
1212 def puttags(self, tags):
1194 self.ui.warn(_('writing Subversion tags is not yet implemented\n'))
1213 self.ui.warn(_('writing Subversion tags is not yet implemented\n'))
1195 return None, None
1214 return None, None
1196
1215
1197 def hascommit(self, rev):
1216 def hascommit(self, rev):
1198 # This is not correct as one can convert to an existing subversion
1217 # This is not correct as one can convert to an existing subversion
1199 # repository and childmap would not list all revisions. Too bad.
1218 # repository and childmap would not list all revisions. Too bad.
1200 if rev in self.childmap:
1219 if rev in self.childmap:
1201 return True
1220 return True
1202 raise util.Abort(_('splice map revision %s not found in subversion '
1221 raise util.Abort(_('splice map revision %s not found in subversion '
1203 'child map (revision lookups are not implemented)')
1222 'child map (revision lookups are not implemented)')
1204 % rev)
1223 % rev)
@@ -1,189 +1,210 b''
1
1
2 $ "$TESTDIR/hghave" svn svn-bindings || exit 80
2 $ "$TESTDIR/hghave" svn svn-bindings || exit 80
3
3
4 $ fixpath()
4 $ fixpath()
5 > {
5 > {
6 > tr '\\' /
6 > tr '\\' /
7 > }
7 > }
8 $ cat >> $HGRCPATH <<EOF
8 $ cat >> $HGRCPATH <<EOF
9 > [extensions]
9 > [extensions]
10 > convert =
10 > convert =
11 > graphlog =
11 > graphlog =
12 > [convert]
12 > [convert]
13 > svn.trunk = mytrunk
13 > svn.trunk = mytrunk
14 > EOF
14 > EOF
15
15
16 $ svnadmin create svn-repo
16 $ svnadmin create svn-repo
17 $ svnpath=`pwd | fixpath`
17 $ svnpath=`pwd | fixpath`
18
18
19
19
20 $ expr "$svnpath" : "\/" > /dev/null
20 $ expr "$svnpath" : "\/" > /dev/null
21 > if [ $? -ne 0 ]; then
21 > if [ $? -ne 0 ]; then
22 > svnpath="/$svnpath"
22 > svnpath="/$svnpath"
23 > fi
23 > fi
24 > svnurl="file://$svnpath/svn-repo"
24 > svnurl="file://$svnpath/svn-repo"
25
25
26 Now test that it works with trunk/tags layout, but no branches yet.
26 Now test that it works with trunk/tags layout, but no branches yet.
27
27
28 Initial svn import
28 Initial svn import
29
29
30 $ mkdir projB
30 $ mkdir projB
31 $ cd projB
31 $ cd projB
32 $ mkdir mytrunk
32 $ mkdir mytrunk
33 $ mkdir tags
33 $ mkdir tags
34 $ cd ..
34 $ cd ..
35
35
36 $ svnurl="file://$svnpath/svn-repo/proj%20B"
36 $ svnurl="file://$svnpath/svn-repo/proj%20B"
37 $ svn import -m "init projB" projB "$svnurl" | fixpath
37 $ svn import -m "init projB" projB "$svnurl" | fixpath
38 Adding projB/mytrunk
38 Adding projB/mytrunk
39 Adding projB/tags
39 Adding projB/tags
40
40
41 Committed revision 1.
41 Committed revision 1.
42
42
43 Update svn repository
43 Update svn repository
44
44
45 $ svn co "$svnurl"/mytrunk B | fixpath
45 $ svn co "$svnurl"/mytrunk B | fixpath
46 Checked out revision 1.
46 Checked out revision 1.
47 $ cd B
47 $ cd B
48 $ echo hello > 'letter .txt'
48 $ echo hello > 'letter .txt'
49 $ svn add 'letter .txt'
49 $ svn add 'letter .txt'
50 A letter .txt
50 A letter .txt
51 $ svn ci -m hello
51 $ svn ci -m hello
52 Adding letter .txt
52 Adding letter .txt
53 Transmitting file data .
53 Transmitting file data .
54 Committed revision 2.
54 Committed revision 2.
55
55
56 $ "$TESTDIR/svn-safe-append.py" world 'letter .txt'
56 $ "$TESTDIR/svn-safe-append.py" world 'letter .txt'
57 $ svn ci -m world
57 $ svn ci -m world
58 Sending letter .txt
58 Sending letter .txt
59 Transmitting file data .
59 Transmitting file data .
60 Committed revision 3.
60 Committed revision 3.
61
61
62 $ svn copy -m "tag v0.1" "$svnurl"/mytrunk "$svnurl"/tags/v0.1
62 $ svn copy -m "tag v0.1" "$svnurl"/mytrunk "$svnurl"/tags/v0.1
63
63
64 Committed revision 4.
64 Committed revision 4.
65
65
66 $ "$TESTDIR/svn-safe-append.py" 'nice day today!' 'letter .txt'
66 $ "$TESTDIR/svn-safe-append.py" 'nice day today!' 'letter .txt'
67 $ svn ci -m "nice day"
67 $ svn ci -m "nice day"
68 Sending letter .txt
68 Sending letter .txt
69 Transmitting file data .
69 Transmitting file data .
70 Committed revision 5.
70 Committed revision 5.
71 $ cd ..
71 $ cd ..
72
72
73 Convert to hg once
73 Convert to hg once
74
74
75 $ hg convert "$svnurl" B-hg
75 $ hg convert "$svnurl" B-hg
76 initializing destination B-hg repository
76 initializing destination B-hg repository
77 scanning source...
77 scanning source...
78 sorting...
78 sorting...
79 converting...
79 converting...
80 3 init projB
80 3 init projB
81 2 hello
81 2 hello
82 1 world
82 1 world
83 0 nice day
83 0 nice day
84 updating tags
84 updating tags
85
85
86 Update svn repository again
86 Update svn repository again
87
87
88 $ cd B
88 $ cd B
89 $ "$TESTDIR/svn-safe-append.py" "see second letter" 'letter .txt'
89 $ "$TESTDIR/svn-safe-append.py" "see second letter" 'letter .txt'
90 $ echo "nice to meet you" > letter2.txt
90 $ echo "nice to meet you" > letter2.txt
91 $ svn add letter2.txt
91 $ svn add letter2.txt
92 A letter2.txt
92 A letter2.txt
93 $ svn ci -m "second letter"
93 $ svn ci -m "second letter"
94 Sending letter .txt
94 Sending letter .txt
95 Adding letter2.txt
95 Adding letter2.txt
96 Transmitting file data ..
96 Transmitting file data ..
97 Committed revision 6.
97 Committed revision 6.
98
98
99 $ svn copy -m "tag v0.2" "$svnurl"/mytrunk "$svnurl"/tags/v0.2
99 $ svn copy -m "tag v0.2" "$svnurl"/mytrunk "$svnurl"/tags/v0.2
100
100
101 Committed revision 7.
101 Committed revision 7.
102
102
103 $ "$TESTDIR/svn-safe-append.py" "blah-blah-blah" letter2.txt
103 $ "$TESTDIR/svn-safe-append.py" "blah-blah-blah" letter2.txt
104 $ svn ci -m "work in progress"
104 $ svn ci -m "work in progress"
105 Sending letter2.txt
105 Sending letter2.txt
106 Transmitting file data .
106 Transmitting file data .
107 Committed revision 8.
107 Committed revision 8.
108 $ cd ..
108 $ cd ..
109
109
110 $ hg convert -s svn "$svnurl/non-existent-path" dest
110 $ hg convert -s svn "$svnurl/non-existent-path" dest
111 initializing destination dest repository
111 initializing destination dest repository
112 abort: no revision found in module /proj B/non-existent-path
112 abort: no revision found in module /proj B/non-existent-path
113 [255]
113 [255]
114
114
115 ########################################
115 ########################################
116
116
117 Test incremental conversion
117 Test incremental conversion
118
118
119 $ hg convert "$svnurl" B-hg
119 $ hg convert "$svnurl" B-hg
120 scanning source...
120 scanning source...
121 sorting...
121 sorting...
122 converting...
122 converting...
123 1 second letter
123 1 second letter
124 0 work in progress
124 0 work in progress
125 updating tags
125 updating tags
126
126
127 $ cd B-hg
127 $ cd B-hg
128 $ hg glog --template '{rev} {desc|firstline} files: {files}\n'
128 $ hg glog --template '{rev} {desc|firstline} files: {files}\n'
129 o 7 update tags files: .hgtags
129 o 7 update tags files: .hgtags
130 |
130 |
131 o 6 work in progress files: letter2.txt
131 o 6 work in progress files: letter2.txt
132 |
132 |
133 o 5 second letter files: letter .txt letter2.txt
133 o 5 second letter files: letter .txt letter2.txt
134 |
134 |
135 o 4 update tags files: .hgtags
135 o 4 update tags files: .hgtags
136 |
136 |
137 o 3 nice day files: letter .txt
137 o 3 nice day files: letter .txt
138 |
138 |
139 o 2 world files: letter .txt
139 o 2 world files: letter .txt
140 |
140 |
141 o 1 hello files: letter .txt
141 o 1 hello files: letter .txt
142 |
142 |
143 o 0 init projB files:
143 o 0 init projB files:
144
144
145 $ hg tags -q
145 $ hg tags -q
146 tip
146 tip
147 v0.2
147 v0.2
148 v0.1
148 v0.1
149 $ cd ..
149 $ cd ..
150
150
151 Test filemap
151 Test filemap
152 $ echo 'include letter2.txt' > filemap
152 $ echo 'include letter2.txt' > filemap
153 $ hg convert --filemap filemap "$svnurl"/mytrunk fmap
153 $ hg convert --filemap filemap "$svnurl"/mytrunk fmap
154 initializing destination fmap repository
154 initializing destination fmap repository
155 scanning source...
155 scanning source...
156 sorting...
156 sorting...
157 converting...
157 converting...
158 5 init projB
158 5 init projB
159 4 hello
159 4 hello
160 3 world
160 3 world
161 2 nice day
161 2 nice day
162 1 second letter
162 1 second letter
163 0 work in progress
163 0 work in progress
164 $ hg -R fmap branch -q
164 $ hg -R fmap branch -q
165 default
165 default
166 $ hg glog -R fmap --template '{rev} {desc|firstline} files: {files}\n'
166 $ hg glog -R fmap --template '{rev} {desc|firstline} files: {files}\n'
167 o 1 work in progress files: letter2.txt
167 o 1 work in progress files: letter2.txt
168 |
168 |
169 o 0 second letter files: letter2.txt
169 o 0 second letter files: letter2.txt
170
170
171
171
172 Test stop revision
172 Test stop revision
173 $ hg convert --rev 1 "$svnurl"/mytrunk stoprev
173 $ hg convert --rev 1 "$svnurl"/mytrunk stoprev
174 initializing destination stoprev repository
174 initializing destination stoprev repository
175 scanning source...
175 scanning source...
176 sorting...
176 sorting...
177 converting...
177 converting...
178 0 init projB
178 0 init projB
179 $ hg -R stoprev branch -q
179 $ hg -R stoprev branch -q
180 default
180 default
181
181
182 Check convert_revision extra-records.
182 Check convert_revision extra-records.
183 This is also the only place testing more than one extra field in a revision.
183 This is also the only place testing more than one extra field in a revision.
184
184
185 $ cd stoprev
185 $ cd stoprev
186 $ hg tip --debug | grep extra
186 $ hg tip --debug | grep extra
187 extra: branch=default
187 extra: branch=default
188 extra: convert_revision=svn:........-....-....-....-............/proj B/mytrunk@1 (re)
188 extra: convert_revision=svn:........-....-....-....-............/proj B/mytrunk@1 (re)
189 $ cd ..
189 $ cd ..
190
191 Test converting empty heads (issue3347)
192
193 $ svnadmin create svn-empty
194 $ svnadmin load -q svn-empty < "$TESTDIR/svn/empty.svndump"
195 $ hg --config convert.svn.trunk= convert svn-empty
196 assuming destination svn-empty-hg
197 initializing destination svn-empty-hg repository
198 scanning source...
199 sorting...
200 converting...
201 1 init projA
202 0 adddir
203 $ hg --config convert.svn.trunk= convert file://$svnpath/svn-empty/trunk
204 assuming destination trunk-hg
205 initializing destination trunk-hg repository
206 scanning source...
207 sorting...
208 converting...
209 1 init projA
210 0 adddir
General Comments 0
You need to be logged in to leave comments. Login now