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