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