##// END OF EJS Templates
convert: svn_sink: workaround of command line size limitation on win32....
Shun-ichi GOTO -
r5790:f85c0034 default
parent child Browse files
Show More
@@ -1,924 +1,949 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 # convert.svn.tags
11 # convert.svn.tags
12 # Relative path to tree of tags (default: "tags")
12 # Relative path to tree of tags (default: "tags")
13 #
13 #
14 # Set these in a hgrc, or on the command line as follows:
14 # Set these in a hgrc, or on the command line as follows:
15 #
15 #
16 # hg convert --config convert.svn.trunk=wackoname [...]
16 # hg convert --config convert.svn.trunk=wackoname [...]
17
17
18 import locale
18 import locale
19 import os
19 import os
20 import re
20 import re
21 import sys
21 import sys
22 import cPickle as pickle
22 import cPickle as pickle
23 import tempfile
23 import tempfile
24
24
25 from mercurial import strutil, util
25 from mercurial import strutil, util
26 from mercurial.i18n import _
26 from mercurial.i18n import _
27
27
28 # Subversion stuff. Works best with very recent Python SVN bindings
28 # Subversion stuff. Works best with very recent Python SVN bindings
29 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
29 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
30 # these bindings.
30 # these bindings.
31
31
32 from cStringIO import StringIO
32 from cStringIO import StringIO
33
33
34 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
34 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
35 from common import commandline, converter_sink, mapfile
35 from common import commandline, converter_sink, mapfile
36
36
37 try:
37 try:
38 from svn.core import SubversionException, Pool
38 from svn.core import SubversionException, Pool
39 import svn
39 import svn
40 import svn.client
40 import svn.client
41 import svn.core
41 import svn.core
42 import svn.ra
42 import svn.ra
43 import svn.delta
43 import svn.delta
44 import transport
44 import transport
45 except ImportError:
45 except ImportError:
46 pass
46 pass
47
47
48 def geturl(path):
48 def geturl(path):
49 try:
49 try:
50 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
50 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
51 except SubversionException:
51 except SubversionException:
52 pass
52 pass
53 if os.path.isdir(path):
53 if os.path.isdir(path):
54 return 'file://%s' % os.path.normpath(os.path.abspath(path))
54 return 'file://%s' % os.path.normpath(os.path.abspath(path))
55 return path
55 return path
56
56
57 def optrev(number):
57 def optrev(number):
58 optrev = svn.core.svn_opt_revision_t()
58 optrev = svn.core.svn_opt_revision_t()
59 optrev.kind = svn.core.svn_opt_revision_number
59 optrev.kind = svn.core.svn_opt_revision_number
60 optrev.value.number = number
60 optrev.value.number = number
61 return optrev
61 return optrev
62
62
63 class changedpath(object):
63 class changedpath(object):
64 def __init__(self, p):
64 def __init__(self, p):
65 self.copyfrom_path = p.copyfrom_path
65 self.copyfrom_path = p.copyfrom_path
66 self.copyfrom_rev = p.copyfrom_rev
66 self.copyfrom_rev = p.copyfrom_rev
67 self.action = p.action
67 self.action = p.action
68
68
69 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
69 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
70 strict_node_history=False):
70 strict_node_history=False):
71 protocol = -1
71 protocol = -1
72 def receiver(orig_paths, revnum, author, date, message, pool):
72 def receiver(orig_paths, revnum, author, date, message, pool):
73 if orig_paths is not None:
73 if orig_paths is not None:
74 for k, v in orig_paths.iteritems():
74 for k, v in orig_paths.iteritems():
75 orig_paths[k] = changedpath(v)
75 orig_paths[k] = changedpath(v)
76 pickle.dump((orig_paths, revnum, author, date, message),
76 pickle.dump((orig_paths, revnum, author, date, message),
77 fp, protocol)
77 fp, protocol)
78
78
79 try:
79 try:
80 # Use an ra of our own so that our parent can consume
80 # Use an ra of our own so that our parent can consume
81 # our results without confusing the server.
81 # our results without confusing the server.
82 t = transport.SvnRaTransport(url=url)
82 t = transport.SvnRaTransport(url=url)
83 svn.ra.get_log(t.ra, paths, start, end, limit,
83 svn.ra.get_log(t.ra, paths, start, end, limit,
84 discover_changed_paths,
84 discover_changed_paths,
85 strict_node_history,
85 strict_node_history,
86 receiver)
86 receiver)
87 except SubversionException, (inst, num):
87 except SubversionException, (inst, num):
88 pickle.dump(num, fp, protocol)
88 pickle.dump(num, fp, protocol)
89 else:
89 else:
90 pickle.dump(None, fp, protocol)
90 pickle.dump(None, fp, protocol)
91 fp.close()
91 fp.close()
92
92
93 def debugsvnlog(ui, **opts):
93 def debugsvnlog(ui, **opts):
94 """Fetch SVN log in a subprocess and channel them back to parent to
94 """Fetch SVN log in a subprocess and channel them back to parent to
95 avoid memory collection issues.
95 avoid memory collection issues.
96 """
96 """
97 util.set_binary(sys.stdin)
97 util.set_binary(sys.stdin)
98 util.set_binary(sys.stdout)
98 util.set_binary(sys.stdout)
99 args = decodeargs(sys.stdin.read())
99 args = decodeargs(sys.stdin.read())
100 get_log_child(sys.stdout, *args)
100 get_log_child(sys.stdout, *args)
101
101
102 # SVN conversion code stolen from bzr-svn and tailor
102 # SVN conversion code stolen from bzr-svn and tailor
103 class svn_source(converter_source):
103 class svn_source(converter_source):
104 def __init__(self, ui, url, rev=None):
104 def __init__(self, ui, url, rev=None):
105 super(svn_source, self).__init__(ui, url, rev=rev)
105 super(svn_source, self).__init__(ui, url, rev=rev)
106
106
107 try:
107 try:
108 SubversionException
108 SubversionException
109 except NameError:
109 except NameError:
110 raise NoRepo('Subversion python bindings could not be loaded')
110 raise NoRepo('Subversion python bindings could not be loaded')
111
111
112 self.encoding = locale.getpreferredencoding()
112 self.encoding = locale.getpreferredencoding()
113 self.lastrevs = {}
113 self.lastrevs = {}
114
114
115 latest = None
115 latest = None
116 try:
116 try:
117 # Support file://path@rev syntax. Useful e.g. to convert
117 # Support file://path@rev syntax. Useful e.g. to convert
118 # deleted branches.
118 # deleted branches.
119 at = url.rfind('@')
119 at = url.rfind('@')
120 if at >= 0:
120 if at >= 0:
121 latest = int(url[at+1:])
121 latest = int(url[at+1:])
122 url = url[:at]
122 url = url[:at]
123 except ValueError, e:
123 except ValueError, e:
124 pass
124 pass
125 self.url = geturl(url)
125 self.url = geturl(url)
126 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
126 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
127 try:
127 try:
128 self.transport = transport.SvnRaTransport(url=self.url)
128 self.transport = transport.SvnRaTransport(url=self.url)
129 self.ra = self.transport.ra
129 self.ra = self.transport.ra
130 self.ctx = self.transport.client
130 self.ctx = self.transport.client
131 self.base = svn.ra.get_repos_root(self.ra)
131 self.base = svn.ra.get_repos_root(self.ra)
132 self.module = self.url[len(self.base):]
132 self.module = self.url[len(self.base):]
133 self.modulemap = {} # revision, module
133 self.modulemap = {} # revision, module
134 self.commits = {}
134 self.commits = {}
135 self.paths = {}
135 self.paths = {}
136 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
136 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
137 except SubversionException, e:
137 except SubversionException, e:
138 ui.print_exc()
138 ui.print_exc()
139 raise NoRepo("%s does not look like a Subversion repo" % self.url)
139 raise NoRepo("%s does not look like a Subversion repo" % self.url)
140
140
141 if rev:
141 if rev:
142 try:
142 try:
143 latest = int(rev)
143 latest = int(rev)
144 except ValueError:
144 except ValueError:
145 raise util.Abort('svn: revision %s is not an integer' % rev)
145 raise util.Abort('svn: revision %s is not an integer' % rev)
146
146
147 try:
147 try:
148 self.get_blacklist()
148 self.get_blacklist()
149 except IOError, e:
149 except IOError, e:
150 pass
150 pass
151
151
152 self.last_changed = self.latest(self.module, latest)
152 self.last_changed = self.latest(self.module, latest)
153
153
154 self.head = self.revid(self.last_changed)
154 self.head = self.revid(self.last_changed)
155 self._changescache = None
155 self._changescache = None
156
156
157 if os.path.exists(os.path.join(url, '.svn/entries')):
157 if os.path.exists(os.path.join(url, '.svn/entries')):
158 self.wc = url
158 self.wc = url
159 else:
159 else:
160 self.wc = None
160 self.wc = None
161 self.convertfp = None
161 self.convertfp = None
162
162
163 def setrevmap(self, revmap):
163 def setrevmap(self, revmap):
164 lastrevs = {}
164 lastrevs = {}
165 for revid in revmap.iterkeys():
165 for revid in revmap.iterkeys():
166 uuid, module, revnum = self.revsplit(revid)
166 uuid, module, revnum = self.revsplit(revid)
167 lastrevnum = lastrevs.setdefault(module, revnum)
167 lastrevnum = lastrevs.setdefault(module, revnum)
168 if revnum > lastrevnum:
168 if revnum > lastrevnum:
169 lastrevs[module] = revnum
169 lastrevs[module] = revnum
170 self.lastrevs = lastrevs
170 self.lastrevs = lastrevs
171
171
172 def exists(self, path, optrev):
172 def exists(self, path, optrev):
173 try:
173 try:
174 svn.client.ls(self.url.rstrip('/') + '/' + path,
174 svn.client.ls(self.url.rstrip('/') + '/' + path,
175 optrev, False, self.ctx)
175 optrev, False, self.ctx)
176 return True
176 return True
177 except SubversionException, err:
177 except SubversionException, err:
178 return False
178 return False
179
179
180 def getheads(self):
180 def getheads(self):
181 # detect standard /branches, /tags, /trunk layout
181 # detect standard /branches, /tags, /trunk layout
182 rev = optrev(self.last_changed)
182 rev = optrev(self.last_changed)
183 rpath = self.url.strip('/')
183 rpath = self.url.strip('/')
184 cfgtrunk = self.ui.config('convert', 'svn.trunk')
184 cfgtrunk = self.ui.config('convert', 'svn.trunk')
185 cfgbranches = self.ui.config('convert', 'svn.branches')
185 cfgbranches = self.ui.config('convert', 'svn.branches')
186 cfgtags = self.ui.config('convert', 'svn.tags')
186 cfgtags = self.ui.config('convert', 'svn.tags')
187 trunk = (cfgtrunk or 'trunk').strip('/')
187 trunk = (cfgtrunk or 'trunk').strip('/')
188 branches = (cfgbranches or 'branches').strip('/')
188 branches = (cfgbranches or 'branches').strip('/')
189 tags = (cfgtags or 'tags').strip('/')
189 tags = (cfgtags or 'tags').strip('/')
190 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
190 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
191 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
191 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
192 (trunk, branches, tags))
192 (trunk, branches, tags))
193 oldmodule = self.module
193 oldmodule = self.module
194 self.module += '/' + trunk
194 self.module += '/' + trunk
195 lt = self.latest(self.module, self.last_changed)
195 lt = self.latest(self.module, self.last_changed)
196 self.head = self.revid(lt)
196 self.head = self.revid(lt)
197 self.heads = [self.head]
197 self.heads = [self.head]
198 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
198 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
199 self.ctx)
199 self.ctx)
200 for branch in branchnames.keys():
200 for branch in branchnames.keys():
201 if oldmodule:
201 if oldmodule:
202 module = oldmodule + '/' + branches + '/' + branch
202 module = oldmodule + '/' + branches + '/' + branch
203 else:
203 else:
204 module = '/' + branches + '/' + branch
204 module = '/' + branches + '/' + branch
205 brevnum = self.latest(module, self.last_changed)
205 brevnum = self.latest(module, self.last_changed)
206 brev = self.revid(brevnum, module)
206 brev = self.revid(brevnum, module)
207 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
207 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
208 self.heads.append(brev)
208 self.heads.append(brev)
209
209
210 if oldmodule:
210 if oldmodule:
211 self.tags = '%s/%s' % (oldmodule, tags)
211 self.tags = '%s/%s' % (oldmodule, tags)
212 else:
212 else:
213 self.tags = '/%s' % tags
213 self.tags = '/%s' % tags
214
214
215 elif cfgtrunk or cfgbranches or cfgtags:
215 elif cfgtrunk or cfgbranches or cfgtags:
216 raise util.Abort('trunk/branch/tags layout expected, but not found')
216 raise util.Abort('trunk/branch/tags layout expected, but not found')
217 else:
217 else:
218 self.ui.note('working with one branch\n')
218 self.ui.note('working with one branch\n')
219 self.heads = [self.head]
219 self.heads = [self.head]
220 self.tags = tags
220 self.tags = tags
221 return self.heads
221 return self.heads
222
222
223 def getfile(self, file, rev):
223 def getfile(self, file, rev):
224 data, mode = self._getfile(file, rev)
224 data, mode = self._getfile(file, rev)
225 self.modecache[(file, rev)] = mode
225 self.modecache[(file, rev)] = mode
226 return data
226 return data
227
227
228 def getmode(self, file, rev):
228 def getmode(self, file, rev):
229 return self.modecache[(file, rev)]
229 return self.modecache[(file, rev)]
230
230
231 def getchanges(self, rev):
231 def getchanges(self, rev):
232 if self._changescache and self._changescache[0] == rev:
232 if self._changescache and self._changescache[0] == rev:
233 return self._changescache[1]
233 return self._changescache[1]
234 self._changescache = None
234 self._changescache = None
235 self.modecache = {}
235 self.modecache = {}
236 (paths, parents) = self.paths[rev]
236 (paths, parents) = self.paths[rev]
237 files, copies = self.expandpaths(rev, paths, parents)
237 files, copies = self.expandpaths(rev, paths, parents)
238 files.sort()
238 files.sort()
239 files = zip(files, [rev] * len(files))
239 files = zip(files, [rev] * len(files))
240
240
241 # caller caches the result, so free it here to release memory
241 # caller caches the result, so free it here to release memory
242 del self.paths[rev]
242 del self.paths[rev]
243 return (files, copies)
243 return (files, copies)
244
244
245 def getchangedfiles(self, rev, i):
245 def getchangedfiles(self, rev, i):
246 changes = self.getchanges(rev)
246 changes = self.getchanges(rev)
247 self._changescache = (rev, changes)
247 self._changescache = (rev, changes)
248 return [f[0] for f in changes[0]]
248 return [f[0] for f in changes[0]]
249
249
250 def getcommit(self, rev):
250 def getcommit(self, rev):
251 if rev not in self.commits:
251 if rev not in self.commits:
252 uuid, module, revnum = self.revsplit(rev)
252 uuid, module, revnum = self.revsplit(rev)
253 self.module = module
253 self.module = module
254 self.reparent(module)
254 self.reparent(module)
255 stop = self.lastrevs.get(module, 0)
255 stop = self.lastrevs.get(module, 0)
256 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
256 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
257 commit = self.commits[rev]
257 commit = self.commits[rev]
258 # caller caches the result, so free it here to release memory
258 # caller caches the result, so free it here to release memory
259 del self.commits[rev]
259 del self.commits[rev]
260 return commit
260 return commit
261
261
262 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
262 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
263 strict_node_history=False):
263 strict_node_history=False):
264
264
265 def parent(fp):
265 def parent(fp):
266 while True:
266 while True:
267 entry = pickle.load(fp)
267 entry = pickle.load(fp)
268 try:
268 try:
269 orig_paths, revnum, author, date, message = entry
269 orig_paths, revnum, author, date, message = entry
270 except:
270 except:
271 if entry is None:
271 if entry is None:
272 break
272 break
273 raise SubversionException("child raised exception", entry)
273 raise SubversionException("child raised exception", entry)
274 yield entry
274 yield entry
275
275
276 args = [self.url, paths, start, end, limit, discover_changed_paths,
276 args = [self.url, paths, start, end, limit, discover_changed_paths,
277 strict_node_history]
277 strict_node_history]
278 arg = encodeargs(args)
278 arg = encodeargs(args)
279 hgexe = util.hgexecutable()
279 hgexe = util.hgexecutable()
280 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
280 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
281 stdin, stdout = os.popen2(cmd, 'b')
281 stdin, stdout = os.popen2(cmd, 'b')
282
282
283 stdin.write(arg)
283 stdin.write(arg)
284 stdin.close()
284 stdin.close()
285
285
286 for p in parent(stdout):
286 for p in parent(stdout):
287 yield p
287 yield p
288
288
289 def gettags(self):
289 def gettags(self):
290 tags = {}
290 tags = {}
291 start = self.revnum(self.head)
291 start = self.revnum(self.head)
292 try:
292 try:
293 for entry in self.get_log([self.tags], 0, start):
293 for entry in self.get_log([self.tags], 0, start):
294 orig_paths, revnum, author, date, message = entry
294 orig_paths, revnum, author, date, message = entry
295 for path in orig_paths:
295 for path in orig_paths:
296 if not path.startswith(self.tags+'/'):
296 if not path.startswith(self.tags+'/'):
297 continue
297 continue
298 ent = orig_paths[path]
298 ent = orig_paths[path]
299 source = ent.copyfrom_path
299 source = ent.copyfrom_path
300 rev = ent.copyfrom_rev
300 rev = ent.copyfrom_rev
301 tag = path.split('/')[-1]
301 tag = path.split('/')[-1]
302 tags[tag] = self.revid(rev, module=source)
302 tags[tag] = self.revid(rev, module=source)
303 except SubversionException, (inst, num):
303 except SubversionException, (inst, num):
304 self.ui.note('no tags found at revision %d\n' % start)
304 self.ui.note('no tags found at revision %d\n' % start)
305 return tags
305 return tags
306
306
307 def converted(self, rev, destrev):
307 def converted(self, rev, destrev):
308 if not self.wc:
308 if not self.wc:
309 return
309 return
310 if self.convertfp is None:
310 if self.convertfp is None:
311 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
311 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
312 'a')
312 'a')
313 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
313 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
314 self.convertfp.flush()
314 self.convertfp.flush()
315
315
316 # -- helper functions --
316 # -- helper functions --
317
317
318 def revid(self, revnum, module=None):
318 def revid(self, revnum, module=None):
319 if not module:
319 if not module:
320 module = self.module
320 module = self.module
321 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
321 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
322 revnum)
322 revnum)
323
323
324 def revnum(self, rev):
324 def revnum(self, rev):
325 return int(rev.split('@')[-1])
325 return int(rev.split('@')[-1])
326
326
327 def revsplit(self, rev):
327 def revsplit(self, rev):
328 url, revnum = rev.encode(self.encoding).split('@', 1)
328 url, revnum = rev.encode(self.encoding).split('@', 1)
329 revnum = int(revnum)
329 revnum = int(revnum)
330 parts = url.split('/', 1)
330 parts = url.split('/', 1)
331 uuid = parts.pop(0)[4:]
331 uuid = parts.pop(0)[4:]
332 mod = ''
332 mod = ''
333 if parts:
333 if parts:
334 mod = '/' + parts[0]
334 mod = '/' + parts[0]
335 return uuid, mod, revnum
335 return uuid, mod, revnum
336
336
337 def latest(self, path, stop=0):
337 def latest(self, path, stop=0):
338 'find the latest revision affecting path, up to stop'
338 'find the latest revision affecting path, up to stop'
339 if not stop:
339 if not stop:
340 stop = svn.ra.get_latest_revnum(self.ra)
340 stop = svn.ra.get_latest_revnum(self.ra)
341 try:
341 try:
342 self.reparent('')
342 self.reparent('')
343 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
343 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
344 self.reparent(self.module)
344 self.reparent(self.module)
345 except SubversionException:
345 except SubversionException:
346 dirent = None
346 dirent = None
347 if not dirent:
347 if not dirent:
348 raise util.Abort('%s not found up to revision %d' % (path, stop))
348 raise util.Abort('%s not found up to revision %d' % (path, stop))
349
349
350 return dirent.created_rev
350 return dirent.created_rev
351
351
352 def get_blacklist(self):
352 def get_blacklist(self):
353 """Avoid certain revision numbers.
353 """Avoid certain revision numbers.
354 It is not uncommon for two nearby revisions to cancel each other
354 It is not uncommon for two nearby revisions to cancel each other
355 out, e.g. 'I copied trunk into a subdirectory of itself instead
355 out, e.g. 'I copied trunk into a subdirectory of itself instead
356 of making a branch'. The converted repository is significantly
356 of making a branch'. The converted repository is significantly
357 smaller if we ignore such revisions."""
357 smaller if we ignore such revisions."""
358 self.blacklist = util.set()
358 self.blacklist = util.set()
359 blacklist = self.blacklist
359 blacklist = self.blacklist
360 for line in file("blacklist.txt", "r"):
360 for line in file("blacklist.txt", "r"):
361 if not line.startswith("#"):
361 if not line.startswith("#"):
362 try:
362 try:
363 svn_rev = int(line.strip())
363 svn_rev = int(line.strip())
364 blacklist.add(svn_rev)
364 blacklist.add(svn_rev)
365 except ValueError, e:
365 except ValueError, e:
366 pass # not an integer or a comment
366 pass # not an integer or a comment
367
367
368 def is_blacklisted(self, svn_rev):
368 def is_blacklisted(self, svn_rev):
369 return svn_rev in self.blacklist
369 return svn_rev in self.blacklist
370
370
371 def reparent(self, module):
371 def reparent(self, module):
372 svn_url = self.base + module
372 svn_url = self.base + module
373 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
373 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
374 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
374 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
375
375
376 def expandpaths(self, rev, paths, parents):
376 def expandpaths(self, rev, paths, parents):
377 def get_entry_from_path(path, module=self.module):
377 def get_entry_from_path(path, module=self.module):
378 # Given the repository url of this wc, say
378 # Given the repository url of this wc, say
379 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
379 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
380 # extract the "entry" portion (a relative path) from what
380 # extract the "entry" portion (a relative path) from what
381 # svn log --xml says, ie
381 # svn log --xml says, ie
382 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
382 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
383 # that is to say "tests/PloneTestCase.py"
383 # that is to say "tests/PloneTestCase.py"
384 if path.startswith(module):
384 if path.startswith(module):
385 relative = path[len(module):]
385 relative = path[len(module):]
386 if relative.startswith('/'):
386 if relative.startswith('/'):
387 return relative[1:]
387 return relative[1:]
388 else:
388 else:
389 return relative
389 return relative
390
390
391 # The path is outside our tracked tree...
391 # The path is outside our tracked tree...
392 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
392 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
393 return None
393 return None
394
394
395 entries = []
395 entries = []
396 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
396 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
397 copies = {}
397 copies = {}
398 revnum = self.revnum(rev)
398 revnum = self.revnum(rev)
399
399
400 if revnum in self.modulemap:
400 if revnum in self.modulemap:
401 new_module = self.modulemap[revnum]
401 new_module = self.modulemap[revnum]
402 if new_module != self.module:
402 if new_module != self.module:
403 self.module = new_module
403 self.module = new_module
404 self.reparent(self.module)
404 self.reparent(self.module)
405
405
406 for path, ent in paths:
406 for path, ent in paths:
407 entrypath = get_entry_from_path(path, module=self.module)
407 entrypath = get_entry_from_path(path, module=self.module)
408 entry = entrypath.decode(self.encoding)
408 entry = entrypath.decode(self.encoding)
409
409
410 kind = svn.ra.check_path(self.ra, entrypath, revnum)
410 kind = svn.ra.check_path(self.ra, entrypath, revnum)
411 if kind == svn.core.svn_node_file:
411 if kind == svn.core.svn_node_file:
412 if ent.copyfrom_path:
412 if ent.copyfrom_path:
413 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
413 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
414 if copyfrom_path:
414 if copyfrom_path:
415 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
415 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
416 # It's probably important for hg that the source
416 # It's probably important for hg that the source
417 # exists in the revision's parent, not just the
417 # exists in the revision's parent, not just the
418 # ent.copyfrom_rev
418 # ent.copyfrom_rev
419 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
419 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
420 if fromkind != 0:
420 if fromkind != 0:
421 copies[self.recode(entry)] = self.recode(copyfrom_path)
421 copies[self.recode(entry)] = self.recode(copyfrom_path)
422 entries.append(self.recode(entry))
422 entries.append(self.recode(entry))
423 elif kind == 0: # gone, but had better be a deleted *file*
423 elif kind == 0: # gone, but had better be a deleted *file*
424 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
424 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
425
425
426 # if a branch is created but entries are removed in the same
426 # if a branch is created but entries are removed in the same
427 # changeset, get the right fromrev
427 # changeset, get the right fromrev
428 if parents:
428 if parents:
429 uuid, old_module, fromrev = self.revsplit(parents[0])
429 uuid, old_module, fromrev = self.revsplit(parents[0])
430 else:
430 else:
431 fromrev = revnum - 1
431 fromrev = revnum - 1
432 # might always need to be revnum - 1 in these 3 lines?
432 # might always need to be revnum - 1 in these 3 lines?
433 old_module = self.modulemap.get(fromrev, self.module)
433 old_module = self.modulemap.get(fromrev, self.module)
434
434
435 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
435 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
436 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
436 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
437
437
438 def lookup_parts(p):
438 def lookup_parts(p):
439 rc = None
439 rc = None
440 parts = p.split("/")
440 parts = p.split("/")
441 for i in range(len(parts)):
441 for i in range(len(parts)):
442 part = "/".join(parts[:i])
442 part = "/".join(parts[:i])
443 info = part, copyfrom.get(part, None)
443 info = part, copyfrom.get(part, None)
444 if info[1] is not None:
444 if info[1] is not None:
445 self.ui.debug("Found parent directory %s\n" % info[1])
445 self.ui.debug("Found parent directory %s\n" % info[1])
446 rc = info
446 rc = info
447 return rc
447 return rc
448
448
449 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
449 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
450
450
451 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
451 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
452
452
453 # need to remove fragment from lookup_parts and replace with copyfrom_path
453 # need to remove fragment from lookup_parts and replace with copyfrom_path
454 if frompath is not None:
454 if frompath is not None:
455 self.ui.debug("munge-o-matic\n")
455 self.ui.debug("munge-o-matic\n")
456 self.ui.debug(entrypath + '\n')
456 self.ui.debug(entrypath + '\n')
457 self.ui.debug(entrypath[len(frompath):] + '\n')
457 self.ui.debug(entrypath[len(frompath):] + '\n')
458 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
458 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
459 fromrev = froment.copyfrom_rev
459 fromrev = froment.copyfrom_rev
460 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
460 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
461
461
462 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
462 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
463 if fromkind == svn.core.svn_node_file: # a deleted file
463 if fromkind == svn.core.svn_node_file: # a deleted file
464 entries.append(self.recode(entry))
464 entries.append(self.recode(entry))
465 elif fromkind == svn.core.svn_node_dir:
465 elif fromkind == svn.core.svn_node_dir:
466 # print "Deleted/moved non-file:", revnum, path, ent
466 # print "Deleted/moved non-file:", revnum, path, ent
467 # children = self._find_children(path, revnum - 1)
467 # children = self._find_children(path, revnum - 1)
468 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
468 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
469 # Sometimes this is tricky. For example: in
469 # Sometimes this is tricky. For example: in
470 # The Subversion Repository revision 6940 a dir
470 # The Subversion Repository revision 6940 a dir
471 # was copied and one of its files was deleted
471 # was copied and one of its files was deleted
472 # from the new location in the same commit. This
472 # from the new location in the same commit. This
473 # code can't deal with that yet.
473 # code can't deal with that yet.
474 if ent.action == 'C':
474 if ent.action == 'C':
475 children = self._find_children(path, fromrev)
475 children = self._find_children(path, fromrev)
476 else:
476 else:
477 oroot = entrypath.strip('/')
477 oroot = entrypath.strip('/')
478 nroot = path.strip('/')
478 nroot = path.strip('/')
479 children = self._find_children(oroot, fromrev)
479 children = self._find_children(oroot, fromrev)
480 children = [s.replace(oroot,nroot) for s in children]
480 children = [s.replace(oroot,nroot) for s in children]
481 # Mark all [files, not directories] as deleted.
481 # Mark all [files, not directories] as deleted.
482 for child in children:
482 for child in children:
483 # Can we move a child directory and its
483 # Can we move a child directory and its
484 # parent in the same commit? (probably can). Could
484 # parent in the same commit? (probably can). Could
485 # cause problems if instead of revnum -1,
485 # cause problems if instead of revnum -1,
486 # we have to look in (copyfrom_path, revnum - 1)
486 # we have to look in (copyfrom_path, revnum - 1)
487 entrypath = get_entry_from_path("/" + child, module=old_module)
487 entrypath = get_entry_from_path("/" + child, module=old_module)
488 if entrypath:
488 if entrypath:
489 entry = self.recode(entrypath.decode(self.encoding))
489 entry = self.recode(entrypath.decode(self.encoding))
490 if entry in copies:
490 if entry in copies:
491 # deleted file within a copy
491 # deleted file within a copy
492 del copies[entry]
492 del copies[entry]
493 else:
493 else:
494 entries.append(entry)
494 entries.append(entry)
495 else:
495 else:
496 self.ui.debug('unknown path in revision %d: %s\n' % \
496 self.ui.debug('unknown path in revision %d: %s\n' % \
497 (revnum, path))
497 (revnum, path))
498 elif kind == svn.core.svn_node_dir:
498 elif kind == svn.core.svn_node_dir:
499 # Should probably synthesize normal file entries
499 # Should probably synthesize normal file entries
500 # and handle as above to clean up copy/rename handling.
500 # and handle as above to clean up copy/rename handling.
501
501
502 # If the directory just had a prop change,
502 # If the directory just had a prop change,
503 # then we shouldn't need to look for its children.
503 # then we shouldn't need to look for its children.
504 # Also this could create duplicate entries. Not sure
504 # Also this could create duplicate entries. Not sure
505 # whether this will matter. Maybe should make entries a set.
505 # whether this will matter. Maybe should make entries a set.
506 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
506 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
507 # This will fail if a directory was copied
507 # This will fail if a directory was copied
508 # from another branch and then some of its files
508 # from another branch and then some of its files
509 # were deleted in the same transaction.
509 # were deleted in the same transaction.
510 children = self._find_children(path, revnum)
510 children = self._find_children(path, revnum)
511 children.sort()
511 children.sort()
512 for child in children:
512 for child in children:
513 # Can we move a child directory and its
513 # Can we move a child directory and its
514 # parent in the same commit? (probably can). Could
514 # parent in the same commit? (probably can). Could
515 # cause problems if instead of revnum -1,
515 # cause problems if instead of revnum -1,
516 # we have to look in (copyfrom_path, revnum - 1)
516 # we have to look in (copyfrom_path, revnum - 1)
517 entrypath = get_entry_from_path("/" + child, module=self.module)
517 entrypath = get_entry_from_path("/" + child, module=self.module)
518 # print child, self.module, entrypath
518 # print child, self.module, entrypath
519 if entrypath:
519 if entrypath:
520 # Need to filter out directories here...
520 # Need to filter out directories here...
521 kind = svn.ra.check_path(self.ra, entrypath, revnum)
521 kind = svn.ra.check_path(self.ra, entrypath, revnum)
522 if kind != svn.core.svn_node_dir:
522 if kind != svn.core.svn_node_dir:
523 entries.append(self.recode(entrypath))
523 entries.append(self.recode(entrypath))
524
524
525 # Copies here (must copy all from source)
525 # Copies here (must copy all from source)
526 # Probably not a real problem for us if
526 # Probably not a real problem for us if
527 # source does not exist
527 # source does not exist
528
528
529 # Can do this with the copy command "hg copy"
529 # Can do this with the copy command "hg copy"
530 # if ent.copyfrom_path:
530 # if ent.copyfrom_path:
531 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
531 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
532 # module=self.module)
532 # module=self.module)
533 # copyto_entry = entrypath
533 # copyto_entry = entrypath
534 #
534 #
535 # print "copy directory", copyfrom_entry, 'to', copyto_entry
535 # print "copy directory", copyfrom_entry, 'to', copyto_entry
536 #
536 #
537 # copies.append((copyfrom_entry, copyto_entry))
537 # copies.append((copyfrom_entry, copyto_entry))
538
538
539 if ent.copyfrom_path:
539 if ent.copyfrom_path:
540 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
540 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
541 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
541 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
542 if copyfrom_entry:
542 if copyfrom_entry:
543 copyfrom[path] = ent
543 copyfrom[path] = ent
544 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
544 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
545
545
546 # Good, /probably/ a regular copy. Really should check
546 # Good, /probably/ a regular copy. Really should check
547 # to see whether the parent revision actually contains
547 # to see whether the parent revision actually contains
548 # the directory in question.
548 # the directory in question.
549 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
549 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
550 children.sort()
550 children.sort()
551 for child in children:
551 for child in children:
552 entrypath = get_entry_from_path("/" + child, module=self.module)
552 entrypath = get_entry_from_path("/" + child, module=self.module)
553 if entrypath:
553 if entrypath:
554 entry = entrypath.decode(self.encoding)
554 entry = entrypath.decode(self.encoding)
555 # print "COPY COPY From", copyfrom_entry, entry
555 # print "COPY COPY From", copyfrom_entry, entry
556 copyto_path = path + entry[len(copyfrom_entry):]
556 copyto_path = path + entry[len(copyfrom_entry):]
557 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
557 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
558 # print "COPY", entry, "COPY To", copyto_entry
558 # print "COPY", entry, "COPY To", copyto_entry
559 copies[self.recode(copyto_entry)] = self.recode(entry)
559 copies[self.recode(copyto_entry)] = self.recode(entry)
560 # copy from quux splort/quuxfile
560 # copy from quux splort/quuxfile
561
561
562 return (entries, copies)
562 return (entries, copies)
563
563
564 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
564 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
565 self.child_cset = None
565 self.child_cset = None
566 def parselogentry(orig_paths, revnum, author, date, message):
566 def parselogentry(orig_paths, revnum, author, date, message):
567 self.ui.debug("parsing revision %d (%d changes)\n" %
567 self.ui.debug("parsing revision %d (%d changes)\n" %
568 (revnum, len(orig_paths)))
568 (revnum, len(orig_paths)))
569
569
570 if revnum in self.modulemap:
570 if revnum in self.modulemap:
571 new_module = self.modulemap[revnum]
571 new_module = self.modulemap[revnum]
572 if new_module != self.module:
572 if new_module != self.module:
573 self.module = new_module
573 self.module = new_module
574 self.reparent(self.module)
574 self.reparent(self.module)
575
575
576 rev = self.revid(revnum)
576 rev = self.revid(revnum)
577 # branch log might return entries for a parent we already have
577 # branch log might return entries for a parent we already have
578 if (rev in self.commits or
578 if (rev in self.commits or
579 (revnum < self.lastrevs.get(self.module, 0))):
579 (revnum < self.lastrevs.get(self.module, 0))):
580 return
580 return
581
581
582 parents = []
582 parents = []
583 # check whether this revision is the start of a branch
583 # check whether this revision is the start of a branch
584 if self.module in orig_paths:
584 if self.module in orig_paths:
585 ent = orig_paths[self.module]
585 ent = orig_paths[self.module]
586 if ent.copyfrom_path:
586 if ent.copyfrom_path:
587 # ent.copyfrom_rev may not be the actual last revision
587 # ent.copyfrom_rev may not be the actual last revision
588 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
588 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
589 self.modulemap[prev] = ent.copyfrom_path
589 self.modulemap[prev] = ent.copyfrom_path
590 parents = [self.revid(prev, ent.copyfrom_path)]
590 parents = [self.revid(prev, ent.copyfrom_path)]
591 self.ui.note('found parent of branch %s at %d: %s\n' % \
591 self.ui.note('found parent of branch %s at %d: %s\n' % \
592 (self.module, prev, ent.copyfrom_path))
592 (self.module, prev, ent.copyfrom_path))
593 else:
593 else:
594 self.ui.debug("No copyfrom path, don't know what to do.\n")
594 self.ui.debug("No copyfrom path, don't know what to do.\n")
595
595
596 self.modulemap[revnum] = self.module # track backwards in time
596 self.modulemap[revnum] = self.module # track backwards in time
597
597
598 orig_paths = orig_paths.items()
598 orig_paths = orig_paths.items()
599 orig_paths.sort()
599 orig_paths.sort()
600 paths = []
600 paths = []
601 # filter out unrelated paths
601 # filter out unrelated paths
602 for path, ent in orig_paths:
602 for path, ent in orig_paths:
603 if not path.startswith(self.module):
603 if not path.startswith(self.module):
604 self.ui.debug("boring@%s: %s\n" % (revnum, path))
604 self.ui.debug("boring@%s: %s\n" % (revnum, path))
605 continue
605 continue
606 paths.append((path, ent))
606 paths.append((path, ent))
607
607
608 self.paths[rev] = (paths, parents)
608 self.paths[rev] = (paths, parents)
609
609
610 # Example SVN datetime. Includes microseconds.
610 # Example SVN datetime. Includes microseconds.
611 # ISO-8601 conformant
611 # ISO-8601 conformant
612 # '2007-01-04T17:35:00.902377Z'
612 # '2007-01-04T17:35:00.902377Z'
613 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
613 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
614
614
615 log = message and self.recode(message)
615 log = message and self.recode(message)
616 author = author and self.recode(author) or ''
616 author = author and self.recode(author) or ''
617 try:
617 try:
618 branch = self.module.split("/")[-1]
618 branch = self.module.split("/")[-1]
619 if branch == 'trunk':
619 if branch == 'trunk':
620 branch = ''
620 branch = ''
621 except IndexError:
621 except IndexError:
622 branch = None
622 branch = None
623
623
624 cset = commit(author=author,
624 cset = commit(author=author,
625 date=util.datestr(date),
625 date=util.datestr(date),
626 desc=log,
626 desc=log,
627 parents=parents,
627 parents=parents,
628 branch=branch,
628 branch=branch,
629 rev=rev.encode('utf-8'))
629 rev=rev.encode('utf-8'))
630
630
631 self.commits[rev] = cset
631 self.commits[rev] = cset
632 if self.child_cset and not self.child_cset.parents:
632 if self.child_cset and not self.child_cset.parents:
633 self.child_cset.parents = [rev]
633 self.child_cset.parents = [rev]
634 self.child_cset = cset
634 self.child_cset = cset
635
635
636 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
636 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
637 (self.module, from_revnum, to_revnum))
637 (self.module, from_revnum, to_revnum))
638
638
639 try:
639 try:
640 for entry in self.get_log([self.module], from_revnum, to_revnum):
640 for entry in self.get_log([self.module], from_revnum, to_revnum):
641 orig_paths, revnum, author, date, message = entry
641 orig_paths, revnum, author, date, message = entry
642 if self.is_blacklisted(revnum):
642 if self.is_blacklisted(revnum):
643 self.ui.note('skipping blacklisted revision %d\n' % revnum)
643 self.ui.note('skipping blacklisted revision %d\n' % revnum)
644 continue
644 continue
645 if orig_paths is None:
645 if orig_paths is None:
646 self.ui.debug('revision %d has no entries\n' % revnum)
646 self.ui.debug('revision %d has no entries\n' % revnum)
647 continue
647 continue
648 parselogentry(orig_paths, revnum, author, date, message)
648 parselogentry(orig_paths, revnum, author, date, message)
649 except SubversionException, (inst, num):
649 except SubversionException, (inst, num):
650 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
650 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
651 raise NoSuchRevision(branch=self,
651 raise NoSuchRevision(branch=self,
652 revision="Revision number %d" % to_revnum)
652 revision="Revision number %d" % to_revnum)
653 raise
653 raise
654
654
655 def _getfile(self, file, rev):
655 def _getfile(self, file, rev):
656 io = StringIO()
656 io = StringIO()
657 # TODO: ra.get_file transmits the whole file instead of diffs.
657 # TODO: ra.get_file transmits the whole file instead of diffs.
658 mode = ''
658 mode = ''
659 try:
659 try:
660 revnum = self.revnum(rev)
660 revnum = self.revnum(rev)
661 if self.module != self.modulemap[revnum]:
661 if self.module != self.modulemap[revnum]:
662 self.module = self.modulemap[revnum]
662 self.module = self.modulemap[revnum]
663 self.reparent(self.module)
663 self.reparent(self.module)
664 info = svn.ra.get_file(self.ra, file, revnum, io)
664 info = svn.ra.get_file(self.ra, file, revnum, io)
665 if isinstance(info, list):
665 if isinstance(info, list):
666 info = info[-1]
666 info = info[-1]
667 mode = ("svn:executable" in info) and 'x' or ''
667 mode = ("svn:executable" in info) and 'x' or ''
668 mode = ("svn:special" in info) and 'l' or mode
668 mode = ("svn:special" in info) and 'l' or mode
669 except SubversionException, e:
669 except SubversionException, e:
670 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
670 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
671 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
671 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
672 if e.apr_err in notfound: # File not found
672 if e.apr_err in notfound: # File not found
673 raise IOError()
673 raise IOError()
674 raise
674 raise
675 data = io.getvalue()
675 data = io.getvalue()
676 if mode == 'l':
676 if mode == 'l':
677 link_prefix = "link "
677 link_prefix = "link "
678 if data.startswith(link_prefix):
678 if data.startswith(link_prefix):
679 data = data[len(link_prefix):]
679 data = data[len(link_prefix):]
680 return data, mode
680 return data, mode
681
681
682 def _find_children(self, path, revnum):
682 def _find_children(self, path, revnum):
683 path = path.strip('/')
683 path = path.strip('/')
684 pool = Pool()
684 pool = Pool()
685 rpath = '/'.join([self.base, path]).strip('/')
685 rpath = '/'.join([self.base, path]).strip('/')
686 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
686 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
687
687
688 pre_revprop_change = '''#!/bin/sh
688 pre_revprop_change = '''#!/bin/sh
689
689
690 REPOS="$1"
690 REPOS="$1"
691 REV="$2"
691 REV="$2"
692 USER="$3"
692 USER="$3"
693 PROPNAME="$4"
693 PROPNAME="$4"
694 ACTION="$5"
694 ACTION="$5"
695
695
696 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
696 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
697 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
697 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
698 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
698 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
699
699
700 echo "Changing prohibited revision property" >&2
700 echo "Changing prohibited revision property" >&2
701 exit 1
701 exit 1
702 '''
702 '''
703
703
704 class svn_sink(converter_sink, commandline):
704 class svn_sink(converter_sink, commandline):
705 commit_re = re.compile(r'Committed revision (\d+).', re.M)
705 commit_re = re.compile(r'Committed revision (\d+).', re.M)
706
706
707 # iterates sublist of given list for concatenated length is within limit
708 def limit_arglist(self, files):
709 if os.name != 'nt':
710 yield files
711 return
712 # When I tested on WinXP, limit = 2500 is NG, 2400 is OK
713 limit = 2000
714 bytes = 0
715 fl = []
716 for fn in files:
717 b = len(fn) + 1
718 if bytes + b < limit:
719 fl.append(fn)
720 bytes += b
721 else:
722 yield fl
723 fl = []
724 bytes = 0
725 if fl:
726 yield fl
727
707 def prerun(self):
728 def prerun(self):
708 if self.wc:
729 if self.wc:
709 os.chdir(self.wc)
730 os.chdir(self.wc)
710
731
711 def postrun(self):
732 def postrun(self):
712 if self.wc:
733 if self.wc:
713 os.chdir(self.cwd)
734 os.chdir(self.cwd)
714
735
715 def join(self, name):
736 def join(self, name):
716 return os.path.join(self.wc, '.svn', name)
737 return os.path.join(self.wc, '.svn', name)
717
738
718 def revmapfile(self):
739 def revmapfile(self):
719 return self.join('hg-shamap')
740 return self.join('hg-shamap')
720
741
721 def authorfile(self):
742 def authorfile(self):
722 return self.join('hg-authormap')
743 return self.join('hg-authormap')
723
744
724 def __init__(self, ui, path):
745 def __init__(self, ui, path):
725 converter_sink.__init__(self, ui, path)
746 converter_sink.__init__(self, ui, path)
726 commandline.__init__(self, ui, 'svn')
747 commandline.__init__(self, ui, 'svn')
727 self.delete = []
748 self.delete = []
728 self.setexec = []
749 self.setexec = []
729 self.delexec = []
750 self.delexec = []
730 self.copies = []
751 self.copies = []
731 self.wc = None
752 self.wc = None
732 self.cwd = os.getcwd()
753 self.cwd = os.getcwd()
733
754
734 path = os.path.realpath(path)
755 path = os.path.realpath(path)
735
756
736 created = False
757 created = False
737 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
758 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
738 self.wc = path
759 self.wc = path
739 self.run0('update')
760 self.run0('update')
740 else:
761 else:
741 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
762 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
742
763
743 if os.path.isdir(os.path.dirname(path)):
764 if os.path.isdir(os.path.dirname(path)):
744 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
765 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
745 ui.status(_('initializing svn repo %r\n') %
766 ui.status(_('initializing svn repo %r\n') %
746 os.path.basename(path))
767 os.path.basename(path))
747 commandline(ui, 'svnadmin').run0('create', path)
768 commandline(ui, 'svnadmin').run0('create', path)
748 created = path
769 created = path
749 path = path.replace('\\', '/')
770 path = path.replace('\\', '/')
750 if not path.startswith('/'):
771 if not path.startswith('/'):
751 path = '/' + path
772 path = '/' + path
752 path = 'file://' + path
773 path = 'file://' + path
753
774
754 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
775 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
755 self.run0('checkout', path, wcpath)
776 self.run0('checkout', path, wcpath)
756
777
757 self.wc = wcpath
778 self.wc = wcpath
758 self.opener = util.opener(self.wc)
779 self.opener = util.opener(self.wc)
759 self.wopener = util.opener(self.wc)
780 self.wopener = util.opener(self.wc)
760 self.childmap = mapfile(ui, self.join('hg-childmap'))
781 self.childmap = mapfile(ui, self.join('hg-childmap'))
761 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
782 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
762
783
763 if created:
784 if created:
764 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
785 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
765 fp = open(hook, 'w')
786 fp = open(hook, 'w')
766 fp.write(pre_revprop_change)
787 fp.write(pre_revprop_change)
767 fp.close()
788 fp.close()
768 util.set_flags(hook, "x")
789 util.set_flags(hook, "x")
769
790
770 xport = transport.SvnRaTransport(url=geturl(path))
791 xport = transport.SvnRaTransport(url=geturl(path))
771 self.uuid = svn.ra.get_uuid(xport.ra)
792 self.uuid = svn.ra.get_uuid(xport.ra)
772
793
773 def wjoin(self, *names):
794 def wjoin(self, *names):
774 return os.path.join(self.wc, *names)
795 return os.path.join(self.wc, *names)
775
796
776 def putfile(self, filename, flags, data):
797 def putfile(self, filename, flags, data):
777 if 'l' in flags:
798 if 'l' in flags:
778 self.wopener.symlink(data, filename)
799 self.wopener.symlink(data, filename)
779 else:
800 else:
780 try:
801 try:
781 if os.path.islink(self.wjoin(filename)):
802 if os.path.islink(self.wjoin(filename)):
782 os.unlink(filename)
803 os.unlink(filename)
783 except OSError:
804 except OSError:
784 pass
805 pass
785 self.wopener(filename, 'w').write(data)
806 self.wopener(filename, 'w').write(data)
786
807
787 if self.is_exec:
808 if self.is_exec:
788 was_exec = self.is_exec(self.wjoin(filename))
809 was_exec = self.is_exec(self.wjoin(filename))
789 else:
810 else:
790 # On filesystems not supporting execute-bit, there is no way
811 # On filesystems not supporting execute-bit, there is no way
791 # to know if it is set but asking subversion. Setting it
812 # to know if it is set but asking subversion. Setting it
792 # systematically is just as expensive and much simpler.
813 # systematically is just as expensive and much simpler.
793 was_exec = 'x' not in flags
814 was_exec = 'x' not in flags
794
815
795 util.set_flags(self.wjoin(filename), flags)
816 util.set_flags(self.wjoin(filename), flags)
796 if was_exec:
817 if was_exec:
797 if 'x' not in flags:
818 if 'x' not in flags:
798 self.delexec.append(filename)
819 self.delexec.append(filename)
799 else:
820 else:
800 if 'x' in flags:
821 if 'x' in flags:
801 self.setexec.append(filename)
822 self.setexec.append(filename)
802
823
803 def delfile(self, name):
824 def delfile(self, name):
804 self.delete.append(name)
825 self.delete.append(name)
805
826
806 def copyfile(self, source, dest):
827 def copyfile(self, source, dest):
807 self.copies.append([source, dest])
828 self.copies.append([source, dest])
808
829
809 def _copyfile(self, source, dest):
830 def _copyfile(self, source, dest):
810 # SVN's copy command pukes if the destination file exists, but
831 # SVN's copy command pukes if the destination file exists, but
811 # our copyfile method expects to record a copy that has
832 # our copyfile method expects to record a copy that has
812 # already occurred. Cross the semantic gap.
833 # already occurred. Cross the semantic gap.
813 wdest = self.wjoin(dest)
834 wdest = self.wjoin(dest)
814 exists = os.path.exists(wdest)
835 exists = os.path.exists(wdest)
815 if exists:
836 if exists:
816 fd, tempname = tempfile.mkstemp(
837 fd, tempname = tempfile.mkstemp(
817 prefix='hg-copy-', dir=os.path.dirname(wdest))
838 prefix='hg-copy-', dir=os.path.dirname(wdest))
818 os.close(fd)
839 os.close(fd)
819 os.unlink(tempname)
840 os.unlink(tempname)
820 os.rename(wdest, tempname)
841 os.rename(wdest, tempname)
821 try:
842 try:
822 self.run0('copy', source, dest)
843 self.run0('copy', source, dest)
823 finally:
844 finally:
824 if exists:
845 if exists:
825 try:
846 try:
826 os.unlink(wdest)
847 os.unlink(wdest)
827 except OSError:
848 except OSError:
828 pass
849 pass
829 os.rename(tempname, wdest)
850 os.rename(tempname, wdest)
830
851
831 def dirs_of(self, files):
852 def dirs_of(self, files):
832 dirs = set()
853 dirs = set()
833 for f in files:
854 for f in files:
834 if os.path.isdir(self.wjoin(f)):
855 if os.path.isdir(self.wjoin(f)):
835 dirs.add(f)
856 dirs.add(f)
836 for i in strutil.rfindall(f, '/'):
857 for i in strutil.rfindall(f, '/'):
837 dirs.add(f[:i])
858 dirs.add(f[:i])
838 return dirs
859 return dirs
839
860
840 def add_dirs(self, files):
861 def add_dirs(self, files):
841 add_dirs = [d for d in self.dirs_of(files)
862 add_dirs = [d for d in self.dirs_of(files)
842 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
863 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
843 if add_dirs:
864 if add_dirs:
844 add_dirs.sort()
865 add_dirs.sort()
845 self.run('add', non_recursive=True, quiet=True, *add_dirs)
866 for fl in self.limit_arglist(add_dirs):
867 self.run('add', non_recursive=True, quiet=True, *fl)
846 return add_dirs
868 return add_dirs
847
869
848 def add_files(self, files):
870 def add_files(self, files):
849 if files:
871 if files:
850 self.run('add', quiet=True, *files)
872 for fl in self.limit_arglist(files):
873 self.run('add', quiet=True, *fl)
851 return files
874 return files
852
875
853 def tidy_dirs(self, names):
876 def tidy_dirs(self, names):
854 dirs = list(self.dirs_of(names))
877 dirs = list(self.dirs_of(names))
855 dirs.sort(reverse=True)
878 dirs.sort(reverse=True)
856 deleted = []
879 deleted = []
857 for d in dirs:
880 for d in dirs:
858 wd = self.wjoin(d)
881 wd = self.wjoin(d)
859 if os.listdir(wd) == '.svn':
882 if os.listdir(wd) == '.svn':
860 self.run0('delete', d)
883 self.run0('delete', d)
861 deleted.append(d)
884 deleted.append(d)
862 return deleted
885 return deleted
863
886
864 def addchild(self, parent, child):
887 def addchild(self, parent, child):
865 self.childmap[parent] = child
888 self.childmap[parent] = child
866
889
867 def revid(self, rev):
890 def revid(self, rev):
868 return u"svn:%s@%s" % (self.uuid, rev)
891 return u"svn:%s@%s" % (self.uuid, rev)
869
892
870 def putcommit(self, files, parents, commit):
893 def putcommit(self, files, parents, commit):
871 for parent in parents:
894 for parent in parents:
872 try:
895 try:
873 return self.revid(self.childmap[parent])
896 return self.revid(self.childmap[parent])
874 except KeyError:
897 except KeyError:
875 pass
898 pass
876 entries = set(self.delete)
899 entries = set(self.delete)
877 files = util.frozenset(files)
900 files = util.frozenset(files)
878 entries.update(self.add_dirs(files.difference(entries)))
901 entries.update(self.add_dirs(files.difference(entries)))
879 if self.copies:
902 if self.copies:
880 for s, d in self.copies:
903 for s, d in self.copies:
881 self._copyfile(s, d)
904 self._copyfile(s, d)
882 self.copies = []
905 self.copies = []
883 if self.delete:
906 if self.delete:
884 self.run0('delete', *self.delete)
907 for fl in self.limit_arglist(self.delete):
908 self.run0('delete', *fl)
885 self.delete = []
909 self.delete = []
886 entries.update(self.add_files(files.difference(entries)))
910 entries.update(self.add_files(files.difference(entries)))
887 entries.update(self.tidy_dirs(entries))
911 entries.update(self.tidy_dirs(entries))
888 if self.delexec:
912 if self.delexec:
889 self.run0('propdel', 'svn:executable', *self.delexec)
913 for fl in self.limit_arglist(self.delexec):
914 self.run0('propdel', 'svn:executable', *fl)
890 self.delexec = []
915 self.delexec = []
891 if self.setexec:
916 if self.setexec:
892 self.run0('propset', 'svn:executable', '*', *self.setexec)
917 for fl in self.limit_arglist(self.setexec):
918 self.run0('propset', 'svn:executable', '*', *fl)
893 self.setexec = []
919 self.setexec = []
894
920
895 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
921 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
896 fp = os.fdopen(fd, 'w')
922 fp = os.fdopen(fd, 'w')
897 fp.write(commit.desc)
923 fp.write(commit.desc)
898 fp.close()
924 fp.close()
899 try:
925 try:
900 output = self.run0('commit',
926 output = self.run0('commit',
901 username=util.shortuser(commit.author),
927 username=util.shortuser(commit.author),
902 file=messagefile,
928 file=messagefile,
903 encoding='utf-8',
929 encoding='utf-8')
904 *list(entries))
905 try:
930 try:
906 rev = self.commit_re.search(output).group(1)
931 rev = self.commit_re.search(output).group(1)
907 except AttributeError:
932 except AttributeError:
908 self.ui.warn(_('unexpected svn output:\n'))
933 self.ui.warn(_('unexpected svn output:\n'))
909 self.ui.warn(output)
934 self.ui.warn(output)
910 raise util.Abort(_('unable to cope with svn output'))
935 raise util.Abort(_('unable to cope with svn output'))
911 if commit.rev:
936 if commit.rev:
912 self.run('propset', 'hg:convert-rev', commit.rev,
937 self.run('propset', 'hg:convert-rev', commit.rev,
913 revprop=True, revision=rev)
938 revprop=True, revision=rev)
914 if commit.branch and commit.branch != 'default':
939 if commit.branch and commit.branch != 'default':
915 self.run('propset', 'hg:convert-branch', commit.branch,
940 self.run('propset', 'hg:convert-branch', commit.branch,
916 revprop=True, revision=rev)
941 revprop=True, revision=rev)
917 for parent in parents:
942 for parent in parents:
918 self.addchild(parent, rev)
943 self.addchild(parent, rev)
919 return self.revid(rev)
944 return self.revid(rev)
920 finally:
945 finally:
921 os.unlink(messagefile)
946 os.unlink(messagefile)
922
947
923 def puttags(self, tags):
948 def puttags(self, tags):
924 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
949 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
General Comments 0
You need to be logged in to leave comments. Login now