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