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