##// END OF EJS Templates
convert svn: try to extract URL from source if it is a working directory
Brendan Cully -
r5010:6b2d8caf default
parent child Browse files
Show More
@@ -1,672 +1,678 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 #
11 #
12 # Set these in a hgrc, or on the command line as follows:
12 # Set these in a hgrc, or on the command line as follows:
13 #
13 #
14 # hg convert --config convert.svn.trunk=wackoname [...]
14 # hg convert --config convert.svn.trunk=wackoname [...]
15
15
16 import locale
16 import locale
17 import os
17 import os
18 import cPickle as pickle
18 import cPickle as pickle
19 from mercurial import util
19 from mercurial import util
20
20
21 # Subversion stuff. Works best with very recent Python SVN bindings
21 # Subversion stuff. Works best with very recent Python SVN bindings
22 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
22 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
23 # these bindings.
23 # these bindings.
24
24
25 from cStringIO import StringIO
25 from cStringIO import StringIO
26
26
27 from common import NoRepo, commit, converter_source
27 from common import NoRepo, commit, converter_source
28
28
29 try:
29 try:
30 from svn.core import SubversionException, Pool
30 from svn.core import SubversionException, Pool
31 import svn
32 import svn.client
31 import svn.core
33 import svn.core
32 import svn.ra
34 import svn.ra
33 import svn.delta
35 import svn.delta
34 import svn
35 import transport
36 import transport
36 except ImportError:
37 except ImportError:
37 pass
38 pass
38
39
39 def geturl(path):
40 def geturl(path):
41 try:
42 #extract URL from working directory
43 return svn.client.url_from_path(path)
44 except SubversionException:
45 pass
40 if os.path.isdir(path):
46 if os.path.isdir(path):
41 return 'file://%s' % os.path.normpath(os.path.abspath(path))
47 return 'file://%s' % os.path.normpath(os.path.abspath(path))
42 return path
48 return path
43
49
44 class CompatibilityException(Exception): pass
50 class CompatibilityException(Exception): pass
45
51
46 class changedpath(object):
52 class changedpath(object):
47 def __init__(self, p):
53 def __init__(self, p):
48 self.copyfrom_path = p.copyfrom_path
54 self.copyfrom_path = p.copyfrom_path
49 self.copyfrom_rev = p.copyfrom_rev
55 self.copyfrom_rev = p.copyfrom_rev
50 self.action = p.action
56 self.action = p.action
51
57
52 # SVN conversion code stolen from bzr-svn and tailor
58 # SVN conversion code stolen from bzr-svn and tailor
53 class convert_svn(converter_source):
59 class convert_svn(converter_source):
54 def __init__(self, ui, url, rev=None):
60 def __init__(self, ui, url, rev=None):
55 super(convert_svn, self).__init__(ui, url, rev=rev)
61 super(convert_svn, self).__init__(ui, url, rev=rev)
56
62
57 try:
63 try:
58 SubversionException
64 SubversionException
59 except NameError:
65 except NameError:
60 msg = 'subversion python bindings could not be loaded\n'
66 msg = 'subversion python bindings could not be loaded\n'
61 ui.warn(msg)
67 ui.warn(msg)
62 raise NoRepo(msg)
68 raise NoRepo(msg)
63
69
64 self.encoding = locale.getpreferredencoding()
70 self.encoding = locale.getpreferredencoding()
65 self.lastrevs = {}
71 self.lastrevs = {}
66
72
67 latest = None
73 latest = None
68 if rev:
74 if rev:
69 try:
75 try:
70 latest = int(rev)
76 latest = int(rev)
71 except ValueError:
77 except ValueError:
72 raise util.Abort('svn: revision %s is not an integer' % rev)
78 raise util.Abort('svn: revision %s is not an integer' % rev)
73 try:
79 try:
74 # Support file://path@rev syntax. Useful e.g. to convert
80 # Support file://path@rev syntax. Useful e.g. to convert
75 # deleted branches.
81 # deleted branches.
76 at = url.rfind('@')
82 at = url.rfind('@')
77 if at >= 0:
83 if at >= 0:
78 latest = int(url[at+1:])
84 latest = int(url[at+1:])
79 url = url[:at]
85 url = url[:at]
80 except ValueError, e:
86 except ValueError, e:
81 pass
87 pass
82 self.url = geturl(url)
88 self.url = geturl(url)
83 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
89 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
84 try:
90 try:
85 self.transport = transport.SvnRaTransport(url=self.url)
91 self.transport = transport.SvnRaTransport(url=self.url)
86 self.ra = self.transport.ra
92 self.ra = self.transport.ra
87 self.ctx = self.transport.client
93 self.ctx = self.transport.client
88 self.base = svn.ra.get_repos_root(self.ra)
94 self.base = svn.ra.get_repos_root(self.ra)
89 self.module = self.url[len(self.base):]
95 self.module = self.url[len(self.base):]
90 self.modulemap = {} # revision, module
96 self.modulemap = {} # revision, module
91 self.commits = {}
97 self.commits = {}
92 self.files = {}
98 self.files = {}
93 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
99 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
94 except SubversionException, e:
100 except SubversionException, e:
95 raise NoRepo("couldn't open SVN repo %s" % url)
101 raise NoRepo("couldn't open SVN repo %s" % self.url)
96
102
97 try:
103 try:
98 self.get_blacklist()
104 self.get_blacklist()
99 except IOError, e:
105 except IOError, e:
100 pass
106 pass
101
107
102 self.last_changed = self.latest(self.module, latest)
108 self.last_changed = self.latest(self.module, latest)
103
109
104 self.head = self.revid(self.last_changed)
110 self.head = self.revid(self.last_changed)
105
111
106 def setrevmap(self, revmap):
112 def setrevmap(self, revmap):
107 lastrevs = {}
113 lastrevs = {}
108 for revid in revmap.keys():
114 for revid in revmap.keys():
109 uuid, module, revnum = self.revsplit(revid)
115 uuid, module, revnum = self.revsplit(revid)
110 lastrevnum = lastrevs.setdefault(module, revnum)
116 lastrevnum = lastrevs.setdefault(module, revnum)
111 if revnum > lastrevnum:
117 if revnum > lastrevnum:
112 lastrevs[module] = revnum
118 lastrevs[module] = revnum
113 self.lastrevs = lastrevs
119 self.lastrevs = lastrevs
114
120
115 def exists(self, path, optrev):
121 def exists(self, path, optrev):
116 try:
122 try:
117 return svn.client.ls(self.url.rstrip('/') + '/' + path,
123 return svn.client.ls(self.url.rstrip('/') + '/' + path,
118 optrev, False, self.ctx)
124 optrev, False, self.ctx)
119 except SubversionException, err:
125 except SubversionException, err:
120 return []
126 return []
121
127
122 def getheads(self):
128 def getheads(self):
123 # detect standard /branches, /tags, /trunk layout
129 # detect standard /branches, /tags, /trunk layout
124 optrev = svn.core.svn_opt_revision_t()
130 optrev = svn.core.svn_opt_revision_t()
125 optrev.kind = svn.core.svn_opt_revision_number
131 optrev.kind = svn.core.svn_opt_revision_number
126 optrev.value.number = self.last_changed
132 optrev.value.number = self.last_changed
127 rpath = self.url.strip('/')
133 rpath = self.url.strip('/')
128 cfgtrunk = self.ui.config('convert', 'svn.trunk')
134 cfgtrunk = self.ui.config('convert', 'svn.trunk')
129 cfgbranches = self.ui.config('convert', 'svn.branches')
135 cfgbranches = self.ui.config('convert', 'svn.branches')
130 trunk = (cfgtrunk or 'trunk').strip('/')
136 trunk = (cfgtrunk or 'trunk').strip('/')
131 branches = (cfgbranches or 'branches').strip('/')
137 branches = (cfgbranches or 'branches').strip('/')
132 if self.exists(trunk, optrev) and self.exists(branches, optrev):
138 if self.exists(trunk, optrev) and self.exists(branches, optrev):
133 self.ui.note('found trunk at %r and branches at %r\n' %
139 self.ui.note('found trunk at %r and branches at %r\n' %
134 (trunk, branches))
140 (trunk, branches))
135 oldmodule = self.module
141 oldmodule = self.module
136 self.module += '/' + trunk
142 self.module += '/' + trunk
137 lt = self.latest(self.module, self.last_changed)
143 lt = self.latest(self.module, self.last_changed)
138 self.head = self.revid(lt)
144 self.head = self.revid(lt)
139 self.heads = [self.head]
145 self.heads = [self.head]
140 branchnames = svn.client.ls(rpath + '/' + branches, optrev, False,
146 branchnames = svn.client.ls(rpath + '/' + branches, optrev, False,
141 self.ctx)
147 self.ctx)
142 for branch in branchnames.keys():
148 for branch in branchnames.keys():
143 if oldmodule:
149 if oldmodule:
144 module = '/' + oldmodule + '/' + branches + '/' + branch
150 module = '/' + oldmodule + '/' + branches + '/' + branch
145 else:
151 else:
146 module = '/' + branches + '/' + branch
152 module = '/' + branches + '/' + branch
147 brevnum = self.latest(module, self.last_changed)
153 brevnum = self.latest(module, self.last_changed)
148 brev = self.revid(brevnum, module)
154 brev = self.revid(brevnum, module)
149 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
155 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
150 self.heads.append(brev)
156 self.heads.append(brev)
151 elif cfgtrunk or cfgbranches:
157 elif cfgtrunk or cfgbranches:
152 raise util.Abort(_('trunk/branch layout expected, '
158 raise util.Abort(_('trunk/branch layout expected, '
153 'but not found'))
159 'but not found'))
154 else:
160 else:
155 self.ui.note('working with one branch\n')
161 self.ui.note('working with one branch\n')
156 self.heads = [self.head]
162 self.heads = [self.head]
157 return self.heads
163 return self.heads
158
164
159 def getfile(self, file, rev):
165 def getfile(self, file, rev):
160 data, mode = self._getfile(file, rev)
166 data, mode = self._getfile(file, rev)
161 self.modecache[(file, rev)] = mode
167 self.modecache[(file, rev)] = mode
162 return data
168 return data
163
169
164 def getmode(self, file, rev):
170 def getmode(self, file, rev):
165 return self.modecache[(file, rev)]
171 return self.modecache[(file, rev)]
166
172
167 def getchanges(self, rev):
173 def getchanges(self, rev):
168 self.modecache = {}
174 self.modecache = {}
169 files = self.files[rev]
175 files = self.files[rev]
170 cl = files
176 cl = files
171 cl.sort()
177 cl.sort()
172 # caller caches the result, so free it here to release memory
178 # caller caches the result, so free it here to release memory
173 del self.files[rev]
179 del self.files[rev]
174 return cl
180 return cl
175
181
176 def getcommit(self, rev):
182 def getcommit(self, rev):
177 if rev not in self.commits:
183 if rev not in self.commits:
178 uuid, module, revnum = self.revsplit(rev)
184 uuid, module, revnum = self.revsplit(rev)
179 self.module = module
185 self.module = module
180 self.reparent(module)
186 self.reparent(module)
181 stop = self.lastrevs.get(module, 0)
187 stop = self.lastrevs.get(module, 0)
182 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
188 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
183 commit = self.commits[rev]
189 commit = self.commits[rev]
184 # caller caches the result, so free it here to release memory
190 # caller caches the result, so free it here to release memory
185 del self.commits[rev]
191 del self.commits[rev]
186 return commit
192 return commit
187
193
188 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
194 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
189 strict_node_history=False):
195 strict_node_history=False):
190 '''wrapper for svn.ra.get_log.
196 '''wrapper for svn.ra.get_log.
191 on a large repository, svn.ra.get_log pins huge amounts of
197 on a large repository, svn.ra.get_log pins huge amounts of
192 memory that cannot be recovered. work around it by forking
198 memory that cannot be recovered. work around it by forking
193 and writing results over a pipe.'''
199 and writing results over a pipe.'''
194
200
195 def child(fp):
201 def child(fp):
196 protocol = -1
202 protocol = -1
197 def receiver(orig_paths, revnum, author, date, message, pool):
203 def receiver(orig_paths, revnum, author, date, message, pool):
198 if orig_paths is not None:
204 if orig_paths is not None:
199 for k, v in orig_paths.iteritems():
205 for k, v in orig_paths.iteritems():
200 orig_paths[k] = changedpath(v)
206 orig_paths[k] = changedpath(v)
201 pickle.dump((orig_paths, revnum, author, date, message),
207 pickle.dump((orig_paths, revnum, author, date, message),
202 fp, protocol)
208 fp, protocol)
203
209
204 try:
210 try:
205 # Use an ra of our own so that our parent can consume
211 # Use an ra of our own so that our parent can consume
206 # our results without confusing the server.
212 # our results without confusing the server.
207 t = transport.SvnRaTransport(url=self.url)
213 t = transport.SvnRaTransport(url=self.url)
208 svn.ra.get_log(t.ra, paths, start, end, limit,
214 svn.ra.get_log(t.ra, paths, start, end, limit,
209 discover_changed_paths,
215 discover_changed_paths,
210 strict_node_history,
216 strict_node_history,
211 receiver)
217 receiver)
212 except SubversionException, (_, num):
218 except SubversionException, (_, num):
213 self.ui.print_exc()
219 self.ui.print_exc()
214 pickle.dump(num, fp, protocol)
220 pickle.dump(num, fp, protocol)
215 else:
221 else:
216 pickle.dump(None, fp, protocol)
222 pickle.dump(None, fp, protocol)
217 fp.close()
223 fp.close()
218
224
219 def parent(fp):
225 def parent(fp):
220 while True:
226 while True:
221 entry = pickle.load(fp)
227 entry = pickle.load(fp)
222 try:
228 try:
223 orig_paths, revnum, author, date, message = entry
229 orig_paths, revnum, author, date, message = entry
224 except:
230 except:
225 if entry is None:
231 if entry is None:
226 break
232 break
227 raise SubversionException("child raised exception", entry)
233 raise SubversionException("child raised exception", entry)
228 yield entry
234 yield entry
229
235
230 rfd, wfd = os.pipe()
236 rfd, wfd = os.pipe()
231 pid = os.fork()
237 pid = os.fork()
232 if pid:
238 if pid:
233 os.close(wfd)
239 os.close(wfd)
234 for p in parent(os.fdopen(rfd, 'rb')):
240 for p in parent(os.fdopen(rfd, 'rb')):
235 yield p
241 yield p
236 ret = os.waitpid(pid, 0)[1]
242 ret = os.waitpid(pid, 0)[1]
237 if ret:
243 if ret:
238 raise util.Abort(_('get_log %s') % util.explain_exit(ret))
244 raise util.Abort(_('get_log %s') % util.explain_exit(ret))
239 else:
245 else:
240 os.close(rfd)
246 os.close(rfd)
241 child(os.fdopen(wfd, 'wb'))
247 child(os.fdopen(wfd, 'wb'))
242 os._exit(0)
248 os._exit(0)
243
249
244 def gettags(self):
250 def gettags(self):
245 tags = {}
251 tags = {}
246 start = self.revnum(self.head)
252 start = self.revnum(self.head)
247 try:
253 try:
248 for entry in self.get_log(['/tags'], 0, start):
254 for entry in self.get_log(['/tags'], 0, start):
249 orig_paths, revnum, author, date, message = entry
255 orig_paths, revnum, author, date, message = entry
250 for path in orig_paths:
256 for path in orig_paths:
251 if not path.startswith('/tags/'):
257 if not path.startswith('/tags/'):
252 continue
258 continue
253 ent = orig_paths[path]
259 ent = orig_paths[path]
254 source = ent.copyfrom_path
260 source = ent.copyfrom_path
255 rev = ent.copyfrom_rev
261 rev = ent.copyfrom_rev
256 tag = path.split('/', 2)[2]
262 tag = path.split('/', 2)[2]
257 tags[tag] = self.revid(rev, module=source)
263 tags[tag] = self.revid(rev, module=source)
258 except SubversionException, (_, num):
264 except SubversionException, (_, num):
259 self.ui.note('no tags found at revision %d\n' % start)
265 self.ui.note('no tags found at revision %d\n' % start)
260 return tags
266 return tags
261
267
262 # -- helper functions --
268 # -- helper functions --
263
269
264 def revid(self, revnum, module=None):
270 def revid(self, revnum, module=None):
265 if not module:
271 if not module:
266 module = self.module
272 module = self.module
267 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
273 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
268
274
269 def revnum(self, rev):
275 def revnum(self, rev):
270 return int(rev.split('@')[-1])
276 return int(rev.split('@')[-1])
271
277
272 def revsplit(self, rev):
278 def revsplit(self, rev):
273 url, revnum = rev.encode(self.encoding).split('@', 1)
279 url, revnum = rev.encode(self.encoding).split('@', 1)
274 revnum = int(revnum)
280 revnum = int(revnum)
275 parts = url.split('/', 1)
281 parts = url.split('/', 1)
276 uuid = parts.pop(0)[4:]
282 uuid = parts.pop(0)[4:]
277 mod = ''
283 mod = ''
278 if parts:
284 if parts:
279 mod = '/' + parts[0]
285 mod = '/' + parts[0]
280 return uuid, mod, revnum
286 return uuid, mod, revnum
281
287
282 def latest(self, path, stop=0):
288 def latest(self, path, stop=0):
283 'find the latest revision affecting path, up to stop'
289 'find the latest revision affecting path, up to stop'
284 if not stop:
290 if not stop:
285 stop = svn.ra.get_latest_revnum(self.ra)
291 stop = svn.ra.get_latest_revnum(self.ra)
286 try:
292 try:
287 self.reparent('')
293 self.reparent('')
288 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
294 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
289 self.reparent(self.module)
295 self.reparent(self.module)
290 except SubversionException:
296 except SubversionException:
291 dirent = None
297 dirent = None
292 if not dirent:
298 if not dirent:
293 print self.base, path
299 print self.base, path
294 raise util.Abort('%s not found up to revision %d' % (path, stop))
300 raise util.Abort('%s not found up to revision %d' % (path, stop))
295
301
296 return dirent.created_rev
302 return dirent.created_rev
297
303
298 def get_blacklist(self):
304 def get_blacklist(self):
299 """Avoid certain revision numbers.
305 """Avoid certain revision numbers.
300 It is not uncommon for two nearby revisions to cancel each other
306 It is not uncommon for two nearby revisions to cancel each other
301 out, e.g. 'I copied trunk into a subdirectory of itself instead
307 out, e.g. 'I copied trunk into a subdirectory of itself instead
302 of making a branch'. The converted repository is significantly
308 of making a branch'. The converted repository is significantly
303 smaller if we ignore such revisions."""
309 smaller if we ignore such revisions."""
304 self.blacklist = set()
310 self.blacklist = set()
305 blacklist = self.blacklist
311 blacklist = self.blacklist
306 for line in file("blacklist.txt", "r"):
312 for line in file("blacklist.txt", "r"):
307 if not line.startswith("#"):
313 if not line.startswith("#"):
308 try:
314 try:
309 svn_rev = int(line.strip())
315 svn_rev = int(line.strip())
310 blacklist.add(svn_rev)
316 blacklist.add(svn_rev)
311 except ValueError, e:
317 except ValueError, e:
312 pass # not an integer or a comment
318 pass # not an integer or a comment
313
319
314 def is_blacklisted(self, svn_rev):
320 def is_blacklisted(self, svn_rev):
315 return svn_rev in self.blacklist
321 return svn_rev in self.blacklist
316
322
317 def reparent(self, module):
323 def reparent(self, module):
318 svn_url = self.base + module
324 svn_url = self.base + module
319 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
325 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
320 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
326 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
321
327
322 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
328 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
323 def get_entry_from_path(path, module=self.module):
329 def get_entry_from_path(path, module=self.module):
324 # Given the repository url of this wc, say
330 # Given the repository url of this wc, say
325 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
331 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
326 # extract the "entry" portion (a relative path) from what
332 # extract the "entry" portion (a relative path) from what
327 # svn log --xml says, ie
333 # svn log --xml says, ie
328 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
334 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
329 # that is to say "tests/PloneTestCase.py"
335 # that is to say "tests/PloneTestCase.py"
330
336
331 if path.startswith(module):
337 if path.startswith(module):
332 relative = path[len(module):]
338 relative = path[len(module):]
333 if relative.startswith('/'):
339 if relative.startswith('/'):
334 return relative[1:]
340 return relative[1:]
335 else:
341 else:
336 return relative
342 return relative
337
343
338 # The path is outside our tracked tree...
344 # The path is outside our tracked tree...
339 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
345 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
340 return None
346 return None
341
347
342 self.child_cset = None
348 self.child_cset = None
343 def parselogentry(orig_paths, revnum, author, date, message):
349 def parselogentry(orig_paths, revnum, author, date, message):
344 self.ui.debug("parsing revision %d (%d changes)\n" %
350 self.ui.debug("parsing revision %d (%d changes)\n" %
345 (revnum, len(orig_paths)))
351 (revnum, len(orig_paths)))
346
352
347 if revnum in self.modulemap:
353 if revnum in self.modulemap:
348 new_module = self.modulemap[revnum]
354 new_module = self.modulemap[revnum]
349 if new_module != self.module:
355 if new_module != self.module:
350 self.module = new_module
356 self.module = new_module
351 self.reparent(self.module)
357 self.reparent(self.module)
352
358
353 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
359 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
354 copies = {}
360 copies = {}
355 entries = []
361 entries = []
356 rev = self.revid(revnum)
362 rev = self.revid(revnum)
357 parents = []
363 parents = []
358
364
359 # branch log might return entries for a parent we already have
365 # branch log might return entries for a parent we already have
360 if (rev in self.commits or
366 if (rev in self.commits or
361 (revnum < self.lastrevs.get(self.module, 0))):
367 (revnum < self.lastrevs.get(self.module, 0))):
362 return
368 return
363
369
364 try:
370 try:
365 branch = self.module.split("/")[-1]
371 branch = self.module.split("/")[-1]
366 if branch == 'trunk':
372 if branch == 'trunk':
367 branch = ''
373 branch = ''
368 except IndexError:
374 except IndexError:
369 branch = None
375 branch = None
370
376
371 orig_paths = orig_paths.items()
377 orig_paths = orig_paths.items()
372 orig_paths.sort()
378 orig_paths.sort()
373 for path, ent in orig_paths:
379 for path, ent in orig_paths:
374 # self.ui.write("path %s\n" % path)
380 # self.ui.write("path %s\n" % path)
375 if path == self.module: # Follow branching back in history
381 if path == self.module: # Follow branching back in history
376 if ent:
382 if ent:
377 if ent.copyfrom_path:
383 if ent.copyfrom_path:
378 # ent.copyfrom_rev may not be the actual last revision
384 # ent.copyfrom_rev may not be the actual last revision
379 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
385 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
380 self.modulemap[prev] = ent.copyfrom_path
386 self.modulemap[prev] = ent.copyfrom_path
381 parents = [self.revid(prev, ent.copyfrom_path)]
387 parents = [self.revid(prev, ent.copyfrom_path)]
382 self.ui.note('found parent of branch %s at %d: %s\n' % \
388 self.ui.note('found parent of branch %s at %d: %s\n' % \
383 (self.module, prev, ent.copyfrom_path))
389 (self.module, prev, ent.copyfrom_path))
384 else:
390 else:
385 self.ui.debug("No copyfrom path, don't know what to do.\n")
391 self.ui.debug("No copyfrom path, don't know what to do.\n")
386 # Maybe it was added and there is no more history.
392 # Maybe it was added and there is no more history.
387 entrypath = get_entry_from_path(path, module=self.module)
393 entrypath = get_entry_from_path(path, module=self.module)
388 # self.ui.write("entrypath %s\n" % entrypath)
394 # self.ui.write("entrypath %s\n" % entrypath)
389 if entrypath is None:
395 if entrypath is None:
390 # Outside our area of interest
396 # Outside our area of interest
391 self.ui.debug("boring@%s: %s\n" % (revnum, path))
397 self.ui.debug("boring@%s: %s\n" % (revnum, path))
392 continue
398 continue
393 entry = entrypath.decode(self.encoding)
399 entry = entrypath.decode(self.encoding)
394
400
395 kind = svn.ra.check_path(self.ra, entrypath, revnum)
401 kind = svn.ra.check_path(self.ra, entrypath, revnum)
396 if kind == svn.core.svn_node_file:
402 if kind == svn.core.svn_node_file:
397 if ent.copyfrom_path:
403 if ent.copyfrom_path:
398 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
404 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
399 if copyfrom_path:
405 if copyfrom_path:
400 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
406 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
401 # It's probably important for hg that the source
407 # It's probably important for hg that the source
402 # exists in the revision's parent, not just the
408 # exists in the revision's parent, not just the
403 # ent.copyfrom_rev
409 # ent.copyfrom_rev
404 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
410 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
405 if fromkind != 0:
411 if fromkind != 0:
406 copies[self.recode(entry)] = self.recode(copyfrom_path)
412 copies[self.recode(entry)] = self.recode(copyfrom_path)
407 entries.append(self.recode(entry))
413 entries.append(self.recode(entry))
408 elif kind == 0: # gone, but had better be a deleted *file*
414 elif kind == 0: # gone, but had better be a deleted *file*
409 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
415 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
410
416
411 # if a branch is created but entries are removed in the same
417 # if a branch is created but entries are removed in the same
412 # changeset, get the right fromrev
418 # changeset, get the right fromrev
413 if parents:
419 if parents:
414 uuid, old_module, fromrev = self.revsplit(parents[0])
420 uuid, old_module, fromrev = self.revsplit(parents[0])
415 else:
421 else:
416 fromrev = revnum - 1
422 fromrev = revnum - 1
417 # might always need to be revnum - 1 in these 3 lines?
423 # might always need to be revnum - 1 in these 3 lines?
418 old_module = self.modulemap.get(fromrev, self.module)
424 old_module = self.modulemap.get(fromrev, self.module)
419
425
420 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
426 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
421 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
427 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
422
428
423 def lookup_parts(p):
429 def lookup_parts(p):
424 rc = None
430 rc = None
425 parts = p.split("/")
431 parts = p.split("/")
426 for i in range(len(parts)):
432 for i in range(len(parts)):
427 part = "/".join(parts[:i])
433 part = "/".join(parts[:i])
428 info = part, copyfrom.get(part, None)
434 info = part, copyfrom.get(part, None)
429 if info[1] is not None:
435 if info[1] is not None:
430 self.ui.debug("Found parent directory %s\n" % info[1])
436 self.ui.debug("Found parent directory %s\n" % info[1])
431 rc = info
437 rc = info
432 return rc
438 return rc
433
439
434 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
440 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
435
441
436 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
442 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
437
443
438 # need to remove fragment from lookup_parts and replace with copyfrom_path
444 # need to remove fragment from lookup_parts and replace with copyfrom_path
439 if frompath is not None:
445 if frompath is not None:
440 self.ui.debug("munge-o-matic\n")
446 self.ui.debug("munge-o-matic\n")
441 self.ui.debug(entrypath + '\n')
447 self.ui.debug(entrypath + '\n')
442 self.ui.debug(entrypath[len(frompath):] + '\n')
448 self.ui.debug(entrypath[len(frompath):] + '\n')
443 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
449 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
444 fromrev = froment.copyfrom_rev
450 fromrev = froment.copyfrom_rev
445 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
451 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
446
452
447 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
453 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
448 if fromkind == svn.core.svn_node_file: # a deleted file
454 if fromkind == svn.core.svn_node_file: # a deleted file
449 entries.append(self.recode(entry))
455 entries.append(self.recode(entry))
450 elif fromkind == svn.core.svn_node_dir:
456 elif fromkind == svn.core.svn_node_dir:
451 # print "Deleted/moved non-file:", revnum, path, ent
457 # print "Deleted/moved non-file:", revnum, path, ent
452 # children = self._find_children(path, revnum - 1)
458 # children = self._find_children(path, revnum - 1)
453 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
459 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
454 # Sometimes this is tricky. For example: in
460 # Sometimes this is tricky. For example: in
455 # The Subversion Repository revision 6940 a dir
461 # The Subversion Repository revision 6940 a dir
456 # was copied and one of its files was deleted
462 # was copied and one of its files was deleted
457 # from the new location in the same commit. This
463 # from the new location in the same commit. This
458 # code can't deal with that yet.
464 # code can't deal with that yet.
459 if ent.action == 'C':
465 if ent.action == 'C':
460 children = self._find_children(path, fromrev)
466 children = self._find_children(path, fromrev)
461 else:
467 else:
462 oroot = entrypath.strip('/')
468 oroot = entrypath.strip('/')
463 nroot = path.strip('/')
469 nroot = path.strip('/')
464 children = self._find_children(oroot, fromrev)
470 children = self._find_children(oroot, fromrev)
465 children = [s.replace(oroot,nroot) for s in children]
471 children = [s.replace(oroot,nroot) for s in children]
466 # Mark all [files, not directories] as deleted.
472 # Mark all [files, not directories] as deleted.
467 for child in children:
473 for child in children:
468 # Can we move a child directory and its
474 # Can we move a child directory and its
469 # parent in the same commit? (probably can). Could
475 # parent in the same commit? (probably can). Could
470 # cause problems if instead of revnum -1,
476 # cause problems if instead of revnum -1,
471 # we have to look in (copyfrom_path, revnum - 1)
477 # we have to look in (copyfrom_path, revnum - 1)
472 entrypath = get_entry_from_path("/" + child, module=old_module)
478 entrypath = get_entry_from_path("/" + child, module=old_module)
473 if entrypath:
479 if entrypath:
474 entry = self.recode(entrypath.decode(self.encoding))
480 entry = self.recode(entrypath.decode(self.encoding))
475 if entry in copies:
481 if entry in copies:
476 # deleted file within a copy
482 # deleted file within a copy
477 del copies[entry]
483 del copies[entry]
478 else:
484 else:
479 entries.append(entry)
485 entries.append(entry)
480 else:
486 else:
481 self.ui.debug('unknown path in revision %d: %s\n' % \
487 self.ui.debug('unknown path in revision %d: %s\n' % \
482 (revnum, path))
488 (revnum, path))
483 elif kind == svn.core.svn_node_dir:
489 elif kind == svn.core.svn_node_dir:
484 # Should probably synthesize normal file entries
490 # Should probably synthesize normal file entries
485 # and handle as above to clean up copy/rename handling.
491 # and handle as above to clean up copy/rename handling.
486
492
487 # If the directory just had a prop change,
493 # If the directory just had a prop change,
488 # then we shouldn't need to look for its children.
494 # then we shouldn't need to look for its children.
489 # Also this could create duplicate entries. Not sure
495 # Also this could create duplicate entries. Not sure
490 # whether this will matter. Maybe should make entries a set.
496 # whether this will matter. Maybe should make entries a set.
491 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
497 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
492 # This will fail if a directory was copied
498 # This will fail if a directory was copied
493 # from another branch and then some of its files
499 # from another branch and then some of its files
494 # were deleted in the same transaction.
500 # were deleted in the same transaction.
495 children = self._find_children(path, revnum)
501 children = self._find_children(path, revnum)
496 children.sort()
502 children.sort()
497 for child in children:
503 for child in children:
498 # Can we move a child directory and its
504 # Can we move a child directory and its
499 # parent in the same commit? (probably can). Could
505 # parent in the same commit? (probably can). Could
500 # cause problems if instead of revnum -1,
506 # cause problems if instead of revnum -1,
501 # we have to look in (copyfrom_path, revnum - 1)
507 # we have to look in (copyfrom_path, revnum - 1)
502 entrypath = get_entry_from_path("/" + child, module=self.module)
508 entrypath = get_entry_from_path("/" + child, module=self.module)
503 # print child, self.module, entrypath
509 # print child, self.module, entrypath
504 if entrypath:
510 if entrypath:
505 # Need to filter out directories here...
511 # Need to filter out directories here...
506 kind = svn.ra.check_path(self.ra, entrypath, revnum)
512 kind = svn.ra.check_path(self.ra, entrypath, revnum)
507 if kind != svn.core.svn_node_dir:
513 if kind != svn.core.svn_node_dir:
508 entries.append(self.recode(entrypath))
514 entries.append(self.recode(entrypath))
509
515
510 # Copies here (must copy all from source)
516 # Copies here (must copy all from source)
511 # Probably not a real problem for us if
517 # Probably not a real problem for us if
512 # source does not exist
518 # source does not exist
513
519
514 # Can do this with the copy command "hg copy"
520 # Can do this with the copy command "hg copy"
515 # if ent.copyfrom_path:
521 # if ent.copyfrom_path:
516 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
522 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
517 # module=self.module)
523 # module=self.module)
518 # copyto_entry = entrypath
524 # copyto_entry = entrypath
519 #
525 #
520 # print "copy directory", copyfrom_entry, 'to', copyto_entry
526 # print "copy directory", copyfrom_entry, 'to', copyto_entry
521 #
527 #
522 # copies.append((copyfrom_entry, copyto_entry))
528 # copies.append((copyfrom_entry, copyto_entry))
523
529
524 if ent.copyfrom_path:
530 if ent.copyfrom_path:
525 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
531 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
526 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
532 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
527 if copyfrom_entry:
533 if copyfrom_entry:
528 copyfrom[path] = ent
534 copyfrom[path] = ent
529 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
535 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
530
536
531 # Good, /probably/ a regular copy. Really should check
537 # Good, /probably/ a regular copy. Really should check
532 # to see whether the parent revision actually contains
538 # to see whether the parent revision actually contains
533 # the directory in question.
539 # the directory in question.
534 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
540 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
535 children.sort()
541 children.sort()
536 for child in children:
542 for child in children:
537 entrypath = get_entry_from_path("/" + child, module=self.module)
543 entrypath = get_entry_from_path("/" + child, module=self.module)
538 if entrypath:
544 if entrypath:
539 entry = entrypath.decode(self.encoding)
545 entry = entrypath.decode(self.encoding)
540 # print "COPY COPY From", copyfrom_entry, entry
546 # print "COPY COPY From", copyfrom_entry, entry
541 copyto_path = path + entry[len(copyfrom_entry):]
547 copyto_path = path + entry[len(copyfrom_entry):]
542 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
548 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
543 # print "COPY", entry, "COPY To", copyto_entry
549 # print "COPY", entry, "COPY To", copyto_entry
544 copies[self.recode(copyto_entry)] = self.recode(entry)
550 copies[self.recode(copyto_entry)] = self.recode(entry)
545 # copy from quux splort/quuxfile
551 # copy from quux splort/quuxfile
546
552
547 self.modulemap[revnum] = self.module # track backwards in time
553 self.modulemap[revnum] = self.module # track backwards in time
548 # a list of (filename, id) where id lets us retrieve the file.
554 # a list of (filename, id) where id lets us retrieve the file.
549 # eg in git, id is the object hash. for svn it'll be the
555 # eg in git, id is the object hash. for svn it'll be the
550 self.files[rev] = zip(entries, [rev] * len(entries))
556 self.files[rev] = zip(entries, [rev] * len(entries))
551 if not entries:
557 if not entries:
552 return
558 return
553
559
554 # Example SVN datetime. Includes microseconds.
560 # Example SVN datetime. Includes microseconds.
555 # ISO-8601 conformant
561 # ISO-8601 conformant
556 # '2007-01-04T17:35:00.902377Z'
562 # '2007-01-04T17:35:00.902377Z'
557 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
563 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
558
564
559 log = message and self.recode(message)
565 log = message and self.recode(message)
560 author = author and self.recode(author) or ''
566 author = author and self.recode(author) or ''
561
567
562 cset = commit(author=author,
568 cset = commit(author=author,
563 date=util.datestr(date),
569 date=util.datestr(date),
564 desc=log,
570 desc=log,
565 parents=parents,
571 parents=parents,
566 copies=copies,
572 copies=copies,
567 branch=branch,
573 branch=branch,
568 rev=rev.encode('utf-8'))
574 rev=rev.encode('utf-8'))
569
575
570 self.commits[rev] = cset
576 self.commits[rev] = cset
571 if self.child_cset and not self.child_cset.parents:
577 if self.child_cset and not self.child_cset.parents:
572 self.child_cset.parents = [rev]
578 self.child_cset.parents = [rev]
573 self.child_cset = cset
579 self.child_cset = cset
574
580
575 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
581 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
576 (self.module, from_revnum, to_revnum))
582 (self.module, from_revnum, to_revnum))
577
583
578 try:
584 try:
579 discover_changed_paths = True
585 discover_changed_paths = True
580 strict_node_history = False
586 strict_node_history = False
581 for entry in self.get_log([self.module], from_revnum, to_revnum):
587 for entry in self.get_log([self.module], from_revnum, to_revnum):
582 orig_paths, revnum, author, date, message = entry
588 orig_paths, revnum, author, date, message = entry
583 if self.is_blacklisted(revnum):
589 if self.is_blacklisted(revnum):
584 self.ui.note('skipping blacklisted revision %d\n' % revnum)
590 self.ui.note('skipping blacklisted revision %d\n' % revnum)
585 continue
591 continue
586 if orig_paths is None:
592 if orig_paths is None:
587 self.ui.debug('revision %d has no entries\n' % revnum)
593 self.ui.debug('revision %d has no entries\n' % revnum)
588 continue
594 continue
589 parselogentry(orig_paths, revnum, author, date, message)
595 parselogentry(orig_paths, revnum, author, date, message)
590 except SubversionException, (_, num):
596 except SubversionException, (_, num):
591 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
597 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
592 raise NoSuchRevision(branch=self,
598 raise NoSuchRevision(branch=self,
593 revision="Revision number %d" % to_revnum)
599 revision="Revision number %d" % to_revnum)
594 raise
600 raise
595
601
596 def _getfile(self, file, rev):
602 def _getfile(self, file, rev):
597 io = StringIO()
603 io = StringIO()
598 # TODO: ra.get_file transmits the whole file instead of diffs.
604 # TODO: ra.get_file transmits the whole file instead of diffs.
599 mode = ''
605 mode = ''
600 try:
606 try:
601 revnum = self.revnum(rev)
607 revnum = self.revnum(rev)
602 if self.module != self.modulemap[revnum]:
608 if self.module != self.modulemap[revnum]:
603 self.module = self.modulemap[revnum]
609 self.module = self.modulemap[revnum]
604 self.reparent(self.module)
610 self.reparent(self.module)
605 info = svn.ra.get_file(self.ra, file, revnum, io)
611 info = svn.ra.get_file(self.ra, file, revnum, io)
606 if isinstance(info, list):
612 if isinstance(info, list):
607 info = info[-1]
613 info = info[-1]
608 mode = ("svn:executable" in info) and 'x' or ''
614 mode = ("svn:executable" in info) and 'x' or ''
609 mode = ("svn:special" in info) and 'l' or mode
615 mode = ("svn:special" in info) and 'l' or mode
610 except SubversionException, e:
616 except SubversionException, e:
611 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
617 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
612 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
618 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
613 if e.apr_err in notfound: # File not found
619 if e.apr_err in notfound: # File not found
614 raise IOError()
620 raise IOError()
615 raise
621 raise
616 data = io.getvalue()
622 data = io.getvalue()
617 if mode == 'l':
623 if mode == 'l':
618 link_prefix = "link "
624 link_prefix = "link "
619 if data.startswith(link_prefix):
625 if data.startswith(link_prefix):
620 data = data[len(link_prefix):]
626 data = data[len(link_prefix):]
621 return data, mode
627 return data, mode
622
628
623 def _find_children(self, path, revnum):
629 def _find_children(self, path, revnum):
624 path = path.strip("/")
630 path = path.strip("/")
625
631
626 def _find_children_fallback(path, revnum):
632 def _find_children_fallback(path, revnum):
627 # SWIG python bindings for getdir are broken up to at least 1.4.3
633 # SWIG python bindings for getdir are broken up to at least 1.4.3
628 pool = Pool()
634 pool = Pool()
629 optrev = svn.core.svn_opt_revision_t()
635 optrev = svn.core.svn_opt_revision_t()
630 optrev.kind = svn.core.svn_opt_revision_number
636 optrev.kind = svn.core.svn_opt_revision_number
631 optrev.value.number = revnum
637 optrev.value.number = revnum
632 rpath = '/'.join([self.base, path]).strip('/')
638 rpath = '/'.join([self.base, path]).strip('/')
633 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
639 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
634
640
635 if hasattr(self, '_find_children_fallback'):
641 if hasattr(self, '_find_children_fallback'):
636 return _find_children_fallback(path, revnum)
642 return _find_children_fallback(path, revnum)
637
643
638 self.reparent("/" + path)
644 self.reparent("/" + path)
639 pool = Pool()
645 pool = Pool()
640
646
641 children = []
647 children = []
642 def find_children_inner(children, path, revnum = revnum):
648 def find_children_inner(children, path, revnum = revnum):
643 if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
649 if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
644 fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
650 fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
645 getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
651 getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
646 else:
652 else:
647 getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
653 getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
648 if type(getdir) == dict:
654 if type(getdir) == dict:
649 # python binding for getdir is broken up to at least 1.4.3
655 # python binding for getdir is broken up to at least 1.4.3
650 raise CompatibilityException()
656 raise CompatibilityException()
651 dirents = getdir[0]
657 dirents = getdir[0]
652 if type(dirents) == int:
658 if type(dirents) == int:
653 # got here once due to infinite recursion bug
659 # got here once due to infinite recursion bug
654 return
660 return
655 c = dirents.keys()
661 c = dirents.keys()
656 c.sort()
662 c.sort()
657 for child in c:
663 for child in c:
658 dirent = dirents[child]
664 dirent = dirents[child]
659 if dirent.kind == svn.core.svn_node_dir:
665 if dirent.kind == svn.core.svn_node_dir:
660 find_children_inner(children, (path + "/" + child).strip("/"))
666 find_children_inner(children, (path + "/" + child).strip("/"))
661 else:
667 else:
662 children.append((path + "/" + child).strip("/"))
668 children.append((path + "/" + child).strip("/"))
663
669
664 try:
670 try:
665 find_children_inner(children, "")
671 find_children_inner(children, "")
666 except CompatibilityException:
672 except CompatibilityException:
667 self._find_children_fallback = True
673 self._find_children_fallback = True
668 self.reparent(self.module)
674 self.reparent(self.module)
669 return _find_children_fallback(path, revnum)
675 return _find_children_fallback(path, revnum)
670
676
671 self.reparent(self.module)
677 self.reparent(self.module)
672 return [path + "/" + c for c in children]
678 return [path + "/" + c for c in children]
General Comments 0
You need to be logged in to leave comments. Login now