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