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