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