##// END OF EJS Templates
convert: fix svn file:// URL generation under Windows
Patrick Mezard -
r5535:7501ef26 default
parent child Browse files
Show More
@@ -1,866 +1,871
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 def setrevmap(self, revmap):
157 def setrevmap(self, revmap):
158 lastrevs = {}
158 lastrevs = {}
159 for revid in revmap.iterkeys():
159 for revid in revmap.iterkeys():
160 uuid, module, revnum = self.revsplit(revid)
160 uuid, module, revnum = self.revsplit(revid)
161 lastrevnum = lastrevs.setdefault(module, revnum)
161 lastrevnum = lastrevs.setdefault(module, revnum)
162 if revnum > lastrevnum:
162 if revnum > lastrevnum:
163 lastrevs[module] = revnum
163 lastrevs[module] = revnum
164 self.lastrevs = lastrevs
164 self.lastrevs = lastrevs
165
165
166 def exists(self, path, optrev):
166 def exists(self, path, optrev):
167 try:
167 try:
168 svn.client.ls(self.url.rstrip('/') + '/' + path,
168 svn.client.ls(self.url.rstrip('/') + '/' + path,
169 optrev, False, self.ctx)
169 optrev, False, self.ctx)
170 return True
170 return True
171 except SubversionException, err:
171 except SubversionException, err:
172 return False
172 return False
173
173
174 def getheads(self):
174 def getheads(self):
175 # detect standard /branches, /tags, /trunk layout
175 # detect standard /branches, /tags, /trunk layout
176 rev = optrev(self.last_changed)
176 rev = optrev(self.last_changed)
177 rpath = self.url.strip('/')
177 rpath = self.url.strip('/')
178 cfgtrunk = self.ui.config('convert', 'svn.trunk')
178 cfgtrunk = self.ui.config('convert', 'svn.trunk')
179 cfgbranches = self.ui.config('convert', 'svn.branches')
179 cfgbranches = self.ui.config('convert', 'svn.branches')
180 cfgtags = self.ui.config('convert', 'svn.tags')
180 cfgtags = self.ui.config('convert', 'svn.tags')
181 trunk = (cfgtrunk or 'trunk').strip('/')
181 trunk = (cfgtrunk or 'trunk').strip('/')
182 branches = (cfgbranches or 'branches').strip('/')
182 branches = (cfgbranches or 'branches').strip('/')
183 tags = (cfgtags or 'tags').strip('/')
183 tags = (cfgtags or 'tags').strip('/')
184 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
184 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
185 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
185 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
186 (trunk, branches, tags))
186 (trunk, branches, tags))
187 oldmodule = self.module
187 oldmodule = self.module
188 self.module += '/' + trunk
188 self.module += '/' + trunk
189 lt = self.latest(self.module, self.last_changed)
189 lt = self.latest(self.module, self.last_changed)
190 self.head = self.revid(lt)
190 self.head = self.revid(lt)
191 self.heads = [self.head]
191 self.heads = [self.head]
192 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
192 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
193 self.ctx)
193 self.ctx)
194 for branch in branchnames.keys():
194 for branch in branchnames.keys():
195 if oldmodule:
195 if oldmodule:
196 module = oldmodule + '/' + branches + '/' + branch
196 module = oldmodule + '/' + branches + '/' + branch
197 else:
197 else:
198 module = '/' + branches + '/' + branch
198 module = '/' + branches + '/' + branch
199 brevnum = self.latest(module, self.last_changed)
199 brevnum = self.latest(module, self.last_changed)
200 brev = self.revid(brevnum, module)
200 brev = self.revid(brevnum, module)
201 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
201 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
202 self.heads.append(brev)
202 self.heads.append(brev)
203
203
204 if oldmodule:
204 if oldmodule:
205 self.tags = '%s/%s' % (oldmodule, tags)
205 self.tags = '%s/%s' % (oldmodule, tags)
206 else:
206 else:
207 self.tags = '/%s' % tags
207 self.tags = '/%s' % tags
208
208
209 elif cfgtrunk or cfgbranches or cfgtags:
209 elif cfgtrunk or cfgbranches or cfgtags:
210 raise util.Abort('trunk/branch/tags layout expected, but not found')
210 raise util.Abort('trunk/branch/tags layout expected, but not found')
211 else:
211 else:
212 self.ui.note('working with one branch\n')
212 self.ui.note('working with one branch\n')
213 self.heads = [self.head]
213 self.heads = [self.head]
214 self.tags = tags
214 self.tags = tags
215 return self.heads
215 return self.heads
216
216
217 def getfile(self, file, rev):
217 def getfile(self, file, rev):
218 data, mode = self._getfile(file, rev)
218 data, mode = self._getfile(file, rev)
219 self.modecache[(file, rev)] = mode
219 self.modecache[(file, rev)] = mode
220 return data
220 return data
221
221
222 def getmode(self, file, rev):
222 def getmode(self, file, rev):
223 return self.modecache[(file, rev)]
223 return self.modecache[(file, rev)]
224
224
225 def getchanges(self, rev):
225 def getchanges(self, rev):
226 if self._changescache and self._changescache[0] == rev:
226 if self._changescache and self._changescache[0] == rev:
227 return self._changescache[1]
227 return self._changescache[1]
228 self._changescache = None
228 self._changescache = None
229 self.modecache = {}
229 self.modecache = {}
230 (paths, parents) = self.paths[rev]
230 (paths, parents) = self.paths[rev]
231 files, copies = self.expandpaths(rev, paths, parents)
231 files, copies = self.expandpaths(rev, paths, parents)
232 files.sort()
232 files.sort()
233 files = zip(files, [rev] * len(files))
233 files = zip(files, [rev] * len(files))
234
234
235 # caller caches the result, so free it here to release memory
235 # caller caches the result, so free it here to release memory
236 del self.paths[rev]
236 del self.paths[rev]
237 return (files, copies)
237 return (files, copies)
238
238
239 def getchangedfiles(self, rev, i):
239 def getchangedfiles(self, rev, i):
240 changes = self.getchanges(rev)
240 changes = self.getchanges(rev)
241 self._changescache = (rev, changes)
241 self._changescache = (rev, changes)
242 return [f[0] for f in changes[0]]
242 return [f[0] for f in changes[0]]
243
243
244 def getcommit(self, rev):
244 def getcommit(self, rev):
245 if rev not in self.commits:
245 if rev not in self.commits:
246 uuid, module, revnum = self.revsplit(rev)
246 uuid, module, revnum = self.revsplit(rev)
247 self.module = module
247 self.module = module
248 self.reparent(module)
248 self.reparent(module)
249 stop = self.lastrevs.get(module, 0)
249 stop = self.lastrevs.get(module, 0)
250 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
250 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
251 commit = self.commits[rev]
251 commit = self.commits[rev]
252 # caller caches the result, so free it here to release memory
252 # caller caches the result, so free it here to release memory
253 del self.commits[rev]
253 del self.commits[rev]
254 return commit
254 return commit
255
255
256 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
256 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
257 strict_node_history=False):
257 strict_node_history=False):
258
258
259 def parent(fp):
259 def parent(fp):
260 while True:
260 while True:
261 entry = pickle.load(fp)
261 entry = pickle.load(fp)
262 try:
262 try:
263 orig_paths, revnum, author, date, message = entry
263 orig_paths, revnum, author, date, message = entry
264 except:
264 except:
265 if entry is None:
265 if entry is None:
266 break
266 break
267 raise SubversionException("child raised exception", entry)
267 raise SubversionException("child raised exception", entry)
268 yield entry
268 yield entry
269
269
270 args = [self.url, paths, start, end, limit, discover_changed_paths,
270 args = [self.url, paths, start, end, limit, discover_changed_paths,
271 strict_node_history]
271 strict_node_history]
272 arg = encodeargs(args)
272 arg = encodeargs(args)
273 hgexe = util.hgexecutable()
273 hgexe = util.hgexecutable()
274 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
274 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
275 stdin, stdout = os.popen2(cmd, 'b')
275 stdin, stdout = os.popen2(cmd, 'b')
276
276
277 stdin.write(arg)
277 stdin.write(arg)
278 stdin.close()
278 stdin.close()
279
279
280 for p in parent(stdout):
280 for p in parent(stdout):
281 yield p
281 yield p
282
282
283 def gettags(self):
283 def gettags(self):
284 tags = {}
284 tags = {}
285 start = self.revnum(self.head)
285 start = self.revnum(self.head)
286 try:
286 try:
287 for entry in self.get_log([self.tags], 0, start):
287 for entry in self.get_log([self.tags], 0, start):
288 orig_paths, revnum, author, date, message = entry
288 orig_paths, revnum, author, date, message = entry
289 for path in orig_paths:
289 for path in orig_paths:
290 if not path.startswith(self.tags+'/'):
290 if not path.startswith(self.tags+'/'):
291 continue
291 continue
292 ent = orig_paths[path]
292 ent = orig_paths[path]
293 source = ent.copyfrom_path
293 source = ent.copyfrom_path
294 rev = ent.copyfrom_rev
294 rev = ent.copyfrom_rev
295 tag = path.split('/')[-1]
295 tag = path.split('/')[-1]
296 tags[tag] = self.revid(rev, module=source)
296 tags[tag] = self.revid(rev, module=source)
297 except SubversionException, (inst, num):
297 except SubversionException, (inst, num):
298 self.ui.note('no tags found at revision %d\n' % start)
298 self.ui.note('no tags found at revision %d\n' % start)
299 return tags
299 return tags
300
300
301 # -- helper functions --
301 # -- helper functions --
302
302
303 def revid(self, revnum, module=None):
303 def revid(self, revnum, module=None):
304 if not module:
304 if not module:
305 module = self.module
305 module = self.module
306 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
306 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
307 revnum)
307 revnum)
308
308
309 def revnum(self, rev):
309 def revnum(self, rev):
310 return int(rev.split('@')[-1])
310 return int(rev.split('@')[-1])
311
311
312 def revsplit(self, rev):
312 def revsplit(self, rev):
313 url, revnum = rev.encode(self.encoding).split('@', 1)
313 url, revnum = rev.encode(self.encoding).split('@', 1)
314 revnum = int(revnum)
314 revnum = int(revnum)
315 parts = url.split('/', 1)
315 parts = url.split('/', 1)
316 uuid = parts.pop(0)[4:]
316 uuid = parts.pop(0)[4:]
317 mod = ''
317 mod = ''
318 if parts:
318 if parts:
319 mod = '/' + parts[0]
319 mod = '/' + parts[0]
320 return uuid, mod, revnum
320 return uuid, mod, revnum
321
321
322 def latest(self, path, stop=0):
322 def latest(self, path, stop=0):
323 'find the latest revision affecting path, up to stop'
323 'find the latest revision affecting path, up to stop'
324 if not stop:
324 if not stop:
325 stop = svn.ra.get_latest_revnum(self.ra)
325 stop = svn.ra.get_latest_revnum(self.ra)
326 try:
326 try:
327 self.reparent('')
327 self.reparent('')
328 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
328 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
329 self.reparent(self.module)
329 self.reparent(self.module)
330 except SubversionException:
330 except SubversionException:
331 dirent = None
331 dirent = None
332 if not dirent:
332 if not dirent:
333 raise util.Abort('%s not found up to revision %d' % (path, stop))
333 raise util.Abort('%s not found up to revision %d' % (path, stop))
334
334
335 return dirent.created_rev
335 return dirent.created_rev
336
336
337 def get_blacklist(self):
337 def get_blacklist(self):
338 """Avoid certain revision numbers.
338 """Avoid certain revision numbers.
339 It is not uncommon for two nearby revisions to cancel each other
339 It is not uncommon for two nearby revisions to cancel each other
340 out, e.g. 'I copied trunk into a subdirectory of itself instead
340 out, e.g. 'I copied trunk into a subdirectory of itself instead
341 of making a branch'. The converted repository is significantly
341 of making a branch'. The converted repository is significantly
342 smaller if we ignore such revisions."""
342 smaller if we ignore such revisions."""
343 self.blacklist = util.set()
343 self.blacklist = util.set()
344 blacklist = self.blacklist
344 blacklist = self.blacklist
345 for line in file("blacklist.txt", "r"):
345 for line in file("blacklist.txt", "r"):
346 if not line.startswith("#"):
346 if not line.startswith("#"):
347 try:
347 try:
348 svn_rev = int(line.strip())
348 svn_rev = int(line.strip())
349 blacklist.add(svn_rev)
349 blacklist.add(svn_rev)
350 except ValueError, e:
350 except ValueError, e:
351 pass # not an integer or a comment
351 pass # not an integer or a comment
352
352
353 def is_blacklisted(self, svn_rev):
353 def is_blacklisted(self, svn_rev):
354 return svn_rev in self.blacklist
354 return svn_rev in self.blacklist
355
355
356 def reparent(self, module):
356 def reparent(self, module):
357 svn_url = self.base + module
357 svn_url = self.base + module
358 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
358 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
359 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
359 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
360
360
361 def expandpaths(self, rev, paths, parents):
361 def expandpaths(self, rev, paths, parents):
362 def get_entry_from_path(path, module=self.module):
362 def get_entry_from_path(path, module=self.module):
363 # Given the repository url of this wc, say
363 # Given the repository url of this wc, say
364 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
364 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
365 # extract the "entry" portion (a relative path) from what
365 # extract the "entry" portion (a relative path) from what
366 # svn log --xml says, ie
366 # svn log --xml says, ie
367 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
367 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
368 # that is to say "tests/PloneTestCase.py"
368 # that is to say "tests/PloneTestCase.py"
369 if path.startswith(module):
369 if path.startswith(module):
370 relative = path[len(module):]
370 relative = path[len(module):]
371 if relative.startswith('/'):
371 if relative.startswith('/'):
372 return relative[1:]
372 return relative[1:]
373 else:
373 else:
374 return relative
374 return relative
375
375
376 # The path is outside our tracked tree...
376 # The path is outside our tracked tree...
377 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
377 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
378 return None
378 return None
379
379
380 entries = []
380 entries = []
381 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
381 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
382 copies = {}
382 copies = {}
383 revnum = self.revnum(rev)
383 revnum = self.revnum(rev)
384
384
385 if revnum in self.modulemap:
385 if revnum in self.modulemap:
386 new_module = self.modulemap[revnum]
386 new_module = self.modulemap[revnum]
387 if new_module != self.module:
387 if new_module != self.module:
388 self.module = new_module
388 self.module = new_module
389 self.reparent(self.module)
389 self.reparent(self.module)
390
390
391 for path, ent in paths:
391 for path, ent in paths:
392 entrypath = get_entry_from_path(path, module=self.module)
392 entrypath = get_entry_from_path(path, module=self.module)
393 entry = entrypath.decode(self.encoding)
393 entry = entrypath.decode(self.encoding)
394
394
395 kind = svn.ra.check_path(self.ra, entrypath, revnum)
395 kind = svn.ra.check_path(self.ra, entrypath, revnum)
396 if kind == svn.core.svn_node_file:
396 if kind == svn.core.svn_node_file:
397 if ent.copyfrom_path:
397 if ent.copyfrom_path:
398 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
398 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
399 if copyfrom_path:
399 if copyfrom_path:
400 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
400 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
401 # It's probably important for hg that the source
401 # It's probably important for hg that the source
402 # exists in the revision's parent, not just the
402 # exists in the revision's parent, not just the
403 # ent.copyfrom_rev
403 # ent.copyfrom_rev
404 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
404 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
405 if fromkind != 0:
405 if fromkind != 0:
406 copies[self.recode(entry)] = self.recode(copyfrom_path)
406 copies[self.recode(entry)] = self.recode(copyfrom_path)
407 entries.append(self.recode(entry))
407 entries.append(self.recode(entry))
408 elif kind == 0: # gone, but had better be a deleted *file*
408 elif kind == 0: # gone, but had better be a deleted *file*
409 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
409 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
410
410
411 # if a branch is created but entries are removed in the same
411 # if a branch is created but entries are removed in the same
412 # changeset, get the right fromrev
412 # changeset, get the right fromrev
413 if parents:
413 if parents:
414 uuid, old_module, fromrev = self.revsplit(parents[0])
414 uuid, old_module, fromrev = self.revsplit(parents[0])
415 else:
415 else:
416 fromrev = revnum - 1
416 fromrev = revnum - 1
417 # might always need to be revnum - 1 in these 3 lines?
417 # might always need to be revnum - 1 in these 3 lines?
418 old_module = self.modulemap.get(fromrev, self.module)
418 old_module = self.modulemap.get(fromrev, self.module)
419
419
420 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
420 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
421 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
421 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
422
422
423 def lookup_parts(p):
423 def lookup_parts(p):
424 rc = None
424 rc = None
425 parts = p.split("/")
425 parts = p.split("/")
426 for i in range(len(parts)):
426 for i in range(len(parts)):
427 part = "/".join(parts[:i])
427 part = "/".join(parts[:i])
428 info = part, copyfrom.get(part, None)
428 info = part, copyfrom.get(part, None)
429 if info[1] is not None:
429 if info[1] is not None:
430 self.ui.debug("Found parent directory %s\n" % info[1])
430 self.ui.debug("Found parent directory %s\n" % info[1])
431 rc = info
431 rc = info
432 return rc
432 return rc
433
433
434 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
434 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
435
435
436 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
436 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
437
437
438 # need to remove fragment from lookup_parts and replace with copyfrom_path
438 # need to remove fragment from lookup_parts and replace with copyfrom_path
439 if frompath is not None:
439 if frompath is not None:
440 self.ui.debug("munge-o-matic\n")
440 self.ui.debug("munge-o-matic\n")
441 self.ui.debug(entrypath + '\n')
441 self.ui.debug(entrypath + '\n')
442 self.ui.debug(entrypath[len(frompath):] + '\n')
442 self.ui.debug(entrypath[len(frompath):] + '\n')
443 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
443 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
444 fromrev = froment.copyfrom_rev
444 fromrev = froment.copyfrom_rev
445 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
445 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
446
446
447 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
447 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
448 if fromkind == svn.core.svn_node_file: # a deleted file
448 if fromkind == svn.core.svn_node_file: # a deleted file
449 entries.append(self.recode(entry))
449 entries.append(self.recode(entry))
450 elif fromkind == svn.core.svn_node_dir:
450 elif fromkind == svn.core.svn_node_dir:
451 # print "Deleted/moved non-file:", revnum, path, ent
451 # print "Deleted/moved non-file:", revnum, path, ent
452 # children = self._find_children(path, revnum - 1)
452 # children = self._find_children(path, revnum - 1)
453 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
453 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
454 # Sometimes this is tricky. For example: in
454 # Sometimes this is tricky. For example: in
455 # The Subversion Repository revision 6940 a dir
455 # The Subversion Repository revision 6940 a dir
456 # was copied and one of its files was deleted
456 # was copied and one of its files was deleted
457 # from the new location in the same commit. This
457 # from the new location in the same commit. This
458 # code can't deal with that yet.
458 # code can't deal with that yet.
459 if ent.action == 'C':
459 if ent.action == 'C':
460 children = self._find_children(path, fromrev)
460 children = self._find_children(path, fromrev)
461 else:
461 else:
462 oroot = entrypath.strip('/')
462 oroot = entrypath.strip('/')
463 nroot = path.strip('/')
463 nroot = path.strip('/')
464 children = self._find_children(oroot, fromrev)
464 children = self._find_children(oroot, fromrev)
465 children = [s.replace(oroot,nroot) for s in children]
465 children = [s.replace(oroot,nroot) for s in children]
466 # Mark all [files, not directories] as deleted.
466 # Mark all [files, not directories] as deleted.
467 for child in children:
467 for child in children:
468 # Can we move a child directory and its
468 # Can we move a child directory and its
469 # parent in the same commit? (probably can). Could
469 # parent in the same commit? (probably can). Could
470 # cause problems if instead of revnum -1,
470 # cause problems if instead of revnum -1,
471 # we have to look in (copyfrom_path, revnum - 1)
471 # we have to look in (copyfrom_path, revnum - 1)
472 entrypath = get_entry_from_path("/" + child, module=old_module)
472 entrypath = get_entry_from_path("/" + child, module=old_module)
473 if entrypath:
473 if entrypath:
474 entry = self.recode(entrypath.decode(self.encoding))
474 entry = self.recode(entrypath.decode(self.encoding))
475 if entry in copies:
475 if entry in copies:
476 # deleted file within a copy
476 # deleted file within a copy
477 del copies[entry]
477 del copies[entry]
478 else:
478 else:
479 entries.append(entry)
479 entries.append(entry)
480 else:
480 else:
481 self.ui.debug('unknown path in revision %d: %s\n' % \
481 self.ui.debug('unknown path in revision %d: %s\n' % \
482 (revnum, path))
482 (revnum, path))
483 elif kind == svn.core.svn_node_dir:
483 elif kind == svn.core.svn_node_dir:
484 # Should probably synthesize normal file entries
484 # Should probably synthesize normal file entries
485 # and handle as above to clean up copy/rename handling.
485 # and handle as above to clean up copy/rename handling.
486
486
487 # If the directory just had a prop change,
487 # If the directory just had a prop change,
488 # then we shouldn't need to look for its children.
488 # then we shouldn't need to look for its children.
489 # Also this could create duplicate entries. Not sure
489 # Also this could create duplicate entries. Not sure
490 # whether this will matter. Maybe should make entries a set.
490 # whether this will matter. Maybe should make entries a set.
491 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
491 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
492 # This will fail if a directory was copied
492 # This will fail if a directory was copied
493 # from another branch and then some of its files
493 # from another branch and then some of its files
494 # were deleted in the same transaction.
494 # were deleted in the same transaction.
495 children = self._find_children(path, revnum)
495 children = self._find_children(path, revnum)
496 children.sort()
496 children.sort()
497 for child in children:
497 for child in children:
498 # Can we move a child directory and its
498 # Can we move a child directory and its
499 # parent in the same commit? (probably can). Could
499 # parent in the same commit? (probably can). Could
500 # cause problems if instead of revnum -1,
500 # cause problems if instead of revnum -1,
501 # we have to look in (copyfrom_path, revnum - 1)
501 # we have to look in (copyfrom_path, revnum - 1)
502 entrypath = get_entry_from_path("/" + child, module=self.module)
502 entrypath = get_entry_from_path("/" + child, module=self.module)
503 # print child, self.module, entrypath
503 # print child, self.module, entrypath
504 if entrypath:
504 if entrypath:
505 # Need to filter out directories here...
505 # Need to filter out directories here...
506 kind = svn.ra.check_path(self.ra, entrypath, revnum)
506 kind = svn.ra.check_path(self.ra, entrypath, revnum)
507 if kind != svn.core.svn_node_dir:
507 if kind != svn.core.svn_node_dir:
508 entries.append(self.recode(entrypath))
508 entries.append(self.recode(entrypath))
509
509
510 # Copies here (must copy all from source)
510 # Copies here (must copy all from source)
511 # Probably not a real problem for us if
511 # Probably not a real problem for us if
512 # source does not exist
512 # source does not exist
513
513
514 # Can do this with the copy command "hg copy"
514 # Can do this with the copy command "hg copy"
515 # if ent.copyfrom_path:
515 # if ent.copyfrom_path:
516 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
516 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
517 # module=self.module)
517 # module=self.module)
518 # copyto_entry = entrypath
518 # copyto_entry = entrypath
519 #
519 #
520 # print "copy directory", copyfrom_entry, 'to', copyto_entry
520 # print "copy directory", copyfrom_entry, 'to', copyto_entry
521 #
521 #
522 # copies.append((copyfrom_entry, copyto_entry))
522 # copies.append((copyfrom_entry, copyto_entry))
523
523
524 if ent.copyfrom_path:
524 if ent.copyfrom_path:
525 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
525 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
526 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
526 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
527 if copyfrom_entry:
527 if copyfrom_entry:
528 copyfrom[path] = ent
528 copyfrom[path] = ent
529 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
529 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
530
530
531 # Good, /probably/ a regular copy. Really should check
531 # Good, /probably/ a regular copy. Really should check
532 # to see whether the parent revision actually contains
532 # to see whether the parent revision actually contains
533 # the directory in question.
533 # the directory in question.
534 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
534 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
535 children.sort()
535 children.sort()
536 for child in children:
536 for child in children:
537 entrypath = get_entry_from_path("/" + child, module=self.module)
537 entrypath = get_entry_from_path("/" + child, module=self.module)
538 if entrypath:
538 if entrypath:
539 entry = entrypath.decode(self.encoding)
539 entry = entrypath.decode(self.encoding)
540 # print "COPY COPY From", copyfrom_entry, entry
540 # print "COPY COPY From", copyfrom_entry, entry
541 copyto_path = path + entry[len(copyfrom_entry):]
541 copyto_path = path + entry[len(copyfrom_entry):]
542 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
542 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
543 # print "COPY", entry, "COPY To", copyto_entry
543 # print "COPY", entry, "COPY To", copyto_entry
544 copies[self.recode(copyto_entry)] = self.recode(entry)
544 copies[self.recode(copyto_entry)] = self.recode(entry)
545 # copy from quux splort/quuxfile
545 # copy from quux splort/quuxfile
546
546
547 return (entries, copies)
547 return (entries, copies)
548
548
549 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
549 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
550 self.child_cset = None
550 self.child_cset = None
551 def parselogentry(orig_paths, revnum, author, date, message):
551 def parselogentry(orig_paths, revnum, author, date, message):
552 self.ui.debug("parsing revision %d (%d changes)\n" %
552 self.ui.debug("parsing revision %d (%d changes)\n" %
553 (revnum, len(orig_paths)))
553 (revnum, len(orig_paths)))
554
554
555 if revnum in self.modulemap:
555 if revnum in self.modulemap:
556 new_module = self.modulemap[revnum]
556 new_module = self.modulemap[revnum]
557 if new_module != self.module:
557 if new_module != self.module:
558 self.module = new_module
558 self.module = new_module
559 self.reparent(self.module)
559 self.reparent(self.module)
560
560
561 rev = self.revid(revnum)
561 rev = self.revid(revnum)
562 # branch log might return entries for a parent we already have
562 # branch log might return entries for a parent we already have
563 if (rev in self.commits or
563 if (rev in self.commits or
564 (revnum < self.lastrevs.get(self.module, 0))):
564 (revnum < self.lastrevs.get(self.module, 0))):
565 return
565 return
566
566
567 parents = []
567 parents = []
568 # check whether this revision is the start of a branch
568 # check whether this revision is the start of a branch
569 if self.module in orig_paths:
569 if self.module in orig_paths:
570 ent = orig_paths[self.module]
570 ent = orig_paths[self.module]
571 if ent.copyfrom_path:
571 if ent.copyfrom_path:
572 # ent.copyfrom_rev may not be the actual last revision
572 # ent.copyfrom_rev may not be the actual last revision
573 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
573 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
574 self.modulemap[prev] = ent.copyfrom_path
574 self.modulemap[prev] = ent.copyfrom_path
575 parents = [self.revid(prev, ent.copyfrom_path)]
575 parents = [self.revid(prev, ent.copyfrom_path)]
576 self.ui.note('found parent of branch %s at %d: %s\n' % \
576 self.ui.note('found parent of branch %s at %d: %s\n' % \
577 (self.module, prev, ent.copyfrom_path))
577 (self.module, prev, ent.copyfrom_path))
578 else:
578 else:
579 self.ui.debug("No copyfrom path, don't know what to do.\n")
579 self.ui.debug("No copyfrom path, don't know what to do.\n")
580
580
581 self.modulemap[revnum] = self.module # track backwards in time
581 self.modulemap[revnum] = self.module # track backwards in time
582
582
583 orig_paths = orig_paths.items()
583 orig_paths = orig_paths.items()
584 orig_paths.sort()
584 orig_paths.sort()
585 paths = []
585 paths = []
586 # filter out unrelated paths
586 # filter out unrelated paths
587 for path, ent in orig_paths:
587 for path, ent in orig_paths:
588 if not path.startswith(self.module):
588 if not path.startswith(self.module):
589 self.ui.debug("boring@%s: %s\n" % (revnum, path))
589 self.ui.debug("boring@%s: %s\n" % (revnum, path))
590 continue
590 continue
591 paths.append((path, ent))
591 paths.append((path, ent))
592
592
593 self.paths[rev] = (paths, parents)
593 self.paths[rev] = (paths, parents)
594
594
595 # Example SVN datetime. Includes microseconds.
595 # Example SVN datetime. Includes microseconds.
596 # ISO-8601 conformant
596 # ISO-8601 conformant
597 # '2007-01-04T17:35:00.902377Z'
597 # '2007-01-04T17:35:00.902377Z'
598 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
598 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
599
599
600 log = message and self.recode(message)
600 log = message and self.recode(message)
601 author = author and self.recode(author) or ''
601 author = author and self.recode(author) or ''
602 try:
602 try:
603 branch = self.module.split("/")[-1]
603 branch = self.module.split("/")[-1]
604 if branch == 'trunk':
604 if branch == 'trunk':
605 branch = ''
605 branch = ''
606 except IndexError:
606 except IndexError:
607 branch = None
607 branch = None
608
608
609 cset = commit(author=author,
609 cset = commit(author=author,
610 date=util.datestr(date),
610 date=util.datestr(date),
611 desc=log,
611 desc=log,
612 parents=parents,
612 parents=parents,
613 branch=branch,
613 branch=branch,
614 rev=rev.encode('utf-8'))
614 rev=rev.encode('utf-8'))
615
615
616 self.commits[rev] = cset
616 self.commits[rev] = cset
617 if self.child_cset and not self.child_cset.parents:
617 if self.child_cset and not self.child_cset.parents:
618 self.child_cset.parents = [rev]
618 self.child_cset.parents = [rev]
619 self.child_cset = cset
619 self.child_cset = cset
620
620
621 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
621 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
622 (self.module, from_revnum, to_revnum))
622 (self.module, from_revnum, to_revnum))
623
623
624 try:
624 try:
625 for entry in self.get_log([self.module], from_revnum, to_revnum):
625 for entry in self.get_log([self.module], from_revnum, to_revnum):
626 orig_paths, revnum, author, date, message = entry
626 orig_paths, revnum, author, date, message = entry
627 if self.is_blacklisted(revnum):
627 if self.is_blacklisted(revnum):
628 self.ui.note('skipping blacklisted revision %d\n' % revnum)
628 self.ui.note('skipping blacklisted revision %d\n' % revnum)
629 continue
629 continue
630 if orig_paths is None:
630 if orig_paths is None:
631 self.ui.debug('revision %d has no entries\n' % revnum)
631 self.ui.debug('revision %d has no entries\n' % revnum)
632 continue
632 continue
633 parselogentry(orig_paths, revnum, author, date, message)
633 parselogentry(orig_paths, revnum, author, date, message)
634 except SubversionException, (inst, num):
634 except SubversionException, (inst, num):
635 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
635 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
636 raise NoSuchRevision(branch=self,
636 raise NoSuchRevision(branch=self,
637 revision="Revision number %d" % to_revnum)
637 revision="Revision number %d" % to_revnum)
638 raise
638 raise
639
639
640 def _getfile(self, file, rev):
640 def _getfile(self, file, rev):
641 io = StringIO()
641 io = StringIO()
642 # TODO: ra.get_file transmits the whole file instead of diffs.
642 # TODO: ra.get_file transmits the whole file instead of diffs.
643 mode = ''
643 mode = ''
644 try:
644 try:
645 revnum = self.revnum(rev)
645 revnum = self.revnum(rev)
646 if self.module != self.modulemap[revnum]:
646 if self.module != self.modulemap[revnum]:
647 self.module = self.modulemap[revnum]
647 self.module = self.modulemap[revnum]
648 self.reparent(self.module)
648 self.reparent(self.module)
649 info = svn.ra.get_file(self.ra, file, revnum, io)
649 info = svn.ra.get_file(self.ra, file, revnum, io)
650 if isinstance(info, list):
650 if isinstance(info, list):
651 info = info[-1]
651 info = info[-1]
652 mode = ("svn:executable" in info) and 'x' or ''
652 mode = ("svn:executable" in info) and 'x' or ''
653 mode = ("svn:special" in info) and 'l' or mode
653 mode = ("svn:special" in info) and 'l' or mode
654 except SubversionException, e:
654 except SubversionException, e:
655 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
655 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
656 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
656 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
657 if e.apr_err in notfound: # File not found
657 if e.apr_err in notfound: # File not found
658 raise IOError()
658 raise IOError()
659 raise
659 raise
660 data = io.getvalue()
660 data = io.getvalue()
661 if mode == 'l':
661 if mode == 'l':
662 link_prefix = "link "
662 link_prefix = "link "
663 if data.startswith(link_prefix):
663 if data.startswith(link_prefix):
664 data = data[len(link_prefix):]
664 data = data[len(link_prefix):]
665 return data, mode
665 return data, mode
666
666
667 def _find_children(self, path, revnum):
667 def _find_children(self, path, revnum):
668 path = path.strip('/')
668 path = path.strip('/')
669 pool = Pool()
669 pool = Pool()
670 rpath = '/'.join([self.base, path]).strip('/')
670 rpath = '/'.join([self.base, path]).strip('/')
671 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
671 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
672
672
673 pre_revprop_change = '''#!/bin/sh
673 pre_revprop_change = '''#!/bin/sh
674
674
675 REPOS="$1"
675 REPOS="$1"
676 REV="$2"
676 REV="$2"
677 USER="$3"
677 USER="$3"
678 PROPNAME="$4"
678 PROPNAME="$4"
679 ACTION="$5"
679 ACTION="$5"
680
680
681 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
681 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
682 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
682 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
683 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
683 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
684
684
685 echo "Changing prohibited revision property" >&2
685 echo "Changing prohibited revision property" >&2
686 exit 1
686 exit 1
687 '''
687 '''
688
688
689 class svn_sink(converter_sink, commandline):
689 class svn_sink(converter_sink, commandline):
690 commit_re = re.compile(r'Committed revision (\d+).', re.M)
690 commit_re = re.compile(r'Committed revision (\d+).', re.M)
691
691
692 def prerun(self):
692 def prerun(self):
693 if self.wc:
693 if self.wc:
694 os.chdir(self.wc)
694 os.chdir(self.wc)
695
695
696 def postrun(self):
696 def postrun(self):
697 if self.wc:
697 if self.wc:
698 os.chdir(self.cwd)
698 os.chdir(self.cwd)
699
699
700 def join(self, name):
700 def join(self, name):
701 return os.path.join(self.wc, '.svn', name)
701 return os.path.join(self.wc, '.svn', name)
702
702
703 def revmapfile(self):
703 def revmapfile(self):
704 return self.join('hg-shamap')
704 return self.join('hg-shamap')
705
705
706 def authorfile(self):
706 def authorfile(self):
707 return self.join('hg-authormap')
707 return self.join('hg-authormap')
708
708
709 def __init__(self, ui, path):
709 def __init__(self, ui, path):
710 converter_sink.__init__(self, ui, path)
710 converter_sink.__init__(self, ui, path)
711 commandline.__init__(self, ui, 'svn')
711 commandline.__init__(self, ui, 'svn')
712 self.delete = []
712 self.delete = []
713 self.wc = None
713 self.wc = None
714 self.cwd = os.getcwd()
714 self.cwd = os.getcwd()
715
715
716 path = os.path.realpath(path)
716 path = os.path.realpath(path)
717
717
718 created = False
718 created = False
719 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
719 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
720 self.wc = path
720 self.wc = path
721 self.run0('update')
721 self.run0('update')
722 else:
722 else:
723 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
724
723 if os.path.isdir(os.path.dirname(path)):
725 if os.path.isdir(os.path.dirname(path)):
724 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
726 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
725 ui.status(_('initializing svn repo %r\n') %
727 ui.status(_('initializing svn repo %r\n') %
726 os.path.basename(path))
728 os.path.basename(path))
727 commandline(ui, 'svnadmin').run0('create', path)
729 commandline(ui, 'svnadmin').run0('create', path)
728 created = path
730 created = path
731 path = path.replace('\\', '/')
732 if not path.startswith('/'):
733 path = '/' + path
729 path = 'file://' + path
734 path = 'file://' + path
730 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
735
731 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
736 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
732 self.run0('checkout', path, wcpath)
737 self.run0('checkout', path, wcpath)
733
738
734 self.wc = wcpath
739 self.wc = wcpath
735 self.opener = util.opener(self.wc)
740 self.opener = util.opener(self.wc)
736 self.wopener = util.opener(self.wc)
741 self.wopener = util.opener(self.wc)
737 self.childmap = mapfile(ui, self.join('hg-childmap'))
742 self.childmap = mapfile(ui, self.join('hg-childmap'))
738
743
739 if created:
744 if created:
740 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
745 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
741 fp = open(hook, 'w')
746 fp = open(hook, 'w')
742 fp.write(pre_revprop_change)
747 fp.write(pre_revprop_change)
743 fp.close()
748 fp.close()
744 util.set_exec(hook, True)
749 util.set_exec(hook, True)
745
750
746 def wjoin(self, *names):
751 def wjoin(self, *names):
747 return os.path.join(self.wc, *names)
752 return os.path.join(self.wc, *names)
748
753
749 def putfile(self, filename, flags, data):
754 def putfile(self, filename, flags, data):
750 if 'l' in flags:
755 if 'l' in flags:
751 self.wopener.symlink(data, filename)
756 self.wopener.symlink(data, filename)
752 else:
757 else:
753 try:
758 try:
754 if os.path.islink(self.wjoin(filename)):
759 if os.path.islink(self.wjoin(filename)):
755 os.unlink(filename)
760 os.unlink(filename)
756 except OSError:
761 except OSError:
757 pass
762 pass
758 self.wopener(filename, 'w').write(data)
763 self.wopener(filename, 'w').write(data)
759 was_exec = util.is_exec(self.wjoin(filename))
764 was_exec = util.is_exec(self.wjoin(filename))
760 util.set_exec(self.wjoin(filename), 'x' in flags)
765 util.set_exec(self.wjoin(filename), 'x' in flags)
761 if was_exec:
766 if was_exec:
762 if 'x' not in flags:
767 if 'x' not in flags:
763 self.run0('propdel', 'svn:executable', filename)
768 self.run0('propdel', 'svn:executable', filename)
764 else:
769 else:
765 if 'x' in flags:
770 if 'x' in flags:
766 self.run0('propset', 'svn:executable', '*', filename)
771 self.run0('propset', 'svn:executable', '*', filename)
767
772
768 def delfile(self, name):
773 def delfile(self, name):
769 self.delete.append(name)
774 self.delete.append(name)
770
775
771 def copyfile(self, source, dest):
776 def copyfile(self, source, dest):
772 # SVN's copy command pukes if the destination file exists, but
777 # SVN's copy command pukes if the destination file exists, but
773 # our copyfile method expects to record a copy that has
778 # our copyfile method expects to record a copy that has
774 # already occurred. Cross the semantic gap.
779 # already occurred. Cross the semantic gap.
775 wdest = self.wjoin(dest)
780 wdest = self.wjoin(dest)
776 exists = os.path.exists(wdest)
781 exists = os.path.exists(wdest)
777 if exists:
782 if exists:
778 fd, tempname = tempfile.mkstemp(
783 fd, tempname = tempfile.mkstemp(
779 prefix='hg-copy-', dir=os.path.dirname(wdest))
784 prefix='hg-copy-', dir=os.path.dirname(wdest))
780 os.close(fd)
785 os.close(fd)
781 os.unlink(tempname)
786 os.unlink(tempname)
782 os.rename(wdest, tempname)
787 os.rename(wdest, tempname)
783 try:
788 try:
784 self.run0('copy', source, dest)
789 self.run0('copy', source, dest)
785 finally:
790 finally:
786 if exists:
791 if exists:
787 try:
792 try:
788 os.unlink(wdest)
793 os.unlink(wdest)
789 except OSError:
794 except OSError:
790 pass
795 pass
791 os.rename(tempname, wdest)
796 os.rename(tempname, wdest)
792
797
793 def dirs_of(self, files):
798 def dirs_of(self, files):
794 dirs = set()
799 dirs = set()
795 for f in files:
800 for f in files:
796 if os.path.isdir(self.wjoin(f)):
801 if os.path.isdir(self.wjoin(f)):
797 dirs.add(f)
802 dirs.add(f)
798 for i in strutil.rfindall(f, '/'):
803 for i in strutil.rfindall(f, '/'):
799 dirs.add(f[:i])
804 dirs.add(f[:i])
800 return dirs
805 return dirs
801
806
802 def add_files(self, files):
807 def add_files(self, files):
803 add_dirs = [d for d in self.dirs_of(files)
808 add_dirs = [d for d in self.dirs_of(files)
804 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
809 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
805 if add_dirs:
810 if add_dirs:
806 self.run('add', non_recursive=True, quiet=True, *add)
811 self.run('add', non_recursive=True, quiet=True, *add)
807 if files:
812 if files:
808 self.run('add', quiet=True, *files)
813 self.run('add', quiet=True, *files)
809 return files.union(add_dirs)
814 return files.union(add_dirs)
810
815
811 def tidy_dirs(self, names):
816 def tidy_dirs(self, names):
812 dirs = list(self.dirs_of(names))
817 dirs = list(self.dirs_of(names))
813 dirs.sort(reverse=True)
818 dirs.sort(reverse=True)
814 deleted = []
819 deleted = []
815 for d in dirs:
820 for d in dirs:
816 wd = self.wjoin(d)
821 wd = self.wjoin(d)
817 if os.listdir(wd) == '.svn':
822 if os.listdir(wd) == '.svn':
818 self.run0('delete', d)
823 self.run0('delete', d)
819 deleted.append(d)
824 deleted.append(d)
820 return deleted
825 return deleted
821
826
822 def addchild(self, parent, child):
827 def addchild(self, parent, child):
823 self.childmap[parent] = child
828 self.childmap[parent] = child
824
829
825 def putcommit(self, files, parents, commit):
830 def putcommit(self, files, parents, commit):
826 for parent in parents:
831 for parent in parents:
827 try:
832 try:
828 return self.childmap[parent]
833 return self.childmap[parent]
829 except KeyError:
834 except KeyError:
830 pass
835 pass
831 entries = set(self.delete)
836 entries = set(self.delete)
832 if self.delete:
837 if self.delete:
833 self.run0('delete', *self.delete)
838 self.run0('delete', *self.delete)
834 self.delete = []
839 self.delete = []
835 files = util.frozenset(files)
840 files = util.frozenset(files)
836 entries.update(self.add_files(files.difference(entries)))
841 entries.update(self.add_files(files.difference(entries)))
837 entries.update(self.tidy_dirs(entries))
842 entries.update(self.tidy_dirs(entries))
838 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
843 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
839 fp = os.fdopen(fd, 'w')
844 fp = os.fdopen(fd, 'w')
840 fp.write(commit.desc)
845 fp.write(commit.desc)
841 fp.close()
846 fp.close()
842 try:
847 try:
843 output = self.run0('commit',
848 output = self.run0('commit',
844 username=util.shortuser(commit.author),
849 username=util.shortuser(commit.author),
845 file=messagefile,
850 file=messagefile,
846 *list(entries))
851 *list(entries))
847 try:
852 try:
848 rev = self.commit_re.search(output).group(1)
853 rev = self.commit_re.search(output).group(1)
849 except AttributeError:
854 except AttributeError:
850 self.ui.warn(_('unexpected svn output:\n'))
855 self.ui.warn(_('unexpected svn output:\n'))
851 self.ui.warn(output)
856 self.ui.warn(output)
852 raise util.Abort(_('unable to cope with svn output'))
857 raise util.Abort(_('unable to cope with svn output'))
853 if commit.rev:
858 if commit.rev:
854 self.run('propset', 'hg:convert-rev', commit.rev,
859 self.run('propset', 'hg:convert-rev', commit.rev,
855 revprop=True, revision=rev)
860 revprop=True, revision=rev)
856 if commit.branch and commit.branch != 'default':
861 if commit.branch and commit.branch != 'default':
857 self.run('propset', 'hg:convert-branch', commit.branch,
862 self.run('propset', 'hg:convert-branch', commit.branch,
858 revprop=True, revision=rev)
863 revprop=True, revision=rev)
859 for parent in parents:
864 for parent in parents:
860 self.addchild(parent, rev)
865 self.addchild(parent, rev)
861 return rev
866 return rev
862 finally:
867 finally:
863 os.unlink(messagefile)
868 os.unlink(messagefile)
864
869
865 def puttags(self, tags):
870 def puttags(self, tags):
866 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
871 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
General Comments 0
You need to be logged in to leave comments. Login now