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