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