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