##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r7382:f5f6b7dc merge default
parent child Browse files
Show More
@@ -1,1163 +1,1172 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 import urllib
24 import urllib
25
25
26 from mercurial import strutil, util
26 from mercurial import strutil, util
27 from mercurial.i18n import _
27 from mercurial.i18n import _
28
28
29 # Subversion stuff. Works best with very recent Python SVN bindings
29 # Subversion stuff. Works best with very recent Python SVN bindings
30 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
30 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
31 # these bindings.
31 # these bindings.
32
32
33 from cStringIO import StringIO
33 from cStringIO import StringIO
34
34
35 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
35 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
36 from common import commandline, converter_sink, mapfile
36 from common import commandline, converter_sink, mapfile
37
37
38 try:
38 try:
39 from svn.core import SubversionException, Pool
39 from svn.core import SubversionException, Pool
40 import svn
40 import svn
41 import svn.client
41 import svn.client
42 import svn.core
42 import svn.core
43 import svn.ra
43 import svn.ra
44 import svn.delta
44 import svn.delta
45 import transport
45 import transport
46 except ImportError:
46 except ImportError:
47 pass
47 pass
48
48
49 class SvnPathNotFound(Exception):
50 pass
51
49 def geturl(path):
52 def geturl(path):
50 try:
53 try:
51 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
54 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
52 except SubversionException:
55 except SubversionException:
53 pass
56 pass
54 if os.path.isdir(path):
57 if os.path.isdir(path):
55 path = os.path.normpath(os.path.abspath(path))
58 path = os.path.normpath(os.path.abspath(path))
56 if os.name == 'nt':
59 if os.name == 'nt':
57 path = '/' + util.normpath(path)
60 path = '/' + util.normpath(path)
58 return 'file://%s' % urllib.quote(path)
61 return 'file://%s' % urllib.quote(path)
59 return path
62 return path
60
63
61 def optrev(number):
64 def optrev(number):
62 optrev = svn.core.svn_opt_revision_t()
65 optrev = svn.core.svn_opt_revision_t()
63 optrev.kind = svn.core.svn_opt_revision_number
66 optrev.kind = svn.core.svn_opt_revision_number
64 optrev.value.number = number
67 optrev.value.number = number
65 return optrev
68 return optrev
66
69
67 class changedpath(object):
70 class changedpath(object):
68 def __init__(self, p):
71 def __init__(self, p):
69 self.copyfrom_path = p.copyfrom_path
72 self.copyfrom_path = p.copyfrom_path
70 self.copyfrom_rev = p.copyfrom_rev
73 self.copyfrom_rev = p.copyfrom_rev
71 self.action = p.action
74 self.action = p.action
72
75
73 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
76 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
74 strict_node_history=False):
77 strict_node_history=False):
75 protocol = -1
78 protocol = -1
76 def receiver(orig_paths, revnum, author, date, message, pool):
79 def receiver(orig_paths, revnum, author, date, message, pool):
77 if orig_paths is not None:
80 if orig_paths is not None:
78 for k, v in orig_paths.iteritems():
81 for k, v in orig_paths.iteritems():
79 orig_paths[k] = changedpath(v)
82 orig_paths[k] = changedpath(v)
80 pickle.dump((orig_paths, revnum, author, date, message),
83 pickle.dump((orig_paths, revnum, author, date, message),
81 fp, protocol)
84 fp, protocol)
82
85
83 try:
86 try:
84 # Use an ra of our own so that our parent can consume
87 # Use an ra of our own so that our parent can consume
85 # our results without confusing the server.
88 # our results without confusing the server.
86 t = transport.SvnRaTransport(url=url)
89 t = transport.SvnRaTransport(url=url)
87 svn.ra.get_log(t.ra, paths, start, end, limit,
90 svn.ra.get_log(t.ra, paths, start, end, limit,
88 discover_changed_paths,
91 discover_changed_paths,
89 strict_node_history,
92 strict_node_history,
90 receiver)
93 receiver)
91 except SubversionException, (inst, num):
94 except SubversionException, (inst, num):
92 pickle.dump(num, fp, protocol)
95 pickle.dump(num, fp, protocol)
93 except IOError:
96 except IOError:
94 # Caller may interrupt the iteration
97 # Caller may interrupt the iteration
95 pickle.dump(None, fp, protocol)
98 pickle.dump(None, fp, protocol)
96 else:
99 else:
97 pickle.dump(None, fp, protocol)
100 pickle.dump(None, fp, protocol)
98 fp.close()
101 fp.close()
99 # With large history, cleanup process goes crazy and suddenly
102 # With large history, cleanup process goes crazy and suddenly
100 # consumes *huge* amount of memory. The output file being closed,
103 # consumes *huge* amount of memory. The output file being closed,
101 # there is no need for clean termination.
104 # there is no need for clean termination.
102 os._exit(0)
105 os._exit(0)
103
106
104 def debugsvnlog(ui, **opts):
107 def debugsvnlog(ui, **opts):
105 """Fetch SVN log in a subprocess and channel them back to parent to
108 """Fetch SVN log in a subprocess and channel them back to parent to
106 avoid memory collection issues.
109 avoid memory collection issues.
107 """
110 """
108 util.set_binary(sys.stdin)
111 util.set_binary(sys.stdin)
109 util.set_binary(sys.stdout)
112 util.set_binary(sys.stdout)
110 args = decodeargs(sys.stdin.read())
113 args = decodeargs(sys.stdin.read())
111 get_log_child(sys.stdout, *args)
114 get_log_child(sys.stdout, *args)
112
115
113 class logstream:
116 class logstream:
114 """Interruptible revision log iterator."""
117 """Interruptible revision log iterator."""
115 def __init__(self, stdout):
118 def __init__(self, stdout):
116 self._stdout = stdout
119 self._stdout = stdout
117
120
118 def __iter__(self):
121 def __iter__(self):
119 while True:
122 while True:
120 entry = pickle.load(self._stdout)
123 entry = pickle.load(self._stdout)
121 try:
124 try:
122 orig_paths, revnum, author, date, message = entry
125 orig_paths, revnum, author, date, message = entry
123 except:
126 except:
124 if entry is None:
127 if entry is None:
125 break
128 break
126 raise SubversionException("child raised exception", entry)
129 raise SubversionException("child raised exception", entry)
127 yield entry
130 yield entry
128
131
129 def close(self):
132 def close(self):
130 if self._stdout:
133 if self._stdout:
131 self._stdout.close()
134 self._stdout.close()
132 self._stdout = None
135 self._stdout = None
133
136
134 # SVN conversion code stolen from bzr-svn and tailor
137 # SVN conversion code stolen from bzr-svn and tailor
135 #
138 #
136 # Subversion looks like a versioned filesystem, branches structures
139 # Subversion looks like a versioned filesystem, branches structures
137 # are defined by conventions and not enforced by the tool. First,
140 # are defined by conventions and not enforced by the tool. First,
138 # we define the potential branches (modules) as "trunk" and "branches"
141 # we define the potential branches (modules) as "trunk" and "branches"
139 # children directories. Revisions are then identified by their
142 # children directories. Revisions are then identified by their
140 # module and revision number (and a repository identifier).
143 # module and revision number (and a repository identifier).
141 #
144 #
142 # The revision graph is really a tree (or a forest). By default, a
145 # The revision graph is really a tree (or a forest). By default, a
143 # revision parent is the previous revision in the same module. If the
146 # revision parent is the previous revision in the same module. If the
144 # module directory is copied/moved from another module then the
147 # module directory is copied/moved from another module then the
145 # revision is the module root and its parent the source revision in
148 # revision is the module root and its parent the source revision in
146 # the parent module. A revision has at most one parent.
149 # the parent module. A revision has at most one parent.
147 #
150 #
148 class svn_source(converter_source):
151 class svn_source(converter_source):
149 def __init__(self, ui, url, rev=None):
152 def __init__(self, ui, url, rev=None):
150 super(svn_source, self).__init__(ui, url, rev=rev)
153 super(svn_source, self).__init__(ui, url, rev=rev)
151
154
152 try:
155 try:
153 SubversionException
156 SubversionException
154 except NameError:
157 except NameError:
155 raise NoRepo('Subversion python bindings could not be loaded')
158 raise NoRepo('Subversion python bindings could not be loaded')
156
159
157 self.encoding = locale.getpreferredencoding()
160 self.encoding = locale.getpreferredencoding()
158 self.lastrevs = {}
161 self.lastrevs = {}
159
162
160 latest = None
163 latest = None
161 try:
164 try:
162 # Support file://path@rev syntax. Useful e.g. to convert
165 # Support file://path@rev syntax. Useful e.g. to convert
163 # deleted branches.
166 # deleted branches.
164 at = url.rfind('@')
167 at = url.rfind('@')
165 if at >= 0:
168 if at >= 0:
166 latest = int(url[at+1:])
169 latest = int(url[at+1:])
167 url = url[:at]
170 url = url[:at]
168 except ValueError, e:
171 except ValueError, e:
169 pass
172 pass
170 self.url = geturl(url)
173 self.url = geturl(url)
171 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
174 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
172 try:
175 try:
173 self.transport = transport.SvnRaTransport(url=self.url)
176 self.transport = transport.SvnRaTransport(url=self.url)
174 self.ra = self.transport.ra
177 self.ra = self.transport.ra
175 self.ctx = self.transport.client
178 self.ctx = self.transport.client
176 self.baseurl = svn.ra.get_repos_root(self.ra)
179 self.baseurl = svn.ra.get_repos_root(self.ra)
177 # Module is either empty or a repository path starting with
180 # Module is either empty or a repository path starting with
178 # a slash and not ending with a slash.
181 # a slash and not ending with a slash.
179 self.module = urllib.unquote(self.url[len(self.baseurl):])
182 self.module = urllib.unquote(self.url[len(self.baseurl):])
180 self.prevmodule = None
183 self.prevmodule = None
181 self.rootmodule = self.module
184 self.rootmodule = self.module
182 self.commits = {}
185 self.commits = {}
183 self.paths = {}
186 self.paths = {}
184 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
187 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
185 except SubversionException, e:
188 except SubversionException, e:
186 ui.print_exc()
189 ui.print_exc()
187 raise NoRepo("%s does not look like a Subversion repo" % self.url)
190 raise NoRepo("%s does not look like a Subversion repo" % self.url)
188
191
189 if rev:
192 if rev:
190 try:
193 try:
191 latest = int(rev)
194 latest = int(rev)
192 except ValueError:
195 except ValueError:
193 raise util.Abort(_('svn: revision %s is not an integer') % rev)
196 raise util.Abort(_('svn: revision %s is not an integer') % rev)
194
197
195 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
198 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
196 try:
199 try:
197 self.startrev = int(self.startrev)
200 self.startrev = int(self.startrev)
198 if self.startrev < 0:
201 if self.startrev < 0:
199 self.startrev = 0
202 self.startrev = 0
200 except ValueError:
203 except ValueError:
201 raise util.Abort(_('svn: start revision %s is not an integer')
204 raise util.Abort(_('svn: start revision %s is not an integer')
202 % self.startrev)
205 % self.startrev)
203
206
204 try:
207 try:
205 self.get_blacklist()
208 self.get_blacklist()
206 except IOError, e:
209 except IOError, e:
207 pass
210 pass
208
211
209 self.head = self.latest(self.module, latest)
212 self.head = self.latest(self.module, latest)
210 if not self.head:
213 if not self.head:
211 raise util.Abort(_('no revision found in module %s') %
214 raise util.Abort(_('no revision found in module %s') %
212 self.module.encode(self.encoding))
215 self.module.encode(self.encoding))
213 self.last_changed = self.revnum(self.head)
216 self.last_changed = self.revnum(self.head)
214
217
215 self._changescache = None
218 self._changescache = None
216
219
217 if os.path.exists(os.path.join(url, '.svn/entries')):
220 if os.path.exists(os.path.join(url, '.svn/entries')):
218 self.wc = url
221 self.wc = url
219 else:
222 else:
220 self.wc = None
223 self.wc = None
221 self.convertfp = None
224 self.convertfp = None
222
225
223 def setrevmap(self, revmap):
226 def setrevmap(self, revmap):
224 lastrevs = {}
227 lastrevs = {}
225 for revid in revmap.iterkeys():
228 for revid in revmap.iterkeys():
226 uuid, module, revnum = self.revsplit(revid)
229 uuid, module, revnum = self.revsplit(revid)
227 lastrevnum = lastrevs.setdefault(module, revnum)
230 lastrevnum = lastrevs.setdefault(module, revnum)
228 if revnum > lastrevnum:
231 if revnum > lastrevnum:
229 lastrevs[module] = revnum
232 lastrevs[module] = revnum
230 self.lastrevs = lastrevs
233 self.lastrevs = lastrevs
231
234
232 def exists(self, path, optrev):
235 def exists(self, path, optrev):
233 try:
236 try:
234 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
237 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
235 optrev, False, self.ctx)
238 optrev, False, self.ctx)
236 return True
239 return True
237 except SubversionException, err:
240 except SubversionException, err:
238 return False
241 return False
239
242
240 def getheads(self):
243 def getheads(self):
241
244
242 def isdir(path, revnum):
245 def isdir(path, revnum):
243 kind = self._checkpath(path, revnum)
246 kind = self._checkpath(path, revnum)
244 return kind == svn.core.svn_node_dir
247 return kind == svn.core.svn_node_dir
245
248
246 def getcfgpath(name, rev):
249 def getcfgpath(name, rev):
247 cfgpath = self.ui.config('convert', 'svn.' + name)
250 cfgpath = self.ui.config('convert', 'svn.' + name)
248 if cfgpath is not None and cfgpath.strip() == '':
251 if cfgpath is not None and cfgpath.strip() == '':
249 return None
252 return None
250 path = (cfgpath or name).strip('/')
253 path = (cfgpath or name).strip('/')
251 if not self.exists(path, rev):
254 if not self.exists(path, rev):
252 if cfgpath:
255 if cfgpath:
253 raise util.Abort(_('expected %s to be at %r, but not found')
256 raise util.Abort(_('expected %s to be at %r, but not found')
254 % (name, path))
257 % (name, path))
255 return None
258 return None
256 self.ui.note(_('found %s at %r\n') % (name, path))
259 self.ui.note(_('found %s at %r\n') % (name, path))
257 return path
260 return path
258
261
259 rev = optrev(self.last_changed)
262 rev = optrev(self.last_changed)
260 oldmodule = ''
263 oldmodule = ''
261 trunk = getcfgpath('trunk', rev)
264 trunk = getcfgpath('trunk', rev)
262 self.tags = getcfgpath('tags', rev)
265 self.tags = getcfgpath('tags', rev)
263 branches = getcfgpath('branches', rev)
266 branches = getcfgpath('branches', rev)
264
267
265 # If the project has a trunk or branches, we will extract heads
268 # If the project has a trunk or branches, we will extract heads
266 # from them. We keep the project root otherwise.
269 # from them. We keep the project root otherwise.
267 if trunk:
270 if trunk:
268 oldmodule = self.module or ''
271 oldmodule = self.module or ''
269 self.module += '/' + trunk
272 self.module += '/' + trunk
270 self.head = self.latest(self.module, self.last_changed)
273 self.head = self.latest(self.module, self.last_changed)
271 if not self.head:
274 if not self.head:
272 raise util.Abort(_('no revision found in module %s') %
275 raise util.Abort(_('no revision found in module %s') %
273 self.module.encode(self.encoding))
276 self.module.encode(self.encoding))
274
277
275 # First head in the list is the module's head
278 # First head in the list is the module's head
276 self.heads = [self.head]
279 self.heads = [self.head]
277 if self.tags is not None:
280 if self.tags is not None:
278 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
281 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
279
282
280 # Check if branches bring a few more heads to the list
283 # Check if branches bring a few more heads to the list
281 if branches:
284 if branches:
282 rpath = self.url.strip('/')
285 rpath = self.url.strip('/')
283 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
286 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
284 rev, False, self.ctx)
287 rev, False, self.ctx)
285 for branch in branchnames.keys():
288 for branch in branchnames.keys():
286 module = '%s/%s/%s' % (oldmodule, branches, branch)
289 module = '%s/%s/%s' % (oldmodule, branches, branch)
287 if not isdir(module, self.last_changed):
290 if not isdir(module, self.last_changed):
288 continue
291 continue
289 brevid = self.latest(module, self.last_changed)
292 brevid = self.latest(module, self.last_changed)
290 if not brevid:
293 if not brevid:
291 self.ui.note(_('ignoring empty branch %s\n') %
294 self.ui.note(_('ignoring empty branch %s\n') %
292 branch.encode(self.encoding))
295 branch.encode(self.encoding))
293 continue
296 continue
294 self.ui.note(_('found branch %s at %d\n') %
297 self.ui.note(_('found branch %s at %d\n') %
295 (branch, self.revnum(brevid)))
298 (branch, self.revnum(brevid)))
296 self.heads.append(brevid)
299 self.heads.append(brevid)
297
300
298 if self.startrev and self.heads:
301 if self.startrev and self.heads:
299 if len(self.heads) > 1:
302 if len(self.heads) > 1:
300 raise util.Abort(_('svn: start revision is not supported with '
303 raise util.Abort(_('svn: start revision is not supported with '
301 'with more than one branch'))
304 'with more than one branch'))
302 revnum = self.revnum(self.heads[0])
305 revnum = self.revnum(self.heads[0])
303 if revnum < self.startrev:
306 if revnum < self.startrev:
304 raise util.Abort(_('svn: no revision found after start revision %d')
307 raise util.Abort(_('svn: no revision found after start revision %d')
305 % self.startrev)
308 % self.startrev)
306
309
307 return self.heads
310 return self.heads
308
311
309 def getfile(self, file, rev):
312 def getfile(self, file, rev):
310 data, mode = self._getfile(file, rev)
313 data, mode = self._getfile(file, rev)
311 self.modecache[(file, rev)] = mode
314 self.modecache[(file, rev)] = mode
312 return data
315 return data
313
316
314 def getmode(self, file, rev):
317 def getmode(self, file, rev):
315 return self.modecache[(file, rev)]
318 return self.modecache[(file, rev)]
316
319
317 def getchanges(self, rev):
320 def getchanges(self, rev):
318 if self._changescache and self._changescache[0] == rev:
321 if self._changescache and self._changescache[0] == rev:
319 return self._changescache[1]
322 return self._changescache[1]
320 self._changescache = None
323 self._changescache = None
321 self.modecache = {}
324 self.modecache = {}
322 (paths, parents) = self.paths[rev]
325 (paths, parents) = self.paths[rev]
323 if parents:
326 if parents:
324 files, copies = self.expandpaths(rev, paths, parents)
327 files, copies = self.expandpaths(rev, paths, parents)
325 else:
328 else:
326 # Perform a full checkout on roots
329 # Perform a full checkout on roots
327 uuid, module, revnum = self.revsplit(rev)
330 uuid, module, revnum = self.revsplit(rev)
328 entries = svn.client.ls(self.baseurl + urllib.quote(module),
331 entries = svn.client.ls(self.baseurl + urllib.quote(module),
329 optrev(revnum), True, self.ctx)
332 optrev(revnum), True, self.ctx)
330 files = [n for n,e in entries.iteritems()
333 files = [n for n,e in entries.iteritems()
331 if e.kind == svn.core.svn_node_file]
334 if e.kind == svn.core.svn_node_file]
332 copies = {}
335 copies = {}
333
336
334 files.sort()
337 files.sort()
335 files = zip(files, [rev] * len(files))
338 files = zip(files, [rev] * len(files))
336
339
337 # caller caches the result, so free it here to release memory
340 # caller caches the result, so free it here to release memory
338 del self.paths[rev]
341 del self.paths[rev]
339 return (files, copies)
342 return (files, copies)
340
343
341 def getchangedfiles(self, rev, i):
344 def getchangedfiles(self, rev, i):
342 changes = self.getchanges(rev)
345 changes = self.getchanges(rev)
343 self._changescache = (rev, changes)
346 self._changescache = (rev, changes)
344 return [f[0] for f in changes[0]]
347 return [f[0] for f in changes[0]]
345
348
346 def getcommit(self, rev):
349 def getcommit(self, rev):
347 if rev not in self.commits:
350 if rev not in self.commits:
348 uuid, module, revnum = self.revsplit(rev)
351 uuid, module, revnum = self.revsplit(rev)
349 self.module = module
352 self.module = module
350 self.reparent(module)
353 self.reparent(module)
351 # We assume that:
354 # We assume that:
352 # - requests for revisions after "stop" come from the
355 # - requests for revisions after "stop" come from the
353 # revision graph backward traversal. Cache all of them
356 # revision graph backward traversal. Cache all of them
354 # down to stop, they will be used eventually.
357 # down to stop, they will be used eventually.
355 # - requests for revisions before "stop" come to get
358 # - requests for revisions before "stop" come to get
356 # isolated branches parents. Just fetch what is needed.
359 # isolated branches parents. Just fetch what is needed.
357 stop = self.lastrevs.get(module, 0)
360 stop = self.lastrevs.get(module, 0)
358 if revnum < stop:
361 if revnum < stop:
359 stop = revnum + 1
362 stop = revnum + 1
360 self._fetch_revisions(revnum, stop)
363 self._fetch_revisions(revnum, stop)
361 commit = self.commits[rev]
364 commit = self.commits[rev]
362 # caller caches the result, so free it here to release memory
365 # caller caches the result, so free it here to release memory
363 del self.commits[rev]
366 del self.commits[rev]
364 return commit
367 return commit
365
368
366 def gettags(self):
369 def gettags(self):
367 tags = {}
370 tags = {}
368 if self.tags is None:
371 if self.tags is None:
369 return tags
372 return tags
370
373
371 # svn tags are just a convention, project branches left in a
374 # svn tags are just a convention, project branches left in a
372 # 'tags' directory. There is no other relationship than
375 # 'tags' directory. There is no other relationship than
373 # ancestry, which is expensive to discover and makes them hard
376 # ancestry, which is expensive to discover and makes them hard
374 # to update incrementally. Worse, past revisions may be
377 # to update incrementally. Worse, past revisions may be
375 # referenced by tags far away in the future, requiring a deep
378 # referenced by tags far away in the future, requiring a deep
376 # history traversal on every calculation. Current code
379 # history traversal on every calculation. Current code
377 # performs a single backward traversal, tracking moves within
380 # performs a single backward traversal, tracking moves within
378 # the tags directory (tag renaming) and recording a new tag
381 # the tags directory (tag renaming) and recording a new tag
379 # everytime a project is copied from outside the tags
382 # everytime a project is copied from outside the tags
380 # directory. It also lists deleted tags, this behaviour may
383 # directory. It also lists deleted tags, this behaviour may
381 # change in the future.
384 # change in the future.
382 pendings = []
385 pendings = []
383 tagspath = self.tags
386 tagspath = self.tags
384 start = svn.ra.get_latest_revnum(self.ra)
387 start = svn.ra.get_latest_revnum(self.ra)
385 try:
388 try:
386 for entry in self._getlog([self.tags], start, self.startrev):
389 for entry in self._getlog([self.tags], start, self.startrev):
387 origpaths, revnum, author, date, message = entry
390 origpaths, revnum, author, date, message = entry
388 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
391 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
389 in origpaths.iteritems() if e.copyfrom_path]
392 in origpaths.iteritems() if e.copyfrom_path]
390 copies.sort()
393 copies.sort()
391 # Apply moves/copies from more specific to general
394 # Apply moves/copies from more specific to general
392 copies.reverse()
395 copies.reverse()
393
396
394 srctagspath = tagspath
397 srctagspath = tagspath
395 if copies and copies[-1][2] == tagspath:
398 if copies and copies[-1][2] == tagspath:
396 # Track tags directory moves
399 # Track tags directory moves
397 srctagspath = copies.pop()[0]
400 srctagspath = copies.pop()[0]
398
401
399 for source, sourcerev, dest in copies:
402 for source, sourcerev, dest in copies:
400 if not dest.startswith(tagspath + '/'):
403 if not dest.startswith(tagspath + '/'):
401 continue
404 continue
402 for tag in pendings:
405 for tag in pendings:
403 if tag[0].startswith(dest):
406 if tag[0].startswith(dest):
404 tagpath = source + tag[0][len(dest):]
407 tagpath = source + tag[0][len(dest):]
405 tag[:2] = [tagpath, sourcerev]
408 tag[:2] = [tagpath, sourcerev]
406 break
409 break
407 else:
410 else:
408 pendings.append([source, sourcerev, dest.split('/')[-1]])
411 pendings.append([source, sourcerev, dest.split('/')[-1]])
409
412
410 # Tell tag renamings from tag creations
413 # Tell tag renamings from tag creations
411 remainings = []
414 remainings = []
412 for source, sourcerev, tagname in pendings:
415 for source, sourcerev, tagname in pendings:
413 if source.startswith(srctagspath):
416 if source.startswith(srctagspath):
414 remainings.append([source, sourcerev, tagname])
417 remainings.append([source, sourcerev, tagname])
415 continue
418 continue
416 # From revision may be fake, get one with changes
419 # From revision may be fake, get one with changes
420 try:
417 tagid = self.latest(source, sourcerev)
421 tagid = self.latest(source, sourcerev)
418 if tagid:
422 if tagid:
419 tags[tagname] = tagid
423 tags[tagname] = tagid
424 except SvnPathNotFound:
425 # It happens when we are following directories we assumed
426 # were copied with their parents but were really created
427 # in the tag directory.
428 pass
420 pendings = remainings
429 pendings = remainings
421 tagspath = srctagspath
430 tagspath = srctagspath
422
431
423 except SubversionException, (inst, num):
432 except SubversionException, (inst, num):
424 self.ui.note(_('no tags found at revision %d\n') % start)
433 self.ui.note(_('no tags found at revision %d\n') % start)
425 return tags
434 return tags
426
435
427 def converted(self, rev, destrev):
436 def converted(self, rev, destrev):
428 if not self.wc:
437 if not self.wc:
429 return
438 return
430 if self.convertfp is None:
439 if self.convertfp is None:
431 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
440 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
432 'a')
441 'a')
433 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
442 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
434 self.convertfp.flush()
443 self.convertfp.flush()
435
444
436 # -- helper functions --
445 # -- helper functions --
437
446
438 def revid(self, revnum, module=None):
447 def revid(self, revnum, module=None):
439 if not module:
448 if not module:
440 module = self.module
449 module = self.module
441 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
450 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
442 revnum)
451 revnum)
443
452
444 def revnum(self, rev):
453 def revnum(self, rev):
445 return int(rev.split('@')[-1])
454 return int(rev.split('@')[-1])
446
455
447 def revsplit(self, rev):
456 def revsplit(self, rev):
448 url, revnum = rev.encode(self.encoding).split('@', 1)
457 url, revnum = rev.encode(self.encoding).split('@', 1)
449 revnum = int(revnum)
458 revnum = int(revnum)
450 parts = url.split('/', 1)
459 parts = url.split('/', 1)
451 uuid = parts.pop(0)[4:]
460 uuid = parts.pop(0)[4:]
452 mod = ''
461 mod = ''
453 if parts:
462 if parts:
454 mod = '/' + parts[0]
463 mod = '/' + parts[0]
455 return uuid, mod, revnum
464 return uuid, mod, revnum
456
465
457 def latest(self, path, stop=0):
466 def latest(self, path, stop=0):
458 """Find the latest revid affecting path, up to stop. It may return
467 """Find the latest revid affecting path, up to stop. It may return
459 a revision in a different module, since a branch may be moved without
468 a revision in a different module, since a branch may be moved without
460 a change being reported. Return None if computed module does not
469 a change being reported. Return None if computed module does not
461 belong to rootmodule subtree.
470 belong to rootmodule subtree.
462 """
471 """
463 if not path.startswith(self.rootmodule):
472 if not path.startswith(self.rootmodule):
464 # Requests on foreign branches may be forbidden at server level
473 # Requests on foreign branches may be forbidden at server level
465 self.ui.debug(_('ignoring foreign branch %r\n') % path)
474 self.ui.debug(_('ignoring foreign branch %r\n') % path)
466 return None
475 return None
467
476
468 if not stop:
477 if not stop:
469 stop = svn.ra.get_latest_revnum(self.ra)
478 stop = svn.ra.get_latest_revnum(self.ra)
470 try:
479 try:
471 prevmodule = self.reparent('')
480 prevmodule = self.reparent('')
472 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
481 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
473 self.reparent(prevmodule)
482 self.reparent(prevmodule)
474 except SubversionException:
483 except SubversionException:
475 dirent = None
484 dirent = None
476 if not dirent:
485 if not dirent:
477 raise util.Abort(_('%s not found up to revision %d') % (path, stop))
486 raise SvnPathNotFound(_('%s not found up to revision %d') % (path, stop))
478
487
479 # stat() gives us the previous revision on this line of development, but
488 # stat() gives us the previous revision on this line of development, but
480 # it might be in *another module*. Fetch the log and detect renames down
489 # it might be in *another module*. Fetch the log and detect renames down
481 # to the latest revision.
490 # to the latest revision.
482 stream = self._getlog([path], stop, dirent.created_rev)
491 stream = self._getlog([path], stop, dirent.created_rev)
483 try:
492 try:
484 for entry in stream:
493 for entry in stream:
485 paths, revnum, author, date, message = entry
494 paths, revnum, author, date, message = entry
486 if revnum <= dirent.created_rev:
495 if revnum <= dirent.created_rev:
487 break
496 break
488
497
489 for p in paths:
498 for p in paths:
490 if not path.startswith(p) or not paths[p].copyfrom_path:
499 if not path.startswith(p) or not paths[p].copyfrom_path:
491 continue
500 continue
492 newpath = paths[p].copyfrom_path + path[len(p):]
501 newpath = paths[p].copyfrom_path + path[len(p):]
493 self.ui.debug(_("branch renamed from %s to %s at %d\n") %
502 self.ui.debug(_("branch renamed from %s to %s at %d\n") %
494 (path, newpath, revnum))
503 (path, newpath, revnum))
495 path = newpath
504 path = newpath
496 break
505 break
497 finally:
506 finally:
498 stream.close()
507 stream.close()
499
508
500 if not path.startswith(self.rootmodule):
509 if not path.startswith(self.rootmodule):
501 self.ui.debug(_('ignoring foreign branch %r\n') % path)
510 self.ui.debug(_('ignoring foreign branch %r\n') % path)
502 return None
511 return None
503 return self.revid(dirent.created_rev, path)
512 return self.revid(dirent.created_rev, path)
504
513
505 def get_blacklist(self):
514 def get_blacklist(self):
506 """Avoid certain revision numbers.
515 """Avoid certain revision numbers.
507 It is not uncommon for two nearby revisions to cancel each other
516 It is not uncommon for two nearby revisions to cancel each other
508 out, e.g. 'I copied trunk into a subdirectory of itself instead
517 out, e.g. 'I copied trunk into a subdirectory of itself instead
509 of making a branch'. The converted repository is significantly
518 of making a branch'. The converted repository is significantly
510 smaller if we ignore such revisions."""
519 smaller if we ignore such revisions."""
511 self.blacklist = util.set()
520 self.blacklist = util.set()
512 blacklist = self.blacklist
521 blacklist = self.blacklist
513 for line in file("blacklist.txt", "r"):
522 for line in file("blacklist.txt", "r"):
514 if not line.startswith("#"):
523 if not line.startswith("#"):
515 try:
524 try:
516 svn_rev = int(line.strip())
525 svn_rev = int(line.strip())
517 blacklist.add(svn_rev)
526 blacklist.add(svn_rev)
518 except ValueError, e:
527 except ValueError, e:
519 pass # not an integer or a comment
528 pass # not an integer or a comment
520
529
521 def is_blacklisted(self, svn_rev):
530 def is_blacklisted(self, svn_rev):
522 return svn_rev in self.blacklist
531 return svn_rev in self.blacklist
523
532
524 def reparent(self, module):
533 def reparent(self, module):
525 """Reparent the svn transport and return the previous parent."""
534 """Reparent the svn transport and return the previous parent."""
526 if self.prevmodule == module:
535 if self.prevmodule == module:
527 return module
536 return module
528 svnurl = self.baseurl + urllib.quote(module)
537 svnurl = self.baseurl + urllib.quote(module)
529 prevmodule = self.prevmodule
538 prevmodule = self.prevmodule
530 if prevmodule is None:
539 if prevmodule is None:
531 prevmodule = ''
540 prevmodule = ''
532 self.ui.debug(_("reparent to %s\n") % svnurl)
541 self.ui.debug(_("reparent to %s\n") % svnurl)
533 svn.ra.reparent(self.ra, svnurl)
542 svn.ra.reparent(self.ra, svnurl)
534 self.prevmodule = module
543 self.prevmodule = module
535 return prevmodule
544 return prevmodule
536
545
537 def expandpaths(self, rev, paths, parents):
546 def expandpaths(self, rev, paths, parents):
538 entries = []
547 entries = []
539 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
548 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
540 copies = {}
549 copies = {}
541
550
542 new_module, revnum = self.revsplit(rev)[1:]
551 new_module, revnum = self.revsplit(rev)[1:]
543 if new_module != self.module:
552 if new_module != self.module:
544 self.module = new_module
553 self.module = new_module
545 self.reparent(self.module)
554 self.reparent(self.module)
546
555
547 for path, ent in paths:
556 for path, ent in paths:
548 entrypath = self.getrelpath(path)
557 entrypath = self.getrelpath(path)
549 entry = entrypath.decode(self.encoding)
558 entry = entrypath.decode(self.encoding)
550
559
551 kind = self._checkpath(entrypath, revnum)
560 kind = self._checkpath(entrypath, revnum)
552 if kind == svn.core.svn_node_file:
561 if kind == svn.core.svn_node_file:
553 entries.append(self.recode(entry))
562 entries.append(self.recode(entry))
554 if not ent.copyfrom_path or not parents:
563 if not ent.copyfrom_path or not parents:
555 continue
564 continue
556 # Copy sources not in parent revisions cannot be represented,
565 # Copy sources not in parent revisions cannot be represented,
557 # ignore their origin for now
566 # ignore their origin for now
558 pmodule, prevnum = self.revsplit(parents[0])[1:]
567 pmodule, prevnum = self.revsplit(parents[0])[1:]
559 if ent.copyfrom_rev < prevnum:
568 if ent.copyfrom_rev < prevnum:
560 continue
569 continue
561 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
570 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
562 if not copyfrom_path:
571 if not copyfrom_path:
563 continue
572 continue
564 self.ui.debug(_("copied to %s from %s@%s\n") %
573 self.ui.debug(_("copied to %s from %s@%s\n") %
565 (entrypath, copyfrom_path, ent.copyfrom_rev))
574 (entrypath, copyfrom_path, ent.copyfrom_rev))
566 copies[self.recode(entry)] = self.recode(copyfrom_path)
575 copies[self.recode(entry)] = self.recode(copyfrom_path)
567 elif kind == 0: # gone, but had better be a deleted *file*
576 elif kind == 0: # gone, but had better be a deleted *file*
568 self.ui.debug(_("gone from %s\n") % ent.copyfrom_rev)
577 self.ui.debug(_("gone from %s\n") % ent.copyfrom_rev)
569
578
570 # if a branch is created but entries are removed in the same
579 # if a branch is created but entries are removed in the same
571 # changeset, get the right fromrev
580 # changeset, get the right fromrev
572 # parents cannot be empty here, you cannot remove things from
581 # parents cannot be empty here, you cannot remove things from
573 # a root revision.
582 # a root revision.
574 uuid, old_module, fromrev = self.revsplit(parents[0])
583 uuid, old_module, fromrev = self.revsplit(parents[0])
575
584
576 basepath = old_module + "/" + self.getrelpath(path)
585 basepath = old_module + "/" + self.getrelpath(path)
577 entrypath = basepath
586 entrypath = basepath
578
587
579 def lookup_parts(p):
588 def lookup_parts(p):
580 rc = None
589 rc = None
581 parts = p.split("/")
590 parts = p.split("/")
582 for i in range(len(parts)):
591 for i in range(len(parts)):
583 part = "/".join(parts[:i])
592 part = "/".join(parts[:i])
584 info = part, copyfrom.get(part, None)
593 info = part, copyfrom.get(part, None)
585 if info[1] is not None:
594 if info[1] is not None:
586 self.ui.debug(_("Found parent directory %s\n") % info[1])
595 self.ui.debug(_("Found parent directory %s\n") % info[1])
587 rc = info
596 rc = info
588 return rc
597 return rc
589
598
590 self.ui.debug(_("base, entry %s %s\n") % (basepath, entrypath))
599 self.ui.debug(_("base, entry %s %s\n") % (basepath, entrypath))
591
600
592 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
601 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
593
602
594 # need to remove fragment from lookup_parts and replace with copyfrom_path
603 # need to remove fragment from lookup_parts and replace with copyfrom_path
595 if frompath is not None:
604 if frompath is not None:
596 self.ui.debug(_("munge-o-matic\n"))
605 self.ui.debug(_("munge-o-matic\n"))
597 self.ui.debug(entrypath + '\n')
606 self.ui.debug(entrypath + '\n')
598 self.ui.debug(entrypath[len(frompath):] + '\n')
607 self.ui.debug(entrypath[len(frompath):] + '\n')
599 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
608 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
600 fromrev = froment.copyfrom_rev
609 fromrev = froment.copyfrom_rev
601 self.ui.debug(_("Info: %s %s %s %s\n") % (frompath, froment, ent, entrypath))
610 self.ui.debug(_("Info: %s %s %s %s\n") % (frompath, froment, ent, entrypath))
602
611
603 # We can avoid the reparent calls if the module has not changed
612 # We can avoid the reparent calls if the module has not changed
604 # but it probably does not worth the pain.
613 # but it probably does not worth the pain.
605 prevmodule = self.reparent('')
614 prevmodule = self.reparent('')
606 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
615 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
607 self.reparent(prevmodule)
616 self.reparent(prevmodule)
608
617
609 if fromkind == svn.core.svn_node_file: # a deleted file
618 if fromkind == svn.core.svn_node_file: # a deleted file
610 entries.append(self.recode(entry))
619 entries.append(self.recode(entry))
611 elif fromkind == svn.core.svn_node_dir:
620 elif fromkind == svn.core.svn_node_dir:
612 # print "Deleted/moved non-file:", revnum, path, ent
621 # print "Deleted/moved non-file:", revnum, path, ent
613 # children = self._find_children(path, revnum - 1)
622 # children = self._find_children(path, revnum - 1)
614 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
623 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
615 # Sometimes this is tricky. For example: in
624 # Sometimes this is tricky. For example: in
616 # The Subversion Repository revision 6940 a dir
625 # The Subversion Repository revision 6940 a dir
617 # was copied and one of its files was deleted
626 # was copied and one of its files was deleted
618 # from the new location in the same commit. This
627 # from the new location in the same commit. This
619 # code can't deal with that yet.
628 # code can't deal with that yet.
620 if ent.action == 'C':
629 if ent.action == 'C':
621 children = self._find_children(path, fromrev)
630 children = self._find_children(path, fromrev)
622 else:
631 else:
623 oroot = entrypath.strip('/')
632 oroot = entrypath.strip('/')
624 nroot = path.strip('/')
633 nroot = path.strip('/')
625 children = self._find_children(oroot, fromrev)
634 children = self._find_children(oroot, fromrev)
626 children = [s.replace(oroot,nroot) for s in children]
635 children = [s.replace(oroot,nroot) for s in children]
627 # Mark all [files, not directories] as deleted.
636 # Mark all [files, not directories] as deleted.
628 for child in children:
637 for child in children:
629 # Can we move a child directory and its
638 # Can we move a child directory and its
630 # parent in the same commit? (probably can). Could
639 # parent in the same commit? (probably can). Could
631 # cause problems if instead of revnum -1,
640 # cause problems if instead of revnum -1,
632 # we have to look in (copyfrom_path, revnum - 1)
641 # we have to look in (copyfrom_path, revnum - 1)
633 entrypath = self.getrelpath("/" + child, module=old_module)
642 entrypath = self.getrelpath("/" + child, module=old_module)
634 if entrypath:
643 if entrypath:
635 entry = self.recode(entrypath.decode(self.encoding))
644 entry = self.recode(entrypath.decode(self.encoding))
636 if entry in copies:
645 if entry in copies:
637 # deleted file within a copy
646 # deleted file within a copy
638 del copies[entry]
647 del copies[entry]
639 else:
648 else:
640 entries.append(entry)
649 entries.append(entry)
641 else:
650 else:
642 self.ui.debug(_('unknown path in revision %d: %s\n') % \
651 self.ui.debug(_('unknown path in revision %d: %s\n') % \
643 (revnum, path))
652 (revnum, path))
644 elif kind == svn.core.svn_node_dir:
653 elif kind == svn.core.svn_node_dir:
645 # Should probably synthesize normal file entries
654 # Should probably synthesize normal file entries
646 # and handle as above to clean up copy/rename handling.
655 # and handle as above to clean up copy/rename handling.
647
656
648 # If the directory just had a prop change,
657 # If the directory just had a prop change,
649 # then we shouldn't need to look for its children.
658 # then we shouldn't need to look for its children.
650 if ent.action == 'M':
659 if ent.action == 'M':
651 continue
660 continue
652
661
653 # Also this could create duplicate entries. Not sure
662 # Also this could create duplicate entries. Not sure
654 # whether this will matter. Maybe should make entries a set.
663 # whether this will matter. Maybe should make entries a set.
655 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
664 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
656 # This will fail if a directory was copied
665 # This will fail if a directory was copied
657 # from another branch and then some of its files
666 # from another branch and then some of its files
658 # were deleted in the same transaction.
667 # were deleted in the same transaction.
659 children = util.sort(self._find_children(path, revnum))
668 children = util.sort(self._find_children(path, revnum))
660 for child in children:
669 for child in children:
661 # Can we move a child directory and its
670 # Can we move a child directory and its
662 # parent in the same commit? (probably can). Could
671 # parent in the same commit? (probably can). Could
663 # cause problems if instead of revnum -1,
672 # cause problems if instead of revnum -1,
664 # we have to look in (copyfrom_path, revnum - 1)
673 # we have to look in (copyfrom_path, revnum - 1)
665 entrypath = self.getrelpath("/" + child)
674 entrypath = self.getrelpath("/" + child)
666 # print child, self.module, entrypath
675 # print child, self.module, entrypath
667 if entrypath:
676 if entrypath:
668 # Need to filter out directories here...
677 # Need to filter out directories here...
669 kind = self._checkpath(entrypath, revnum)
678 kind = self._checkpath(entrypath, revnum)
670 if kind != svn.core.svn_node_dir:
679 if kind != svn.core.svn_node_dir:
671 entries.append(self.recode(entrypath))
680 entries.append(self.recode(entrypath))
672
681
673 # Copies here (must copy all from source)
682 # Copies here (must copy all from source)
674 # Probably not a real problem for us if
683 # Probably not a real problem for us if
675 # source does not exist
684 # source does not exist
676 if not ent.copyfrom_path or not parents:
685 if not ent.copyfrom_path or not parents:
677 continue
686 continue
678 # Copy sources not in parent revisions cannot be represented,
687 # Copy sources not in parent revisions cannot be represented,
679 # ignore their origin for now
688 # ignore their origin for now
680 pmodule, prevnum = self.revsplit(parents[0])[1:]
689 pmodule, prevnum = self.revsplit(parents[0])[1:]
681 if ent.copyfrom_rev < prevnum:
690 if ent.copyfrom_rev < prevnum:
682 continue
691 continue
683 copyfrompath = ent.copyfrom_path.decode(self.encoding)
692 copyfrompath = ent.copyfrom_path.decode(self.encoding)
684 copyfrompath = self.getrelpath(copyfrompath, pmodule)
693 copyfrompath = self.getrelpath(copyfrompath, pmodule)
685 if not copyfrompath:
694 if not copyfrompath:
686 continue
695 continue
687 copyfrom[path] = ent
696 copyfrom[path] = ent
688 self.ui.debug(_("mark %s came from %s:%d\n")
697 self.ui.debug(_("mark %s came from %s:%d\n")
689 % (path, copyfrompath, ent.copyfrom_rev))
698 % (path, copyfrompath, ent.copyfrom_rev))
690 children = self._find_children(ent.copyfrom_path, ent.copyfrom_rev)
699 children = self._find_children(ent.copyfrom_path, ent.copyfrom_rev)
691 children.sort()
700 children.sort()
692 for child in children:
701 for child in children:
693 entrypath = self.getrelpath("/" + child, pmodule)
702 entrypath = self.getrelpath("/" + child, pmodule)
694 if not entrypath:
703 if not entrypath:
695 continue
704 continue
696 entry = entrypath.decode(self.encoding)
705 entry = entrypath.decode(self.encoding)
697 copytopath = path + entry[len(copyfrompath):]
706 copytopath = path + entry[len(copyfrompath):]
698 copytopath = self.getrelpath(copytopath)
707 copytopath = self.getrelpath(copytopath)
699 copies[self.recode(copytopath)] = self.recode(entry, pmodule)
708 copies[self.recode(copytopath)] = self.recode(entry, pmodule)
700
709
701 return (util.unique(entries), copies)
710 return (util.unique(entries), copies)
702
711
703 def _fetch_revisions(self, from_revnum, to_revnum):
712 def _fetch_revisions(self, from_revnum, to_revnum):
704 if from_revnum < to_revnum:
713 if from_revnum < to_revnum:
705 from_revnum, to_revnum = to_revnum, from_revnum
714 from_revnum, to_revnum = to_revnum, from_revnum
706
715
707 self.child_cset = None
716 self.child_cset = None
708
717
709 def isdescendantof(parent, child):
718 def isdescendantof(parent, child):
710 if not child or not parent or not child.startswith(parent):
719 if not child or not parent or not child.startswith(parent):
711 return False
720 return False
712 subpath = child[len(parent):]
721 subpath = child[len(parent):]
713 return len(subpath) > 1 and subpath[0] == '/'
722 return len(subpath) > 1 and subpath[0] == '/'
714
723
715 def parselogentry(orig_paths, revnum, author, date, message):
724 def parselogentry(orig_paths, revnum, author, date, message):
716 """Return the parsed commit object or None, and True if
725 """Return the parsed commit object or None, and True if
717 the revision is a branch root.
726 the revision is a branch root.
718 """
727 """
719 self.ui.debug(_("parsing revision %d (%d changes)\n") %
728 self.ui.debug(_("parsing revision %d (%d changes)\n") %
720 (revnum, len(orig_paths)))
729 (revnum, len(orig_paths)))
721
730
722 branched = False
731 branched = False
723 rev = self.revid(revnum)
732 rev = self.revid(revnum)
724 # branch log might return entries for a parent we already have
733 # branch log might return entries for a parent we already have
725
734
726 if (rev in self.commits or revnum < to_revnum):
735 if (rev in self.commits or revnum < to_revnum):
727 return None, branched
736 return None, branched
728
737
729 parents = []
738 parents = []
730 # check whether this revision is the start of a branch or part
739 # check whether this revision is the start of a branch or part
731 # of a branch renaming
740 # of a branch renaming
732 orig_paths = util.sort(orig_paths.items())
741 orig_paths = util.sort(orig_paths.items())
733 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
742 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
734 if root_paths:
743 if root_paths:
735 path, ent = root_paths[-1]
744 path, ent = root_paths[-1]
736 if ent.copyfrom_path:
745 if ent.copyfrom_path:
737 # If dir was moved while one of its file was removed
746 # If dir was moved while one of its file was removed
738 # the log may look like:
747 # the log may look like:
739 # A /dir (from /dir:x)
748 # A /dir (from /dir:x)
740 # A /dir/a (from /dir/a:y)
749 # A /dir/a (from /dir/a:y)
741 # A /dir/b (from /dir/b:z)
750 # A /dir/b (from /dir/b:z)
742 # ...
751 # ...
743 # for all remaining children.
752 # for all remaining children.
744 # Let's take the highest child element from rev as source.
753 # Let's take the highest child element from rev as source.
745 copies = [(p,e) for p,e in orig_paths[:-1]
754 copies = [(p,e) for p,e in orig_paths[:-1]
746 if isdescendantof(ent.copyfrom_path, e.copyfrom_path)]
755 if isdescendantof(ent.copyfrom_path, e.copyfrom_path)]
747 fromrev = max([e.copyfrom_rev for p,e in copies] + [ent.copyfrom_rev])
756 fromrev = max([e.copyfrom_rev for p,e in copies] + [ent.copyfrom_rev])
748 branched = True
757 branched = True
749 newpath = ent.copyfrom_path + self.module[len(path):]
758 newpath = ent.copyfrom_path + self.module[len(path):]
750 # ent.copyfrom_rev may not be the actual last revision
759 # ent.copyfrom_rev may not be the actual last revision
751 previd = self.latest(newpath, fromrev)
760 previd = self.latest(newpath, fromrev)
752 if previd is not None:
761 if previd is not None:
753 prevmodule, prevnum = self.revsplit(previd)[1:]
762 prevmodule, prevnum = self.revsplit(previd)[1:]
754 if prevnum >= self.startrev:
763 if prevnum >= self.startrev:
755 parents = [previd]
764 parents = [previd]
756 self.ui.note(_('found parent of branch %s at %d: %s\n') %
765 self.ui.note(_('found parent of branch %s at %d: %s\n') %
757 (self.module, prevnum, prevmodule))
766 (self.module, prevnum, prevmodule))
758 else:
767 else:
759 self.ui.debug(_("No copyfrom path, don't know what to do.\n"))
768 self.ui.debug(_("No copyfrom path, don't know what to do.\n"))
760
769
761 paths = []
770 paths = []
762 # filter out unrelated paths
771 # filter out unrelated paths
763 for path, ent in orig_paths:
772 for path, ent in orig_paths:
764 if self.getrelpath(path) is None:
773 if self.getrelpath(path) is None:
765 continue
774 continue
766 paths.append((path, ent))
775 paths.append((path, ent))
767
776
768 # Example SVN datetime. Includes microseconds.
777 # Example SVN datetime. Includes microseconds.
769 # ISO-8601 conformant
778 # ISO-8601 conformant
770 # '2007-01-04T17:35:00.902377Z'
779 # '2007-01-04T17:35:00.902377Z'
771 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
780 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
772
781
773 log = message and self.recode(message) or ''
782 log = message and self.recode(message) or ''
774 author = author and self.recode(author) or ''
783 author = author and self.recode(author) or ''
775 try:
784 try:
776 branch = self.module.split("/")[-1]
785 branch = self.module.split("/")[-1]
777 if branch == 'trunk':
786 if branch == 'trunk':
778 branch = ''
787 branch = ''
779 except IndexError:
788 except IndexError:
780 branch = None
789 branch = None
781
790
782 cset = commit(author=author,
791 cset = commit(author=author,
783 date=util.datestr(date),
792 date=util.datestr(date),
784 desc=log,
793 desc=log,
785 parents=parents,
794 parents=parents,
786 branch=branch,
795 branch=branch,
787 rev=rev.encode('utf-8'))
796 rev=rev.encode('utf-8'))
788
797
789 self.commits[rev] = cset
798 self.commits[rev] = cset
790 # The parents list is *shared* among self.paths and the
799 # The parents list is *shared* among self.paths and the
791 # commit object. Both will be updated below.
800 # commit object. Both will be updated below.
792 self.paths[rev] = (paths, cset.parents)
801 self.paths[rev] = (paths, cset.parents)
793 if self.child_cset and not self.child_cset.parents:
802 if self.child_cset and not self.child_cset.parents:
794 self.child_cset.parents[:] = [rev]
803 self.child_cset.parents[:] = [rev]
795 self.child_cset = cset
804 self.child_cset = cset
796 return cset, branched
805 return cset, branched
797
806
798 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
807 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
799 (self.module, from_revnum, to_revnum))
808 (self.module, from_revnum, to_revnum))
800
809
801 try:
810 try:
802 firstcset = None
811 firstcset = None
803 lastonbranch = False
812 lastonbranch = False
804 stream = self._getlog([self.module], from_revnum, to_revnum)
813 stream = self._getlog([self.module], from_revnum, to_revnum)
805 try:
814 try:
806 for entry in stream:
815 for entry in stream:
807 paths, revnum, author, date, message = entry
816 paths, revnum, author, date, message = entry
808 if revnum < self.startrev:
817 if revnum < self.startrev:
809 lastonbranch = True
818 lastonbranch = True
810 break
819 break
811 if self.is_blacklisted(revnum):
820 if self.is_blacklisted(revnum):
812 self.ui.note(_('skipping blacklisted revision %d\n')
821 self.ui.note(_('skipping blacklisted revision %d\n')
813 % revnum)
822 % revnum)
814 continue
823 continue
815 if paths is None:
824 if paths is None:
816 self.ui.debug(_('revision %d has no entries\n') % revnum)
825 self.ui.debug(_('revision %d has no entries\n') % revnum)
817 continue
826 continue
818 cset, lastonbranch = parselogentry(paths, revnum, author,
827 cset, lastonbranch = parselogentry(paths, revnum, author,
819 date, message)
828 date, message)
820 if cset:
829 if cset:
821 firstcset = cset
830 firstcset = cset
822 if lastonbranch:
831 if lastonbranch:
823 break
832 break
824 finally:
833 finally:
825 stream.close()
834 stream.close()
826
835
827 if not lastonbranch and firstcset and not firstcset.parents:
836 if not lastonbranch and firstcset and not firstcset.parents:
828 # The first revision of the sequence (the last fetched one)
837 # The first revision of the sequence (the last fetched one)
829 # has invalid parents if not a branch root. Find the parent
838 # has invalid parents if not a branch root. Find the parent
830 # revision now, if any.
839 # revision now, if any.
831 try:
840 try:
832 firstrevnum = self.revnum(firstcset.rev)
841 firstrevnum = self.revnum(firstcset.rev)
833 if firstrevnum > 1:
842 if firstrevnum > 1:
834 latest = self.latest(self.module, firstrevnum - 1)
843 latest = self.latest(self.module, firstrevnum - 1)
835 if latest:
844 if latest:
836 firstcset.parents.append(latest)
845 firstcset.parents.append(latest)
837 except util.Abort:
846 except SvnPathNotFound:
838 pass
847 pass
839 except SubversionException, (inst, num):
848 except SubversionException, (inst, num):
840 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
849 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
841 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
850 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
842 raise
851 raise
843
852
844 def _getfile(self, file, rev):
853 def _getfile(self, file, rev):
845 io = StringIO()
854 io = StringIO()
846 # TODO: ra.get_file transmits the whole file instead of diffs.
855 # TODO: ra.get_file transmits the whole file instead of diffs.
847 mode = ''
856 mode = ''
848 try:
857 try:
849 new_module, revnum = self.revsplit(rev)[1:]
858 new_module, revnum = self.revsplit(rev)[1:]
850 if self.module != new_module:
859 if self.module != new_module:
851 self.module = new_module
860 self.module = new_module
852 self.reparent(self.module)
861 self.reparent(self.module)
853 info = svn.ra.get_file(self.ra, file, revnum, io)
862 info = svn.ra.get_file(self.ra, file, revnum, io)
854 if isinstance(info, list):
863 if isinstance(info, list):
855 info = info[-1]
864 info = info[-1]
856 mode = ("svn:executable" in info) and 'x' or ''
865 mode = ("svn:executable" in info) and 'x' or ''
857 mode = ("svn:special" in info) and 'l' or mode
866 mode = ("svn:special" in info) and 'l' or mode
858 except SubversionException, e:
867 except SubversionException, e:
859 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
868 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
860 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
869 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
861 if e.apr_err in notfound: # File not found
870 if e.apr_err in notfound: # File not found
862 raise IOError()
871 raise IOError()
863 raise
872 raise
864 data = io.getvalue()
873 data = io.getvalue()
865 if mode == 'l':
874 if mode == 'l':
866 link_prefix = "link "
875 link_prefix = "link "
867 if data.startswith(link_prefix):
876 if data.startswith(link_prefix):
868 data = data[len(link_prefix):]
877 data = data[len(link_prefix):]
869 return data, mode
878 return data, mode
870
879
871 def _find_children(self, path, revnum):
880 def _find_children(self, path, revnum):
872 path = path.strip('/')
881 path = path.strip('/')
873 pool = Pool()
882 pool = Pool()
874 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
883 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
875 return ['%s/%s' % (path, x) for x in
884 return ['%s/%s' % (path, x) for x in
876 svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
885 svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
877
886
878 def getrelpath(self, path, module=None):
887 def getrelpath(self, path, module=None):
879 if module is None:
888 if module is None:
880 module = self.module
889 module = self.module
881 # Given the repository url of this wc, say
890 # Given the repository url of this wc, say
882 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
891 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
883 # extract the "entry" portion (a relative path) from what
892 # extract the "entry" portion (a relative path) from what
884 # svn log --xml says, ie
893 # svn log --xml says, ie
885 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
894 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
886 # that is to say "tests/PloneTestCase.py"
895 # that is to say "tests/PloneTestCase.py"
887 if path.startswith(module):
896 if path.startswith(module):
888 relative = path.rstrip('/')[len(module):]
897 relative = path.rstrip('/')[len(module):]
889 if relative.startswith('/'):
898 if relative.startswith('/'):
890 return relative[1:]
899 return relative[1:]
891 elif relative == '':
900 elif relative == '':
892 return relative
901 return relative
893
902
894 # The path is outside our tracked tree...
903 # The path is outside our tracked tree...
895 self.ui.debug(_('%r is not under %r, ignoring\n') % (path, module))
904 self.ui.debug(_('%r is not under %r, ignoring\n') % (path, module))
896 return None
905 return None
897
906
898 def _checkpath(self, path, revnum):
907 def _checkpath(self, path, revnum):
899 # ra.check_path does not like leading slashes very much, it leads
908 # ra.check_path does not like leading slashes very much, it leads
900 # to PROPFIND subversion errors
909 # to PROPFIND subversion errors
901 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
910 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
902
911
903 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
912 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
904 strict_node_history=False):
913 strict_node_history=False):
905 # Normalize path names, svn >= 1.5 only wants paths relative to
914 # Normalize path names, svn >= 1.5 only wants paths relative to
906 # supplied URL
915 # supplied URL
907 relpaths = []
916 relpaths = []
908 for p in paths:
917 for p in paths:
909 if not p.startswith('/'):
918 if not p.startswith('/'):
910 p = self.module + '/' + p
919 p = self.module + '/' + p
911 relpaths.append(p.strip('/'))
920 relpaths.append(p.strip('/'))
912 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
921 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
913 strict_node_history]
922 strict_node_history]
914 arg = encodeargs(args)
923 arg = encodeargs(args)
915 hgexe = util.hgexecutable()
924 hgexe = util.hgexecutable()
916 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
925 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
917 stdin, stdout = util.popen2(cmd, 'b')
926 stdin, stdout = util.popen2(cmd, 'b')
918 stdin.write(arg)
927 stdin.write(arg)
919 stdin.close()
928 stdin.close()
920 return logstream(stdout)
929 return logstream(stdout)
921
930
922 pre_revprop_change = '''#!/bin/sh
931 pre_revprop_change = '''#!/bin/sh
923
932
924 REPOS="$1"
933 REPOS="$1"
925 REV="$2"
934 REV="$2"
926 USER="$3"
935 USER="$3"
927 PROPNAME="$4"
936 PROPNAME="$4"
928 ACTION="$5"
937 ACTION="$5"
929
938
930 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
939 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
931 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
940 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
932 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
941 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
933
942
934 echo "Changing prohibited revision property" >&2
943 echo "Changing prohibited revision property" >&2
935 exit 1
944 exit 1
936 '''
945 '''
937
946
938 class svn_sink(converter_sink, commandline):
947 class svn_sink(converter_sink, commandline):
939 commit_re = re.compile(r'Committed revision (\d+).', re.M)
948 commit_re = re.compile(r'Committed revision (\d+).', re.M)
940
949
941 def prerun(self):
950 def prerun(self):
942 if self.wc:
951 if self.wc:
943 os.chdir(self.wc)
952 os.chdir(self.wc)
944
953
945 def postrun(self):
954 def postrun(self):
946 if self.wc:
955 if self.wc:
947 os.chdir(self.cwd)
956 os.chdir(self.cwd)
948
957
949 def join(self, name):
958 def join(self, name):
950 return os.path.join(self.wc, '.svn', name)
959 return os.path.join(self.wc, '.svn', name)
951
960
952 def revmapfile(self):
961 def revmapfile(self):
953 return self.join('hg-shamap')
962 return self.join('hg-shamap')
954
963
955 def authorfile(self):
964 def authorfile(self):
956 return self.join('hg-authormap')
965 return self.join('hg-authormap')
957
966
958 def __init__(self, ui, path):
967 def __init__(self, ui, path):
959 converter_sink.__init__(self, ui, path)
968 converter_sink.__init__(self, ui, path)
960 commandline.__init__(self, ui, 'svn')
969 commandline.__init__(self, ui, 'svn')
961 self.delete = []
970 self.delete = []
962 self.setexec = []
971 self.setexec = []
963 self.delexec = []
972 self.delexec = []
964 self.copies = []
973 self.copies = []
965 self.wc = None
974 self.wc = None
966 self.cwd = os.getcwd()
975 self.cwd = os.getcwd()
967
976
968 path = os.path.realpath(path)
977 path = os.path.realpath(path)
969
978
970 created = False
979 created = False
971 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
980 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
972 self.wc = path
981 self.wc = path
973 self.run0('update')
982 self.run0('update')
974 else:
983 else:
975 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
984 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
976
985
977 if os.path.isdir(os.path.dirname(path)):
986 if os.path.isdir(os.path.dirname(path)):
978 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
987 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
979 ui.status(_('initializing svn repo %r\n') %
988 ui.status(_('initializing svn repo %r\n') %
980 os.path.basename(path))
989 os.path.basename(path))
981 commandline(ui, 'svnadmin').run0('create', path)
990 commandline(ui, 'svnadmin').run0('create', path)
982 created = path
991 created = path
983 path = util.normpath(path)
992 path = util.normpath(path)
984 if not path.startswith('/'):
993 if not path.startswith('/'):
985 path = '/' + path
994 path = '/' + path
986 path = 'file://' + path
995 path = 'file://' + path
987
996
988 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
997 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
989 self.run0('checkout', path, wcpath)
998 self.run0('checkout', path, wcpath)
990
999
991 self.wc = wcpath
1000 self.wc = wcpath
992 self.opener = util.opener(self.wc)
1001 self.opener = util.opener(self.wc)
993 self.wopener = util.opener(self.wc)
1002 self.wopener = util.opener(self.wc)
994 self.childmap = mapfile(ui, self.join('hg-childmap'))
1003 self.childmap = mapfile(ui, self.join('hg-childmap'))
995 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
1004 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
996
1005
997 if created:
1006 if created:
998 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1007 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
999 fp = open(hook, 'w')
1008 fp = open(hook, 'w')
1000 fp.write(pre_revprop_change)
1009 fp.write(pre_revprop_change)
1001 fp.close()
1010 fp.close()
1002 util.set_flags(hook, False, True)
1011 util.set_flags(hook, False, True)
1003
1012
1004 xport = transport.SvnRaTransport(url=geturl(path))
1013 xport = transport.SvnRaTransport(url=geturl(path))
1005 self.uuid = svn.ra.get_uuid(xport.ra)
1014 self.uuid = svn.ra.get_uuid(xport.ra)
1006
1015
1007 def wjoin(self, *names):
1016 def wjoin(self, *names):
1008 return os.path.join(self.wc, *names)
1017 return os.path.join(self.wc, *names)
1009
1018
1010 def putfile(self, filename, flags, data):
1019 def putfile(self, filename, flags, data):
1011 if 'l' in flags:
1020 if 'l' in flags:
1012 self.wopener.symlink(data, filename)
1021 self.wopener.symlink(data, filename)
1013 else:
1022 else:
1014 try:
1023 try:
1015 if os.path.islink(self.wjoin(filename)):
1024 if os.path.islink(self.wjoin(filename)):
1016 os.unlink(filename)
1025 os.unlink(filename)
1017 except OSError:
1026 except OSError:
1018 pass
1027 pass
1019 self.wopener(filename, 'w').write(data)
1028 self.wopener(filename, 'w').write(data)
1020
1029
1021 if self.is_exec:
1030 if self.is_exec:
1022 was_exec = self.is_exec(self.wjoin(filename))
1031 was_exec = self.is_exec(self.wjoin(filename))
1023 else:
1032 else:
1024 # On filesystems not supporting execute-bit, there is no way
1033 # On filesystems not supporting execute-bit, there is no way
1025 # to know if it is set but asking subversion. Setting it
1034 # to know if it is set but asking subversion. Setting it
1026 # systematically is just as expensive and much simpler.
1035 # systematically is just as expensive and much simpler.
1027 was_exec = 'x' not in flags
1036 was_exec = 'x' not in flags
1028
1037
1029 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1038 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1030 if was_exec:
1039 if was_exec:
1031 if 'x' not in flags:
1040 if 'x' not in flags:
1032 self.delexec.append(filename)
1041 self.delexec.append(filename)
1033 else:
1042 else:
1034 if 'x' in flags:
1043 if 'x' in flags:
1035 self.setexec.append(filename)
1044 self.setexec.append(filename)
1036
1045
1037 def _copyfile(self, source, dest):
1046 def _copyfile(self, source, dest):
1038 # SVN's copy command pukes if the destination file exists, but
1047 # SVN's copy command pukes if the destination file exists, but
1039 # our copyfile method expects to record a copy that has
1048 # our copyfile method expects to record a copy that has
1040 # already occurred. Cross the semantic gap.
1049 # already occurred. Cross the semantic gap.
1041 wdest = self.wjoin(dest)
1050 wdest = self.wjoin(dest)
1042 exists = os.path.exists(wdest)
1051 exists = os.path.exists(wdest)
1043 if exists:
1052 if exists:
1044 fd, tempname = tempfile.mkstemp(
1053 fd, tempname = tempfile.mkstemp(
1045 prefix='hg-copy-', dir=os.path.dirname(wdest))
1054 prefix='hg-copy-', dir=os.path.dirname(wdest))
1046 os.close(fd)
1055 os.close(fd)
1047 os.unlink(tempname)
1056 os.unlink(tempname)
1048 os.rename(wdest, tempname)
1057 os.rename(wdest, tempname)
1049 try:
1058 try:
1050 self.run0('copy', source, dest)
1059 self.run0('copy', source, dest)
1051 finally:
1060 finally:
1052 if exists:
1061 if exists:
1053 try:
1062 try:
1054 os.unlink(wdest)
1063 os.unlink(wdest)
1055 except OSError:
1064 except OSError:
1056 pass
1065 pass
1057 os.rename(tempname, wdest)
1066 os.rename(tempname, wdest)
1058
1067
1059 def dirs_of(self, files):
1068 def dirs_of(self, files):
1060 dirs = util.set()
1069 dirs = util.set()
1061 for f in files:
1070 for f in files:
1062 if os.path.isdir(self.wjoin(f)):
1071 if os.path.isdir(self.wjoin(f)):
1063 dirs.add(f)
1072 dirs.add(f)
1064 for i in strutil.rfindall(f, '/'):
1073 for i in strutil.rfindall(f, '/'):
1065 dirs.add(f[:i])
1074 dirs.add(f[:i])
1066 return dirs
1075 return dirs
1067
1076
1068 def add_dirs(self, files):
1077 def add_dirs(self, files):
1069 add_dirs = [d for d in util.sort(self.dirs_of(files))
1078 add_dirs = [d for d in util.sort(self.dirs_of(files))
1070 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1079 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1071 if add_dirs:
1080 if add_dirs:
1072 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1081 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1073 return add_dirs
1082 return add_dirs
1074
1083
1075 def add_files(self, files):
1084 def add_files(self, files):
1076 if files:
1085 if files:
1077 self.xargs(files, 'add', quiet=True)
1086 self.xargs(files, 'add', quiet=True)
1078 return files
1087 return files
1079
1088
1080 def tidy_dirs(self, names):
1089 def tidy_dirs(self, names):
1081 dirs = util.sort(self.dirs_of(names))
1090 dirs = util.sort(self.dirs_of(names))
1082 dirs.reverse()
1091 dirs.reverse()
1083 deleted = []
1092 deleted = []
1084 for d in dirs:
1093 for d in dirs:
1085 wd = self.wjoin(d)
1094 wd = self.wjoin(d)
1086 if os.listdir(wd) == '.svn':
1095 if os.listdir(wd) == '.svn':
1087 self.run0('delete', d)
1096 self.run0('delete', d)
1088 deleted.append(d)
1097 deleted.append(d)
1089 return deleted
1098 return deleted
1090
1099
1091 def addchild(self, parent, child):
1100 def addchild(self, parent, child):
1092 self.childmap[parent] = child
1101 self.childmap[parent] = child
1093
1102
1094 def revid(self, rev):
1103 def revid(self, rev):
1095 return u"svn:%s@%s" % (self.uuid, rev)
1104 return u"svn:%s@%s" % (self.uuid, rev)
1096
1105
1097 def putcommit(self, files, copies, parents, commit, source):
1106 def putcommit(self, files, copies, parents, commit, source):
1098 # Apply changes to working copy
1107 # Apply changes to working copy
1099 for f, v in files:
1108 for f, v in files:
1100 try:
1109 try:
1101 data = source.getfile(f, v)
1110 data = source.getfile(f, v)
1102 except IOError, inst:
1111 except IOError, inst:
1103 self.delete.append(f)
1112 self.delete.append(f)
1104 else:
1113 else:
1105 e = source.getmode(f, v)
1114 e = source.getmode(f, v)
1106 self.putfile(f, e, data)
1115 self.putfile(f, e, data)
1107 if f in copies:
1116 if f in copies:
1108 self.copies.append([copies[f], f])
1117 self.copies.append([copies[f], f])
1109 files = [f[0] for f in files]
1118 files = [f[0] for f in files]
1110
1119
1111 for parent in parents:
1120 for parent in parents:
1112 try:
1121 try:
1113 return self.revid(self.childmap[parent])
1122 return self.revid(self.childmap[parent])
1114 except KeyError:
1123 except KeyError:
1115 pass
1124 pass
1116 entries = util.set(self.delete)
1125 entries = util.set(self.delete)
1117 files = util.frozenset(files)
1126 files = util.frozenset(files)
1118 entries.update(self.add_dirs(files.difference(entries)))
1127 entries.update(self.add_dirs(files.difference(entries)))
1119 if self.copies:
1128 if self.copies:
1120 for s, d in self.copies:
1129 for s, d in self.copies:
1121 self._copyfile(s, d)
1130 self._copyfile(s, d)
1122 self.copies = []
1131 self.copies = []
1123 if self.delete:
1132 if self.delete:
1124 self.xargs(self.delete, 'delete')
1133 self.xargs(self.delete, 'delete')
1125 self.delete = []
1134 self.delete = []
1126 entries.update(self.add_files(files.difference(entries)))
1135 entries.update(self.add_files(files.difference(entries)))
1127 entries.update(self.tidy_dirs(entries))
1136 entries.update(self.tidy_dirs(entries))
1128 if self.delexec:
1137 if self.delexec:
1129 self.xargs(self.delexec, 'propdel', 'svn:executable')
1138 self.xargs(self.delexec, 'propdel', 'svn:executable')
1130 self.delexec = []
1139 self.delexec = []
1131 if self.setexec:
1140 if self.setexec:
1132 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1141 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1133 self.setexec = []
1142 self.setexec = []
1134
1143
1135 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1144 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1136 fp = os.fdopen(fd, 'w')
1145 fp = os.fdopen(fd, 'w')
1137 fp.write(commit.desc)
1146 fp.write(commit.desc)
1138 fp.close()
1147 fp.close()
1139 try:
1148 try:
1140 output = self.run0('commit',
1149 output = self.run0('commit',
1141 username=util.shortuser(commit.author),
1150 username=util.shortuser(commit.author),
1142 file=messagefile,
1151 file=messagefile,
1143 encoding='utf-8')
1152 encoding='utf-8')
1144 try:
1153 try:
1145 rev = self.commit_re.search(output).group(1)
1154 rev = self.commit_re.search(output).group(1)
1146 except AttributeError:
1155 except AttributeError:
1147 self.ui.warn(_('unexpected svn output:\n'))
1156 self.ui.warn(_('unexpected svn output:\n'))
1148 self.ui.warn(output)
1157 self.ui.warn(output)
1149 raise util.Abort(_('unable to cope with svn output'))
1158 raise util.Abort(_('unable to cope with svn output'))
1150 if commit.rev:
1159 if commit.rev:
1151 self.run('propset', 'hg:convert-rev', commit.rev,
1160 self.run('propset', 'hg:convert-rev', commit.rev,
1152 revprop=True, revision=rev)
1161 revprop=True, revision=rev)
1153 if commit.branch and commit.branch != 'default':
1162 if commit.branch and commit.branch != 'default':
1154 self.run('propset', 'hg:convert-branch', commit.branch,
1163 self.run('propset', 'hg:convert-branch', commit.branch,
1155 revprop=True, revision=rev)
1164 revprop=True, revision=rev)
1156 for parent in parents:
1165 for parent in parents:
1157 self.addchild(parent, rev)
1166 self.addchild(parent, rev)
1158 return self.revid(rev)
1167 return self.revid(rev)
1159 finally:
1168 finally:
1160 os.unlink(messagefile)
1169 os.unlink(messagefile)
1161
1170
1162 def puttags(self, tags):
1171 def puttags(self, tags):
1163 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
1172 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
General Comments 0
You need to be logged in to leave comments. Login now