##// 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 rev = self.rev(revnum)
222 return
223 try:
223
224 branch = self.module.split("/")[-1]
224 rev = self.rev(revnum)
225 if branch == 'trunk':
225 try:
226 branch = ''
226 branch = self.module.split("/")[-1]
227 except IndexError:
227 if branch == 'trunk':
228 branch = None
228 branch = ''
229 except IndexError:
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]
234 if ent:
235 if ent.copyfrom_path:
236 self.modulemap[ent.copyfrom_rev] = ent.copyfrom_path
237 else:
238 self.ui.debug("No copyfrom path, don't know what to do.\n")
239 # Maybe it was added and there is no more history.
240 entrypath = get_entry_from_path(path, module=self.module)
241 # self.ui.write("entrypath %s\n" % entrypath)
242 if entrypath is None:
243 # Outside our area of interest
244 self.ui.debug("boring@%s: %s\n" % (revnum, path))
245 continue
246 entry = entrypath.decode(self.encoding)
247 ent = orig_paths[path]
235 ent = orig_paths[path]
248 if not entrypath:
236 if ent:
249 # TODO: branch creation event
237 if ent.copyfrom_path:
250 pass
238 self.modulemap[ent.copyfrom_rev] = ent.copyfrom_path
239 else:
240 self.ui.debug("No copyfrom path, don't know what to do.\n")
241 # Maybe it was added and there is no more history.
242 entrypath = get_entry_from_path(path, module=self.module)
243 # self.ui.write("entrypath %s\n" % entrypath)
244 if entrypath is None:
245 # Outside our area of interest
246 self.ui.debug("boring@%s: %s\n" % (revnum, path))
247 continue
248 entry = entrypath.decode(self.encoding)
249 ent = orig_paths[path]
250 if not entrypath:
251 # TODO: branch creation event
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
268 fromrev = revnum - 1
269 # might always need to be revnum - 1 in these 3 lines?
270 old_module = self.modulemap.get(fromrev, self.module)
271 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
272 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
273
269
274 def lookup_parts(p):
270 fromrev = revnum - 1
275 rc = None
271 # might always need to be revnum - 1 in these 3 lines?
276 parts = p.split("/")
272 old_module = self.modulemap.get(fromrev, self.module)
277 for i in range(len(parts)):
273 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
278 part = "/".join(parts[:i])
274 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
279 info = part, copyfrom.get(part, None)
280 if info[1] is not None:
281 self.ui.debug("Found parent directory %s\n" % info)
282 rc = info
283 return rc
284
275
285 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
276 def lookup_parts(p):
277 rc = None
278 parts = p.split("/")
279 for i in range(len(parts)):
280 part = "/".join(parts[:i])
281 info = part, copyfrom.get(part, None)
282 if info[1] is not None:
283 self.ui.debug("Found parent directory %s\n" % info)
284 rc = info
285 return rc
286
287 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
288
289 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
286
290
287 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
291 # need to remove fragment from lookup_parts and replace with copyfrom_path
292 if frompath is not None:
293 self.ui.debug("munge-o-matic\n")
294 self.ui.debug(entrypath + '\n')
295 self.ui.debug(entrypath[len(frompath):] + '\n')
296 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
297 fromrev = froment.copyfrom_rev
298 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
288
299
289 # need to remove fragment from lookup_parts and replace with copyfrom_path
300 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
290 if frompath is not None:
301 if fromkind == svn.core.svn_node_file: # a deleted file
291 self.ui.debug("munge-o-matic\n")
302 entries.append(self.recode(entry))
292 self.ui.debug(entrypath + '\n')
303 else:
293 self.ui.debug(entrypath[len(frompath):] + '\n')
304 # print "Deleted/moved non-file:", revnum, path, ent
294 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
305 # children = self._find_children(path, revnum - 1)
295 fromrev = froment.copyfrom_rev
306 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
296 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
307 # Sometimes this is tricky. For example: in
297
308 # The Subversion Repository revision 6940 a dir
298 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
309 # was copied and one of its files was deleted
299 if fromkind == svn.core.svn_node_file: # a deleted file
310 # from the new location in the same commit. This
300 entries.append(self.recode(entry))
311 # code can't deal with that yet.
312 if ent.action == 'C':
313 children = self._find_children(path, fromrev)
301 else:
314 else:
302 # print "Deleted/moved non-file:", revnum, path, ent
315 oroot = entrypath.strip('/')
303 # children = self._find_children(path, revnum - 1)
316 nroot = path.strip('/')
304 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
317 children = self._find_children(oroot, fromrev)
305 # Sometimes this is tricky. For example: in
318 children = [s.replace(oroot,nroot) for s in children]
306 # The Subversion Repository revision 6940 a dir
319 # Mark all [files, not directories] as deleted.
307 # was copied and one of its files was deleted
308 # from the new location in the same commit. This
309 # code can't deal with that yet.
310 if ent.action == 'C':
311 children = self._find_children(path, fromrev)
312 else:
313 oroot = entrypath.strip('/')
314 nroot = path.strip('/')
315 children = self._find_children(oroot, fromrev)
316 children = [s.replace(oroot,nroot) for s in children]
317 # Mark all [files, not directories] as deleted.
318 for child in children:
319 # Can we move a child directory and its
320 # parent in the same commit? (probably can). Could
321 # cause problems if instead of revnum -1,
322 # we have to look in (copyfrom_path, revnum - 1)
323 entrypath = get_entry_from_path("/" + child, module=old_module)
324 if entrypath:
325 entry = self.recode(entrypath.decode(self.encoding))
326 if entry in copies:
327 # deleted file within a copy
328 del copies[entry]
329 else:
330 entries.append(entry)
331 elif kind == svn.core.svn_node_dir:
332 # Should probably synthesize normal file entries
333 # and handle as above to clean up copy/rename handling.
334
335 # If the directory just had a prop change,
336 # then we shouldn't need to look for its children.
337 # Also this could create duplicate entries. Not sure
338 # whether this will matter. Maybe should make entries a set.
339 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
340 # This will fail if a directory was copied
341 # from another branch and then some of its files
342 # were deleted in the same transaction.
343 children = self._find_children(path, revnum)
344 children.sort()
345 for child in children:
320 for child in children:
346 # Can we move a child directory and its
321 # Can we move a child directory and its
347 # parent in the same commit? (probably can). Could
322 # parent in the same commit? (probably can). Could
348 # cause problems if instead of revnum -1,
323 # cause problems if instead of revnum -1,
349 # we have to look in (copyfrom_path, revnum - 1)
324 # we have to look in (copyfrom_path, revnum - 1)
350 entrypath = get_entry_from_path("/" + child, module=self.module)
325 entrypath = get_entry_from_path("/" + child, module=old_module)
351 # print child, self.module, entrypath
352 if entrypath:
326 if entrypath:
353 # Need to filter out directories here...
327 entry = self.recode(entrypath.decode(self.encoding))
354 kind = svn.ra.check_path(self.ra, entrypath, revnum)
328 if entry in copies:
355 if kind != svn.core.svn_node_dir:
329 # deleted file within a copy
356 entries.append(self.recode(entrypath))
330 del copies[entry]
357
331 else:
358 # Copies here (must copy all from source)
332 entries.append(entry)
359 # Probably not a real problem for us if
333 elif kind == svn.core.svn_node_dir:
360 # source does not exist
334 # Should probably synthesize normal file entries
335 # and handle as above to clean up copy/rename handling.
361
336
362 # Can do this with the copy command "hg copy"
337 # If the directory just had a prop change,
363 # if ent.copyfrom_path:
338 # then we shouldn't need to look for its children.
364 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
339 # Also this could create duplicate entries. Not sure
365 # module=self.module)
340 # whether this will matter. Maybe should make entries a set.
366 # copyto_entry = entrypath
341 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
367 #
342 # This will fail if a directory was copied
368 # print "copy directory", copyfrom_entry, 'to', copyto_entry
343 # from another branch and then some of its files
369 #
344 # were deleted in the same transaction.
370 # copies.append((copyfrom_entry, copyto_entry))
345 children = self._find_children(path, revnum)
371
346 children.sort()
372 if ent.copyfrom_path:
347 for child in children:
373 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
348 # Can we move a child directory and its
374 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
349 # parent in the same commit? (probably can). Could
375 if copyfrom_entry:
350 # cause problems if instead of revnum -1,
376 copyfrom[path] = ent
351 # we have to look in (copyfrom_path, revnum - 1)
377 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
352 entrypath = get_entry_from_path("/" + child, module=self.module)
353 # print child, self.module, entrypath
354 if entrypath:
355 # Need to filter out directories here...
356 kind = svn.ra.check_path(self.ra, entrypath, revnum)
357 if kind != svn.core.svn_node_dir:
358 entries.append(self.recode(entrypath))
359
360 # Copies here (must copy all from source)
361 # Probably not a real problem for us if
362 # source does not exist
363
364 # Can do this with the copy command "hg copy"
365 # if ent.copyfrom_path:
366 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
367 # module=self.module)
368 # copyto_entry = entrypath
369 #
370 # print "copy directory", copyfrom_entry, 'to', copyto_entry
371 #
372 # copies.append((copyfrom_entry, copyto_entry))
378
373
379 # Good, /probably/ a regular copy. Really should check
374 if ent.copyfrom_path:
380 # to see whether the parent revision actually contains
375 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
381 # the directory in question.
376 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
382 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
377 if copyfrom_entry:
383 children.sort()
378 copyfrom[path] = ent
384 for child in children:
379 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
385 entrypath = get_entry_from_path("/" + child, module=self.module)
380
386 if entrypath:
381 # Good, /probably/ a regular copy. Really should check
387 entry = entrypath.decode(self.encoding)
382 # to see whether the parent revision actually contains
388 # print "COPY COPY From", copyfrom_entry, entry
383 # the directory in question.
389 copyto_path = path + entry[len(copyfrom_entry):]
384 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
390 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
385 children.sort()
391 # print "COPY", entry, "COPY To", copyto_entry
386 for child in children:
392 copies[self.recode(copyto_entry)] = self.recode(entry)
387 entrypath = get_entry_from_path("/" + child, module=self.module)
393 # copy from quux splort/quuxfile
388 if entrypath:
394
389 entry = entrypath.decode(self.encoding)
395 self.modulemap[revnum] = self.module # track backwards in time
390 # print "COPY COPY From", copyfrom_entry, entry
396 # a list of (filename, id) where id lets us retrieve the file.
391 copyto_path = path + entry[len(copyfrom_entry):]
397 # eg in git, id is the object hash. for svn it'll be the
392 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
398 self.files[rev] = zip(entries, [rev] * len(entries))
393 # print "COPY", entry, "COPY To", copyto_entry
399 if not entries:
394 copies[self.recode(copyto_entry)] = self.recode(entry)
400 return
395 # copy from quux splort/quuxfile
401
396
402 # Example SVN datetime. Includes microseconds.
397 self.modulemap[revnum] = self.module # track backwards in time
403 # ISO-8601 conformant
398 # a list of (filename, id) where id lets us retrieve the file.
404 # '2007-01-04T17:35:00.902377Z'
399 # eg in git, id is the object hash. for svn it'll be the
405 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
400 self.files[rev] = zip(entries, [rev] * len(entries))
401 if not entries:
402 return
406
403
407 log = message and self.recode(message)
404 # Example SVN datetime. Includes microseconds.
408 author = author and self.recode(author) or ''
405 # ISO-8601 conformant
406 # '2007-01-04T17:35:00.902377Z'
407 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
409
408
410 cset = commit(author=author,
409 log = message and self.recode(message)
411 date=util.datestr(date),
410 author = author and self.recode(author) or ''
412 desc=log,
413 parents=[],
414 copies=copies,
415 branch=branch)
416
411
417 if self.child_cset and self.child_rev != rev:
412 cset = commit(author=author,
418 self.child_cset.parents = [rev]
413 date=util.datestr(date),
419 self.commits[self.child_rev] = self.child_cset
414 desc=log,
420 self.child_cset = cset
415 parents=[],
421 self.child_rev = rev
416 copies=copies,
417 branch=branch)
418
419 if self.child_cset and self.child_rev != rev:
420 self.child_cset.parents = [rev]
421 self.commits[self.child_rev] = self.child_cset
422 self.child_cset = cset
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