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