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