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