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