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