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