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