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