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