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