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