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