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