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