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