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