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