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