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