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