##// END OF EJS Templates
convert: svn -- fix tags handling...
Kirill Smelkov -
r5462:91a522a6 default
parent child Browse files
Show More
@@ -1,655 +1,666 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
12 # Relative path to tree of tags (default: "tags")
11 #
13 #
12 # 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:
13 #
15 #
14 # hg convert --config convert.svn.trunk=wackoname [...]
16 # hg convert --config convert.svn.trunk=wackoname [...]
15
17
16 import locale
18 import locale
17 import os
19 import os
18 import sys
20 import sys
19 import cPickle as pickle
21 import cPickle as pickle
20 from mercurial import util
22 from mercurial import util
21
23
22 # Subversion stuff. Works best with very recent Python SVN bindings
24 # Subversion stuff. Works best with very recent Python SVN bindings
23 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
25 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
24 # these bindings.
26 # these bindings.
25
27
26 from cStringIO import StringIO
28 from cStringIO import StringIO
27
29
28 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
30 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
29
31
30 try:
32 try:
31 from svn.core import SubversionException, Pool
33 from svn.core import SubversionException, Pool
32 import svn
34 import svn
33 import svn.client
35 import svn.client
34 import svn.core
36 import svn.core
35 import svn.ra
37 import svn.ra
36 import svn.delta
38 import svn.delta
37 import transport
39 import transport
38 except ImportError:
40 except ImportError:
39 pass
41 pass
40
42
41 def geturl(path):
43 def geturl(path):
42 try:
44 try:
43 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
45 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
44 except SubversionException:
46 except SubversionException:
45 pass
47 pass
46 if os.path.isdir(path):
48 if os.path.isdir(path):
47 return 'file://%s' % os.path.normpath(os.path.abspath(path))
49 return 'file://%s' % os.path.normpath(os.path.abspath(path))
48 return path
50 return path
49
51
50 def optrev(number):
52 def optrev(number):
51 optrev = svn.core.svn_opt_revision_t()
53 optrev = svn.core.svn_opt_revision_t()
52 optrev.kind = svn.core.svn_opt_revision_number
54 optrev.kind = svn.core.svn_opt_revision_number
53 optrev.value.number = number
55 optrev.value.number = number
54 return optrev
56 return optrev
55
57
56 class changedpath(object):
58 class changedpath(object):
57 def __init__(self, p):
59 def __init__(self, p):
58 self.copyfrom_path = p.copyfrom_path
60 self.copyfrom_path = p.copyfrom_path
59 self.copyfrom_rev = p.copyfrom_rev
61 self.copyfrom_rev = p.copyfrom_rev
60 self.action = p.action
62 self.action = p.action
61
63
62 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
64 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
63 strict_node_history=False):
65 strict_node_history=False):
64 protocol = -1
66 protocol = -1
65 def receiver(orig_paths, revnum, author, date, message, pool):
67 def receiver(orig_paths, revnum, author, date, message, pool):
66 if orig_paths is not None:
68 if orig_paths is not None:
67 for k, v in orig_paths.iteritems():
69 for k, v in orig_paths.iteritems():
68 orig_paths[k] = changedpath(v)
70 orig_paths[k] = changedpath(v)
69 pickle.dump((orig_paths, revnum, author, date, message),
71 pickle.dump((orig_paths, revnum, author, date, message),
70 fp, protocol)
72 fp, protocol)
71
73
72 try:
74 try:
73 # Use an ra of our own so that our parent can consume
75 # Use an ra of our own so that our parent can consume
74 # our results without confusing the server.
76 # our results without confusing the server.
75 t = transport.SvnRaTransport(url=url)
77 t = transport.SvnRaTransport(url=url)
76 svn.ra.get_log(t.ra, paths, start, end, limit,
78 svn.ra.get_log(t.ra, paths, start, end, limit,
77 discover_changed_paths,
79 discover_changed_paths,
78 strict_node_history,
80 strict_node_history,
79 receiver)
81 receiver)
80 except SubversionException, (inst, num):
82 except SubversionException, (inst, num):
81 pickle.dump(num, fp, protocol)
83 pickle.dump(num, fp, protocol)
82 else:
84 else:
83 pickle.dump(None, fp, protocol)
85 pickle.dump(None, fp, protocol)
84 fp.close()
86 fp.close()
85
87
86 def debugsvnlog(ui, **opts):
88 def debugsvnlog(ui, **opts):
87 """Fetch SVN log in a subprocess and channel them back to parent to
89 """Fetch SVN log in a subprocess and channel them back to parent to
88 avoid memory collection issues.
90 avoid memory collection issues.
89 """
91 """
90 util.set_binary(sys.stdin)
92 util.set_binary(sys.stdin)
91 util.set_binary(sys.stdout)
93 util.set_binary(sys.stdout)
92 args = decodeargs(sys.stdin.read())
94 args = decodeargs(sys.stdin.read())
93 get_log_child(sys.stdout, *args)
95 get_log_child(sys.stdout, *args)
94
96
95 # SVN conversion code stolen from bzr-svn and tailor
97 # SVN conversion code stolen from bzr-svn and tailor
96 class svn_source(converter_source):
98 class svn_source(converter_source):
97 def __init__(self, ui, url, rev=None):
99 def __init__(self, ui, url, rev=None):
98 super(svn_source, self).__init__(ui, url, rev=rev)
100 super(svn_source, self).__init__(ui, url, rev=rev)
99
101
100 try:
102 try:
101 SubversionException
103 SubversionException
102 except NameError:
104 except NameError:
103 raise NoRepo('subversion python bindings could not be loaded')
105 raise NoRepo('subversion python bindings could not be loaded')
104
106
105 self.encoding = locale.getpreferredencoding()
107 self.encoding = locale.getpreferredencoding()
106 self.lastrevs = {}
108 self.lastrevs = {}
107
109
108 latest = None
110 latest = None
109 try:
111 try:
110 # Support file://path@rev syntax. Useful e.g. to convert
112 # Support file://path@rev syntax. Useful e.g. to convert
111 # deleted branches.
113 # deleted branches.
112 at = url.rfind('@')
114 at = url.rfind('@')
113 if at >= 0:
115 if at >= 0:
114 latest = int(url[at+1:])
116 latest = int(url[at+1:])
115 url = url[:at]
117 url = url[:at]
116 except ValueError, e:
118 except ValueError, e:
117 pass
119 pass
118 self.url = geturl(url)
120 self.url = geturl(url)
119 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
121 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
120 try:
122 try:
121 self.transport = transport.SvnRaTransport(url=self.url)
123 self.transport = transport.SvnRaTransport(url=self.url)
122 self.ra = self.transport.ra
124 self.ra = self.transport.ra
123 self.ctx = self.transport.client
125 self.ctx = self.transport.client
124 self.base = svn.ra.get_repos_root(self.ra)
126 self.base = svn.ra.get_repos_root(self.ra)
125 self.module = self.url[len(self.base):]
127 self.module = self.url[len(self.base):]
126 self.modulemap = {} # revision, module
128 self.modulemap = {} # revision, module
127 self.commits = {}
129 self.commits = {}
128 self.paths = {}
130 self.paths = {}
129 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
131 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
130 except SubversionException, e:
132 except SubversionException, e:
131 ui.print_exc()
133 ui.print_exc()
132 raise NoRepo("couldn't open SVN repo %s" % self.url)
134 raise NoRepo("couldn't open SVN repo %s" % self.url)
133
135
134 if rev:
136 if rev:
135 try:
137 try:
136 latest = int(rev)
138 latest = int(rev)
137 except ValueError:
139 except ValueError:
138 raise util.Abort('svn: revision %s is not an integer' % rev)
140 raise util.Abort('svn: revision %s is not an integer' % rev)
139
141
140 try:
142 try:
141 self.get_blacklist()
143 self.get_blacklist()
142 except IOError, e:
144 except IOError, e:
143 pass
145 pass
144
146
145 self.last_changed = self.latest(self.module, latest)
147 self.last_changed = self.latest(self.module, latest)
146
148
147 self.head = self.revid(self.last_changed)
149 self.head = self.revid(self.last_changed)
148 self._changescache = None
150 self._changescache = None
149
151
150 def setrevmap(self, revmap, order):
152 def setrevmap(self, revmap, order):
151 lastrevs = {}
153 lastrevs = {}
152 for revid in revmap.keys():
154 for revid in revmap.keys():
153 uuid, module, revnum = self.revsplit(revid)
155 uuid, module, revnum = self.revsplit(revid)
154 lastrevnum = lastrevs.setdefault(module, revnum)
156 lastrevnum = lastrevs.setdefault(module, revnum)
155 if revnum > lastrevnum:
157 if revnum > lastrevnum:
156 lastrevs[module] = revnum
158 lastrevs[module] = revnum
157 self.lastrevs = lastrevs
159 self.lastrevs = lastrevs
158
160
159 def exists(self, path, optrev):
161 def exists(self, path, optrev):
160 try:
162 try:
161 svn.client.ls(self.url.rstrip('/') + '/' + path,
163 svn.client.ls(self.url.rstrip('/') + '/' + path,
162 optrev, False, self.ctx)
164 optrev, False, self.ctx)
163 return True
165 return True
164 except SubversionException, err:
166 except SubversionException, err:
165 return False
167 return False
166
168
167 def getheads(self):
169 def getheads(self):
168 # detect standard /branches, /tags, /trunk layout
170 # detect standard /branches, /tags, /trunk layout
169 rev = optrev(self.last_changed)
171 rev = optrev(self.last_changed)
170 rpath = self.url.strip('/')
172 rpath = self.url.strip('/')
171 cfgtrunk = self.ui.config('convert', 'svn.trunk')
173 cfgtrunk = self.ui.config('convert', 'svn.trunk')
172 cfgbranches = self.ui.config('convert', 'svn.branches')
174 cfgbranches = self.ui.config('convert', 'svn.branches')
175 cfgtags = self.ui.config('convert', 'svn.tags')
173 trunk = (cfgtrunk or 'trunk').strip('/')
176 trunk = (cfgtrunk or 'trunk').strip('/')
174 branches = (cfgbranches or 'branches').strip('/')
177 branches = (cfgbranches or 'branches').strip('/')
175 if self.exists(trunk, rev) and self.exists(branches, rev):
178 tags = (cfgtags or 'tags').strip('/')
176 self.ui.note('found trunk at %r and branches at %r\n' %
179 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
177 (trunk, branches))
180 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
181 (trunk, branches, tags))
178 oldmodule = self.module
182 oldmodule = self.module
179 self.module += '/' + trunk
183 self.module += '/' + trunk
180 lt = self.latest(self.module, self.last_changed)
184 lt = self.latest(self.module, self.last_changed)
181 self.head = self.revid(lt)
185 self.head = self.revid(lt)
182 self.heads = [self.head]
186 self.heads = [self.head]
183 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
187 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
184 self.ctx)
188 self.ctx)
185 for branch in branchnames.keys():
189 for branch in branchnames.keys():
186 if oldmodule:
190 if oldmodule:
187 module = '/' + oldmodule + '/' + branches + '/' + branch
191 module = oldmodule + '/' + branches + '/' + branch
188 else:
192 else:
189 module = '/' + branches + '/' + branch
193 module = '/' + branches + '/' + branch
190 brevnum = self.latest(module, self.last_changed)
194 brevnum = self.latest(module, self.last_changed)
191 brev = self.revid(brevnum, module)
195 brev = self.revid(brevnum, module)
192 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
196 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
193 self.heads.append(brev)
197 self.heads.append(brev)
194 elif cfgtrunk or cfgbranches:
198
195 raise util.Abort('trunk/branch layout expected, but not found')
199 if oldmodule:
200 self.tags = '%s/%s' % (oldmodule, tags)
201 else:
202 self.tags = '/%s' % tags
203
204 elif cfgtrunk or cfgbranches or cfgtags:
205 raise util.Abort('trunk/branch/tags layout expected, but not found')
196 else:
206 else:
197 self.ui.note('working with one branch\n')
207 self.ui.note('working with one branch\n')
198 self.heads = [self.head]
208 self.heads = [self.head]
209 self.tags = tags
199 return self.heads
210 return self.heads
200
211
201 def getfile(self, file, rev):
212 def getfile(self, file, rev):
202 data, mode = self._getfile(file, rev)
213 data, mode = self._getfile(file, rev)
203 self.modecache[(file, rev)] = mode
214 self.modecache[(file, rev)] = mode
204 return data
215 return data
205
216
206 def getmode(self, file, rev):
217 def getmode(self, file, rev):
207 return self.modecache[(file, rev)]
218 return self.modecache[(file, rev)]
208
219
209 def getchanges(self, rev):
220 def getchanges(self, rev):
210 if self._changescache and self._changescache[0] == rev:
221 if self._changescache and self._changescache[0] == rev:
211 return self._changescache[1]
222 return self._changescache[1]
212 self._changescache = None
223 self._changescache = None
213 self.modecache = {}
224 self.modecache = {}
214 (paths, parents) = self.paths[rev]
225 (paths, parents) = self.paths[rev]
215 files, copies = self.expandpaths(rev, paths, parents)
226 files, copies = self.expandpaths(rev, paths, parents)
216 files.sort()
227 files.sort()
217 files = zip(files, [rev] * len(files))
228 files = zip(files, [rev] * len(files))
218
229
219 # caller caches the result, so free it here to release memory
230 # caller caches the result, so free it here to release memory
220 del self.paths[rev]
231 del self.paths[rev]
221 return (files, copies)
232 return (files, copies)
222
233
223 def getchangedfiles(self, rev, i):
234 def getchangedfiles(self, rev, i):
224 changes = self.getchanges(rev)
235 changes = self.getchanges(rev)
225 self._changescache = (rev, changes)
236 self._changescache = (rev, changes)
226 return [f[0] for f in changes[0]]
237 return [f[0] for f in changes[0]]
227
238
228 def getcommit(self, rev):
239 def getcommit(self, rev):
229 if rev not in self.commits:
240 if rev not in self.commits:
230 uuid, module, revnum = self.revsplit(rev)
241 uuid, module, revnum = self.revsplit(rev)
231 self.module = module
242 self.module = module
232 self.reparent(module)
243 self.reparent(module)
233 stop = self.lastrevs.get(module, 0)
244 stop = self.lastrevs.get(module, 0)
234 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
245 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
235 commit = self.commits[rev]
246 commit = self.commits[rev]
236 # caller caches the result, so free it here to release memory
247 # caller caches the result, so free it here to release memory
237 del self.commits[rev]
248 del self.commits[rev]
238 return commit
249 return commit
239
250
240 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
251 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
241 strict_node_history=False):
252 strict_node_history=False):
242
253
243 def parent(fp):
254 def parent(fp):
244 while True:
255 while True:
245 entry = pickle.load(fp)
256 entry = pickle.load(fp)
246 try:
257 try:
247 orig_paths, revnum, author, date, message = entry
258 orig_paths, revnum, author, date, message = entry
248 except:
259 except:
249 if entry is None:
260 if entry is None:
250 break
261 break
251 raise SubversionException("child raised exception", entry)
262 raise SubversionException("child raised exception", entry)
252 yield entry
263 yield entry
253
264
254 args = [self.url, paths, start, end, limit, discover_changed_paths,
265 args = [self.url, paths, start, end, limit, discover_changed_paths,
255 strict_node_history]
266 strict_node_history]
256 arg = encodeargs(args)
267 arg = encodeargs(args)
257 hgexe = util.hgexecutable()
268 hgexe = util.hgexecutable()
258 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
269 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
259 stdin, stdout = os.popen2(cmd, 'b')
270 stdin, stdout = os.popen2(cmd, 'b')
260
271
261 stdin.write(arg)
272 stdin.write(arg)
262 stdin.close()
273 stdin.close()
263
274
264 for p in parent(stdout):
275 for p in parent(stdout):
265 yield p
276 yield p
266
277
267 def gettags(self):
278 def gettags(self):
268 tags = {}
279 tags = {}
269 start = self.revnum(self.head)
280 start = self.revnum(self.head)
270 try:
281 try:
271 for entry in self.get_log(['/tags'], 0, start):
282 for entry in self.get_log([self.tags], 0, start):
272 orig_paths, revnum, author, date, message = entry
283 orig_paths, revnum, author, date, message = entry
273 for path in orig_paths:
284 for path in orig_paths:
274 if not path.startswith('/tags/'):
285 if not path.startswith(self.tags+'/'):
275 continue
286 continue
276 ent = orig_paths[path]
287 ent = orig_paths[path]
277 source = ent.copyfrom_path
288 source = ent.copyfrom_path
278 rev = ent.copyfrom_rev
289 rev = ent.copyfrom_rev
279 tag = path.split('/', 2)[2]
290 tag = path.split('/')[-1]
280 tags[tag] = self.revid(rev, module=source)
291 tags[tag] = self.revid(rev, module=source)
281 except SubversionException, (inst, num):
292 except SubversionException, (inst, num):
282 self.ui.note('no tags found at revision %d\n' % start)
293 self.ui.note('no tags found at revision %d\n' % start)
283 return tags
294 return tags
284
295
285 # -- helper functions --
296 # -- helper functions --
286
297
287 def revid(self, revnum, module=None):
298 def revid(self, revnum, module=None):
288 if not module:
299 if not module:
289 module = self.module
300 module = self.module
290 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
301 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
291 revnum)
302 revnum)
292
303
293 def revnum(self, rev):
304 def revnum(self, rev):
294 return int(rev.split('@')[-1])
305 return int(rev.split('@')[-1])
295
306
296 def revsplit(self, rev):
307 def revsplit(self, rev):
297 url, revnum = rev.encode(self.encoding).split('@', 1)
308 url, revnum = rev.encode(self.encoding).split('@', 1)
298 revnum = int(revnum)
309 revnum = int(revnum)
299 parts = url.split('/', 1)
310 parts = url.split('/', 1)
300 uuid = parts.pop(0)[4:]
311 uuid = parts.pop(0)[4:]
301 mod = ''
312 mod = ''
302 if parts:
313 if parts:
303 mod = '/' + parts[0]
314 mod = '/' + parts[0]
304 return uuid, mod, revnum
315 return uuid, mod, revnum
305
316
306 def latest(self, path, stop=0):
317 def latest(self, path, stop=0):
307 'find the latest revision affecting path, up to stop'
318 'find the latest revision affecting path, up to stop'
308 if not stop:
319 if not stop:
309 stop = svn.ra.get_latest_revnum(self.ra)
320 stop = svn.ra.get_latest_revnum(self.ra)
310 try:
321 try:
311 self.reparent('')
322 self.reparent('')
312 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
323 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
313 self.reparent(self.module)
324 self.reparent(self.module)
314 except SubversionException:
325 except SubversionException:
315 dirent = None
326 dirent = None
316 if not dirent:
327 if not dirent:
317 raise util.Abort('%s not found up to revision %d' % (path, stop))
328 raise util.Abort('%s not found up to revision %d' % (path, stop))
318
329
319 return dirent.created_rev
330 return dirent.created_rev
320
331
321 def get_blacklist(self):
332 def get_blacklist(self):
322 """Avoid certain revision numbers.
333 """Avoid certain revision numbers.
323 It is not uncommon for two nearby revisions to cancel each other
334 It is not uncommon for two nearby revisions to cancel each other
324 out, e.g. 'I copied trunk into a subdirectory of itself instead
335 out, e.g. 'I copied trunk into a subdirectory of itself instead
325 of making a branch'. The converted repository is significantly
336 of making a branch'. The converted repository is significantly
326 smaller if we ignore such revisions."""
337 smaller if we ignore such revisions."""
327 self.blacklist = util.set()
338 self.blacklist = util.set()
328 blacklist = self.blacklist
339 blacklist = self.blacklist
329 for line in file("blacklist.txt", "r"):
340 for line in file("blacklist.txt", "r"):
330 if not line.startswith("#"):
341 if not line.startswith("#"):
331 try:
342 try:
332 svn_rev = int(line.strip())
343 svn_rev = int(line.strip())
333 blacklist.add(svn_rev)
344 blacklist.add(svn_rev)
334 except ValueError, e:
345 except ValueError, e:
335 pass # not an integer or a comment
346 pass # not an integer or a comment
336
347
337 def is_blacklisted(self, svn_rev):
348 def is_blacklisted(self, svn_rev):
338 return svn_rev in self.blacklist
349 return svn_rev in self.blacklist
339
350
340 def reparent(self, module):
351 def reparent(self, module):
341 svn_url = self.base + module
352 svn_url = self.base + module
342 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
353 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
343 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
354 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
344
355
345 def expandpaths(self, rev, paths, parents):
356 def expandpaths(self, rev, paths, parents):
346 def get_entry_from_path(path, module=self.module):
357 def get_entry_from_path(path, module=self.module):
347 # Given the repository url of this wc, say
358 # Given the repository url of this wc, say
348 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
359 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
349 # extract the "entry" portion (a relative path) from what
360 # extract the "entry" portion (a relative path) from what
350 # svn log --xml says, ie
361 # svn log --xml says, ie
351 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
362 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
352 # that is to say "tests/PloneTestCase.py"
363 # that is to say "tests/PloneTestCase.py"
353 if path.startswith(module):
364 if path.startswith(module):
354 relative = path[len(module):]
365 relative = path[len(module):]
355 if relative.startswith('/'):
366 if relative.startswith('/'):
356 return relative[1:]
367 return relative[1:]
357 else:
368 else:
358 return relative
369 return relative
359
370
360 # The path is outside our tracked tree...
371 # The path is outside our tracked tree...
361 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
372 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
362 return None
373 return None
363
374
364 entries = []
375 entries = []
365 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
376 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
366 copies = {}
377 copies = {}
367 revnum = self.revnum(rev)
378 revnum = self.revnum(rev)
368
379
369 if revnum in self.modulemap:
380 if revnum in self.modulemap:
370 new_module = self.modulemap[revnum]
381 new_module = self.modulemap[revnum]
371 if new_module != self.module:
382 if new_module != self.module:
372 self.module = new_module
383 self.module = new_module
373 self.reparent(self.module)
384 self.reparent(self.module)
374
385
375 for path, ent in paths:
386 for path, ent in paths:
376 entrypath = get_entry_from_path(path, module=self.module)
387 entrypath = get_entry_from_path(path, module=self.module)
377 entry = entrypath.decode(self.encoding)
388 entry = entrypath.decode(self.encoding)
378
389
379 kind = svn.ra.check_path(self.ra, entrypath, revnum)
390 kind = svn.ra.check_path(self.ra, entrypath, revnum)
380 if kind == svn.core.svn_node_file:
391 if kind == svn.core.svn_node_file:
381 if ent.copyfrom_path:
392 if ent.copyfrom_path:
382 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
393 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
383 if copyfrom_path:
394 if copyfrom_path:
384 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
395 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
385 # It's probably important for hg that the source
396 # It's probably important for hg that the source
386 # exists in the revision's parent, not just the
397 # exists in the revision's parent, not just the
387 # ent.copyfrom_rev
398 # ent.copyfrom_rev
388 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
399 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
389 if fromkind != 0:
400 if fromkind != 0:
390 copies[self.recode(entry)] = self.recode(copyfrom_path)
401 copies[self.recode(entry)] = self.recode(copyfrom_path)
391 entries.append(self.recode(entry))
402 entries.append(self.recode(entry))
392 elif kind == 0: # gone, but had better be a deleted *file*
403 elif kind == 0: # gone, but had better be a deleted *file*
393 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
404 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
394
405
395 # if a branch is created but entries are removed in the same
406 # if a branch is created but entries are removed in the same
396 # changeset, get the right fromrev
407 # changeset, get the right fromrev
397 if parents:
408 if parents:
398 uuid, old_module, fromrev = self.revsplit(parents[0])
409 uuid, old_module, fromrev = self.revsplit(parents[0])
399 else:
410 else:
400 fromrev = revnum - 1
411 fromrev = revnum - 1
401 # might always need to be revnum - 1 in these 3 lines?
412 # might always need to be revnum - 1 in these 3 lines?
402 old_module = self.modulemap.get(fromrev, self.module)
413 old_module = self.modulemap.get(fromrev, self.module)
403
414
404 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
415 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
405 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
416 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
406
417
407 def lookup_parts(p):
418 def lookup_parts(p):
408 rc = None
419 rc = None
409 parts = p.split("/")
420 parts = p.split("/")
410 for i in range(len(parts)):
421 for i in range(len(parts)):
411 part = "/".join(parts[:i])
422 part = "/".join(parts[:i])
412 info = part, copyfrom.get(part, None)
423 info = part, copyfrom.get(part, None)
413 if info[1] is not None:
424 if info[1] is not None:
414 self.ui.debug("Found parent directory %s\n" % info[1])
425 self.ui.debug("Found parent directory %s\n" % info[1])
415 rc = info
426 rc = info
416 return rc
427 return rc
417
428
418 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
429 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
419
430
420 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
431 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
421
432
422 # need to remove fragment from lookup_parts and replace with copyfrom_path
433 # need to remove fragment from lookup_parts and replace with copyfrom_path
423 if frompath is not None:
434 if frompath is not None:
424 self.ui.debug("munge-o-matic\n")
435 self.ui.debug("munge-o-matic\n")
425 self.ui.debug(entrypath + '\n')
436 self.ui.debug(entrypath + '\n')
426 self.ui.debug(entrypath[len(frompath):] + '\n')
437 self.ui.debug(entrypath[len(frompath):] + '\n')
427 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
438 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
428 fromrev = froment.copyfrom_rev
439 fromrev = froment.copyfrom_rev
429 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
440 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
430
441
431 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
442 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
432 if fromkind == svn.core.svn_node_file: # a deleted file
443 if fromkind == svn.core.svn_node_file: # a deleted file
433 entries.append(self.recode(entry))
444 entries.append(self.recode(entry))
434 elif fromkind == svn.core.svn_node_dir:
445 elif fromkind == svn.core.svn_node_dir:
435 # print "Deleted/moved non-file:", revnum, path, ent
446 # print "Deleted/moved non-file:", revnum, path, ent
436 # children = self._find_children(path, revnum - 1)
447 # children = self._find_children(path, revnum - 1)
437 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
448 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
438 # Sometimes this is tricky. For example: in
449 # Sometimes this is tricky. For example: in
439 # The Subversion Repository revision 6940 a dir
450 # The Subversion Repository revision 6940 a dir
440 # was copied and one of its files was deleted
451 # was copied and one of its files was deleted
441 # from the new location in the same commit. This
452 # from the new location in the same commit. This
442 # code can't deal with that yet.
453 # code can't deal with that yet.
443 if ent.action == 'C':
454 if ent.action == 'C':
444 children = self._find_children(path, fromrev)
455 children = self._find_children(path, fromrev)
445 else:
456 else:
446 oroot = entrypath.strip('/')
457 oroot = entrypath.strip('/')
447 nroot = path.strip('/')
458 nroot = path.strip('/')
448 children = self._find_children(oroot, fromrev)
459 children = self._find_children(oroot, fromrev)
449 children = [s.replace(oroot,nroot) for s in children]
460 children = [s.replace(oroot,nroot) for s in children]
450 # Mark all [files, not directories] as deleted.
461 # Mark all [files, not directories] as deleted.
451 for child in children:
462 for child in children:
452 # Can we move a child directory and its
463 # Can we move a child directory and its
453 # parent in the same commit? (probably can). Could
464 # parent in the same commit? (probably can). Could
454 # cause problems if instead of revnum -1,
465 # cause problems if instead of revnum -1,
455 # we have to look in (copyfrom_path, revnum - 1)
466 # we have to look in (copyfrom_path, revnum - 1)
456 entrypath = get_entry_from_path("/" + child, module=old_module)
467 entrypath = get_entry_from_path("/" + child, module=old_module)
457 if entrypath:
468 if entrypath:
458 entry = self.recode(entrypath.decode(self.encoding))
469 entry = self.recode(entrypath.decode(self.encoding))
459 if entry in copies:
470 if entry in copies:
460 # deleted file within a copy
471 # deleted file within a copy
461 del copies[entry]
472 del copies[entry]
462 else:
473 else:
463 entries.append(entry)
474 entries.append(entry)
464 else:
475 else:
465 self.ui.debug('unknown path in revision %d: %s\n' % \
476 self.ui.debug('unknown path in revision %d: %s\n' % \
466 (revnum, path))
477 (revnum, path))
467 elif kind == svn.core.svn_node_dir:
478 elif kind == svn.core.svn_node_dir:
468 # Should probably synthesize normal file entries
479 # Should probably synthesize normal file entries
469 # and handle as above to clean up copy/rename handling.
480 # and handle as above to clean up copy/rename handling.
470
481
471 # If the directory just had a prop change,
482 # If the directory just had a prop change,
472 # then we shouldn't need to look for its children.
483 # then we shouldn't need to look for its children.
473 # Also this could create duplicate entries. Not sure
484 # Also this could create duplicate entries. Not sure
474 # whether this will matter. Maybe should make entries a set.
485 # whether this will matter. Maybe should make entries a set.
475 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
486 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
476 # This will fail if a directory was copied
487 # This will fail if a directory was copied
477 # from another branch and then some of its files
488 # from another branch and then some of its files
478 # were deleted in the same transaction.
489 # were deleted in the same transaction.
479 children = self._find_children(path, revnum)
490 children = self._find_children(path, revnum)
480 children.sort()
491 children.sort()
481 for child in children:
492 for child in children:
482 # Can we move a child directory and its
493 # Can we move a child directory and its
483 # parent in the same commit? (probably can). Could
494 # parent in the same commit? (probably can). Could
484 # cause problems if instead of revnum -1,
495 # cause problems if instead of revnum -1,
485 # we have to look in (copyfrom_path, revnum - 1)
496 # we have to look in (copyfrom_path, revnum - 1)
486 entrypath = get_entry_from_path("/" + child, module=self.module)
497 entrypath = get_entry_from_path("/" + child, module=self.module)
487 # print child, self.module, entrypath
498 # print child, self.module, entrypath
488 if entrypath:
499 if entrypath:
489 # Need to filter out directories here...
500 # Need to filter out directories here...
490 kind = svn.ra.check_path(self.ra, entrypath, revnum)
501 kind = svn.ra.check_path(self.ra, entrypath, revnum)
491 if kind != svn.core.svn_node_dir:
502 if kind != svn.core.svn_node_dir:
492 entries.append(self.recode(entrypath))
503 entries.append(self.recode(entrypath))
493
504
494 # Copies here (must copy all from source)
505 # Copies here (must copy all from source)
495 # Probably not a real problem for us if
506 # Probably not a real problem for us if
496 # source does not exist
507 # source does not exist
497
508
498 # Can do this with the copy command "hg copy"
509 # Can do this with the copy command "hg copy"
499 # if ent.copyfrom_path:
510 # if ent.copyfrom_path:
500 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
511 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
501 # module=self.module)
512 # module=self.module)
502 # copyto_entry = entrypath
513 # copyto_entry = entrypath
503 #
514 #
504 # print "copy directory", copyfrom_entry, 'to', copyto_entry
515 # print "copy directory", copyfrom_entry, 'to', copyto_entry
505 #
516 #
506 # copies.append((copyfrom_entry, copyto_entry))
517 # copies.append((copyfrom_entry, copyto_entry))
507
518
508 if ent.copyfrom_path:
519 if ent.copyfrom_path:
509 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
520 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
510 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
521 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
511 if copyfrom_entry:
522 if copyfrom_entry:
512 copyfrom[path] = ent
523 copyfrom[path] = ent
513 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
524 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
514
525
515 # Good, /probably/ a regular copy. Really should check
526 # Good, /probably/ a regular copy. Really should check
516 # to see whether the parent revision actually contains
527 # to see whether the parent revision actually contains
517 # the directory in question.
528 # the directory in question.
518 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
529 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
519 children.sort()
530 children.sort()
520 for child in children:
531 for child in children:
521 entrypath = get_entry_from_path("/" + child, module=self.module)
532 entrypath = get_entry_from_path("/" + child, module=self.module)
522 if entrypath:
533 if entrypath:
523 entry = entrypath.decode(self.encoding)
534 entry = entrypath.decode(self.encoding)
524 # print "COPY COPY From", copyfrom_entry, entry
535 # print "COPY COPY From", copyfrom_entry, entry
525 copyto_path = path + entry[len(copyfrom_entry):]
536 copyto_path = path + entry[len(copyfrom_entry):]
526 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
537 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
527 # print "COPY", entry, "COPY To", copyto_entry
538 # print "COPY", entry, "COPY To", copyto_entry
528 copies[self.recode(copyto_entry)] = self.recode(entry)
539 copies[self.recode(copyto_entry)] = self.recode(entry)
529 # copy from quux splort/quuxfile
540 # copy from quux splort/quuxfile
530
541
531 return (entries, copies)
542 return (entries, copies)
532
543
533 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
544 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
534 self.child_cset = None
545 self.child_cset = None
535 def parselogentry(orig_paths, revnum, author, date, message):
546 def parselogentry(orig_paths, revnum, author, date, message):
536 self.ui.debug("parsing revision %d (%d changes)\n" %
547 self.ui.debug("parsing revision %d (%d changes)\n" %
537 (revnum, len(orig_paths)))
548 (revnum, len(orig_paths)))
538
549
539 if revnum in self.modulemap:
550 if revnum in self.modulemap:
540 new_module = self.modulemap[revnum]
551 new_module = self.modulemap[revnum]
541 if new_module != self.module:
552 if new_module != self.module:
542 self.module = new_module
553 self.module = new_module
543 self.reparent(self.module)
554 self.reparent(self.module)
544
555
545 rev = self.revid(revnum)
556 rev = self.revid(revnum)
546 # branch log might return entries for a parent we already have
557 # branch log might return entries for a parent we already have
547 if (rev in self.commits or
558 if (rev in self.commits or
548 (revnum < self.lastrevs.get(self.module, 0))):
559 (revnum < self.lastrevs.get(self.module, 0))):
549 return
560 return
550
561
551 parents = []
562 parents = []
552 # check whether this revision is the start of a branch
563 # check whether this revision is the start of a branch
553 if self.module in orig_paths:
564 if self.module in orig_paths:
554 ent = orig_paths[self.module]
565 ent = orig_paths[self.module]
555 if ent.copyfrom_path:
566 if ent.copyfrom_path:
556 # ent.copyfrom_rev may not be the actual last revision
567 # ent.copyfrom_rev may not be the actual last revision
557 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
568 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
558 self.modulemap[prev] = ent.copyfrom_path
569 self.modulemap[prev] = ent.copyfrom_path
559 parents = [self.revid(prev, ent.copyfrom_path)]
570 parents = [self.revid(prev, ent.copyfrom_path)]
560 self.ui.note('found parent of branch %s at %d: %s\n' % \
571 self.ui.note('found parent of branch %s at %d: %s\n' % \
561 (self.module, prev, ent.copyfrom_path))
572 (self.module, prev, ent.copyfrom_path))
562 else:
573 else:
563 self.ui.debug("No copyfrom path, don't know what to do.\n")
574 self.ui.debug("No copyfrom path, don't know what to do.\n")
564
575
565 self.modulemap[revnum] = self.module # track backwards in time
576 self.modulemap[revnum] = self.module # track backwards in time
566
577
567 orig_paths = orig_paths.items()
578 orig_paths = orig_paths.items()
568 orig_paths.sort()
579 orig_paths.sort()
569 paths = []
580 paths = []
570 # filter out unrelated paths
581 # filter out unrelated paths
571 for path, ent in orig_paths:
582 for path, ent in orig_paths:
572 if not path.startswith(self.module):
583 if not path.startswith(self.module):
573 self.ui.debug("boring@%s: %s\n" % (revnum, path))
584 self.ui.debug("boring@%s: %s\n" % (revnum, path))
574 continue
585 continue
575 paths.append((path, ent))
586 paths.append((path, ent))
576
587
577 self.paths[rev] = (paths, parents)
588 self.paths[rev] = (paths, parents)
578
589
579 # Example SVN datetime. Includes microseconds.
590 # Example SVN datetime. Includes microseconds.
580 # ISO-8601 conformant
591 # ISO-8601 conformant
581 # '2007-01-04T17:35:00.902377Z'
592 # '2007-01-04T17:35:00.902377Z'
582 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
593 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
583
594
584 log = message and self.recode(message)
595 log = message and self.recode(message)
585 author = author and self.recode(author) or ''
596 author = author and self.recode(author) or ''
586 try:
597 try:
587 branch = self.module.split("/")[-1]
598 branch = self.module.split("/")[-1]
588 if branch == 'trunk':
599 if branch == 'trunk':
589 branch = ''
600 branch = ''
590 except IndexError:
601 except IndexError:
591 branch = None
602 branch = None
592
603
593 cset = commit(author=author,
604 cset = commit(author=author,
594 date=util.datestr(date),
605 date=util.datestr(date),
595 desc=log,
606 desc=log,
596 parents=parents,
607 parents=parents,
597 branch=branch,
608 branch=branch,
598 rev=rev.encode('utf-8'))
609 rev=rev.encode('utf-8'))
599
610
600 self.commits[rev] = cset
611 self.commits[rev] = cset
601 if self.child_cset and not self.child_cset.parents:
612 if self.child_cset and not self.child_cset.parents:
602 self.child_cset.parents = [rev]
613 self.child_cset.parents = [rev]
603 self.child_cset = cset
614 self.child_cset = cset
604
615
605 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
616 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
606 (self.module, from_revnum, to_revnum))
617 (self.module, from_revnum, to_revnum))
607
618
608 try:
619 try:
609 for entry in self.get_log([self.module], from_revnum, to_revnum):
620 for entry in self.get_log([self.module], from_revnum, to_revnum):
610 orig_paths, revnum, author, date, message = entry
621 orig_paths, revnum, author, date, message = entry
611 if self.is_blacklisted(revnum):
622 if self.is_blacklisted(revnum):
612 self.ui.note('skipping blacklisted revision %d\n' % revnum)
623 self.ui.note('skipping blacklisted revision %d\n' % revnum)
613 continue
624 continue
614 if orig_paths is None:
625 if orig_paths is None:
615 self.ui.debug('revision %d has no entries\n' % revnum)
626 self.ui.debug('revision %d has no entries\n' % revnum)
616 continue
627 continue
617 parselogentry(orig_paths, revnum, author, date, message)
628 parselogentry(orig_paths, revnum, author, date, message)
618 except SubversionException, (inst, num):
629 except SubversionException, (inst, num):
619 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
630 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
620 raise NoSuchRevision(branch=self,
631 raise NoSuchRevision(branch=self,
621 revision="Revision number %d" % to_revnum)
632 revision="Revision number %d" % to_revnum)
622 raise
633 raise
623
634
624 def _getfile(self, file, rev):
635 def _getfile(self, file, rev):
625 io = StringIO()
636 io = StringIO()
626 # TODO: ra.get_file transmits the whole file instead of diffs.
637 # TODO: ra.get_file transmits the whole file instead of diffs.
627 mode = ''
638 mode = ''
628 try:
639 try:
629 revnum = self.revnum(rev)
640 revnum = self.revnum(rev)
630 if self.module != self.modulemap[revnum]:
641 if self.module != self.modulemap[revnum]:
631 self.module = self.modulemap[revnum]
642 self.module = self.modulemap[revnum]
632 self.reparent(self.module)
643 self.reparent(self.module)
633 info = svn.ra.get_file(self.ra, file, revnum, io)
644 info = svn.ra.get_file(self.ra, file, revnum, io)
634 if isinstance(info, list):
645 if isinstance(info, list):
635 info = info[-1]
646 info = info[-1]
636 mode = ("svn:executable" in info) and 'x' or ''
647 mode = ("svn:executable" in info) and 'x' or ''
637 mode = ("svn:special" in info) and 'l' or mode
648 mode = ("svn:special" in info) and 'l' or mode
638 except SubversionException, e:
649 except SubversionException, e:
639 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
650 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
640 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
651 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
641 if e.apr_err in notfound: # File not found
652 if e.apr_err in notfound: # File not found
642 raise IOError()
653 raise IOError()
643 raise
654 raise
644 data = io.getvalue()
655 data = io.getvalue()
645 if mode == 'l':
656 if mode == 'l':
646 link_prefix = "link "
657 link_prefix = "link "
647 if data.startswith(link_prefix):
658 if data.startswith(link_prefix):
648 data = data[len(link_prefix):]
659 data = data[len(link_prefix):]
649 return data, mode
660 return data, mode
650
661
651 def _find_children(self, path, revnum):
662 def _find_children(self, path, revnum):
652 path = path.strip('/')
663 path = path.strip('/')
653 pool = Pool()
664 pool = Pool()
654 rpath = '/'.join([self.base, path]).strip('/')
665 rpath = '/'.join([self.base, path]).strip('/')
655 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
666 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
@@ -1,60 +1,115 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
12
13 svnadmin create svn-repo
13 svnadmin create svn-repo
14
14
15 echo % initial svn import
15 echo % initial svn import
16 mkdir t
16 mkdir t
17 cd t
17 cd t
18 echo a > a
18 echo a > a
19 cd ..
19 cd ..
20
20
21 svnpath=`pwd | fix_path`
21 svnpath=`pwd | fix_path`
22 # SVN wants all paths to start with a slash. Unfortunately,
22 # SVN wants all paths to start with a slash. Unfortunately,
23 # Windows ones don't. Handle that.
23 # Windows ones don't. Handle that.
24 expr $svnpath : "\/" > /dev/null
24 expr $svnpath : "\/" > /dev/null
25 if [ $? -ne 0 ]; then
25 if [ $? -ne 0 ]; then
26 svnpath='/'$svnpath
26 svnpath='/'$svnpath
27 fi
27 fi
28
28
29 svnurl=file://$svnpath/svn-repo/trunk
29 svnurl=file://$svnpath/svn-repo/trunk
30 svn import -m init t $svnurl | fix_path
30 svn import -m init t $svnurl | fix_path
31
31
32 echo % update svn repository
32 echo % update svn repository
33 svn co $svnurl t2 | fix_path
33 svn co $svnurl t2 | fix_path
34 cd t2
34 cd t2
35 echo b >> a
35 echo b >> a
36 echo b > b
36 echo b > b
37 svn add b
37 svn add b
38 svn ci -m changea
38 svn ci -m changea
39 cd ..
39 cd ..
40
40
41 echo % convert to hg once
41 echo % convert to hg once
42 hg convert $svnurl
42 hg convert $svnurl
43
43
44 echo % update svn repository again
44 echo % update svn repository again
45 cd t2
45 cd t2
46 echo c >> a
46 echo c >> a
47 echo c >> b
47 echo c >> b
48 svn ci -m changeb
48 svn ci -m changeb
49 cd ..
49 cd ..
50
50
51 echo % test incremental conversion
51 echo % test incremental conversion
52 hg convert $svnurl
52 hg convert $svnurl
53
53
54 echo % test filemap
54 echo % test filemap
55 echo 'include b' > filemap
55 echo 'include b' > filemap
56 hg convert --filemap filemap $svnurl fmap
56 hg convert --filemap filemap $svnurl fmap
57 echo '[extensions]' >> $HGRCPATH
57 echo '[extensions]' >> $HGRCPATH
58 echo 'hgext.graphlog =' >> $HGRCPATH
58 echo 'hgext.graphlog =' >> $HGRCPATH
59 hg glog -R fmap --template '#rev# #desc|firstline# files: #files#\n'
59 hg glog -R fmap --template '#rev# #desc|firstline# files: #files#\n'
60
60
61 ########################################
62
63 echo "# now tests that it works with trunk/branches/tags layout"
64 echo
65 echo % initial svn import
66 mkdir projA
67 cd projA
68 mkdir trunk
69 mkdir branches
70 mkdir tags
71 cd ..
72
73 svnurl=file://$svnpath/svn-repo/projA
74 svn import -m "init projA" projA $svnurl | fix_path
75
76
77 echo % update svn repository
78 svn co $svnurl/trunk A | fix_path
79 cd A
80 echo hello > letter.txt
81 svn add letter.txt
82 svn ci -m hello
83
84 echo world >> letter.txt
85 svn ci -m world
86
87 svn copy -m "tag v0.1" $svnurl/trunk $svnurl/tags/v0.1
88
89 echo 'nice day today!' >> letter.txt
90 svn ci -m "nice day"
91 cd ..
92
93 echo % convert to hg once
94 hg convert $svnurl A-hg
95
96 echo % update svn repository again
97 cd A
98 echo "see second letter" >> letter.txt
99 echo "nice to meet you" > letter2.txt
100 svn add letter2.txt
101 svn ci -m "second letter"
102
103 svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
104
105 echo "blah-blah-blah" >> letter2.txt
106 svn ci -m "work in progress"
107 cd ..
108
109 echo % test incremental conversion
110 hg convert $svnurl A-hg
111
112 cd A-hg
113 hg glog --template '#rev# #desc|firstline# files: #files#\n'
114 hg tags -q
115 cd ..
@@ -1,44 +1,114 b''
1 % initial svn import
1 % initial svn import
2 Adding t/a
2 Adding t/a
3
3
4 Committed revision 1.
4 Committed revision 1.
5 % update svn repository
5 % update svn repository
6 A t2/a
6 A t2/a
7 Checked out revision 1.
7 Checked out revision 1.
8 A b
8 A b
9 Sending a
9 Sending a
10 Adding b
10 Adding b
11 Transmitting file data ..
11 Transmitting file data ..
12 Committed revision 2.
12 Committed revision 2.
13 % convert to hg once
13 % convert to hg once
14 assuming destination trunk-hg
14 assuming destination trunk-hg
15 initializing destination trunk-hg repository
15 initializing destination trunk-hg repository
16 scanning source...
16 scanning source...
17 sorting...
17 sorting...
18 converting...
18 converting...
19 1 init
19 1 init
20 0 changea
20 0 changea
21 % update svn repository again
21 % update svn repository again
22 Sending a
22 Sending a
23 Sending b
23 Sending b
24 Transmitting file data ..
24 Transmitting file data ..
25 Committed revision 3.
25 Committed revision 3.
26 % test incremental conversion
26 % test incremental conversion
27 assuming destination trunk-hg
27 assuming destination trunk-hg
28 destination trunk-hg is a Mercurial repository
28 destination trunk-hg is a Mercurial repository
29 scanning source...
29 scanning source...
30 sorting...
30 sorting...
31 converting...
31 converting...
32 0 changeb
32 0 changeb
33 % test filemap
33 % test filemap
34 initializing destination fmap repository
34 initializing destination fmap repository
35 scanning source...
35 scanning source...
36 sorting...
36 sorting...
37 converting...
37 converting...
38 2 init
38 2 init
39 1 changea
39 1 changea
40 0 changeb
40 0 changeb
41 o 1 changeb files: b
41 o 1 changeb files: b
42 |
42 |
43 o 0 changea files: b
43 o 0 changea files: b
44
44
45 # now tests that it works with trunk/branches/tags layout
46
47 % initial svn import
48 Adding projA/trunk
49 Adding projA/branches
50 Adding projA/tags
51
52 Committed revision 4.
53 % update svn repository
54 Checked out revision 4.
55 A letter.txt
56 Adding letter.txt
57 Transmitting file data .
58 Committed revision 5.
59 Sending letter.txt
60 Transmitting file data .
61 Committed revision 6.
62
63 Committed revision 7.
64 Sending letter.txt
65 Transmitting file data .
66 Committed revision 8.
67 % convert to hg once
68 initializing destination A-hg repository
69 scanning source...
70 sorting...
71 converting...
72 3 init projA
73 2 hello
74 1 world
75 0 nice day
76 updating tags
77 % update svn repository again
78 A letter2.txt
79 Sending letter.txt
80 Adding letter2.txt
81 Transmitting file data ..
82 Committed revision 9.
83
84 Committed revision 10.
85 Sending letter2.txt
86 Transmitting file data .
87 Committed revision 11.
88 % test incremental conversion
89 destination A-hg is a Mercurial repository
90 scanning source...
91 sorting...
92 converting...
93 1 second letter
94 0 work in progress
95 updating tags
96 o 7 update tags files: .hgtags
97 |
98 o 6 work in progress files: letter2.txt
99 |
100 o 5 second letter files: letter.txt letter2.txt
101 |
102 o 4 update tags files: .hgtags
103 |
104 o 3 nice day files: letter.txt
105 |
106 o 2 world files: letter.txt
107 |
108 o 1 hello files: letter.txt
109 |
110 o 0 init projA files:
111
112 tip
113 v0.2
114 v0.1
General Comments 0
You need to be logged in to leave comments. Login now