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