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