##// END OF EJS Templates
convert: separate trunk detection from branch layout detection...
Edouard Gomez -
r5854:8b95f598 default
parent child Browse files
Show More
@@ -1,928 +1,930
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
24
25 from mercurial import strutil, util
25 from mercurial import strutil, util
26 from mercurial.i18n import _
26 from mercurial.i18n import _
27
27
28 # Subversion stuff. Works best with very recent Python SVN bindings
28 # Subversion stuff. Works best with very recent Python SVN bindings
29 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
29 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
30 # these bindings.
30 # these bindings.
31
31
32 from cStringIO import StringIO
32 from cStringIO import StringIO
33
33
34 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
34 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
35 from common import commandline, converter_sink, mapfile
35 from common import commandline, converter_sink, mapfile
36
36
37 try:
37 try:
38 from svn.core import SubversionException, Pool
38 from svn.core import SubversionException, Pool
39 import svn
39 import svn
40 import svn.client
40 import svn.client
41 import svn.core
41 import svn.core
42 import svn.ra
42 import svn.ra
43 import svn.delta
43 import svn.delta
44 import transport
44 import transport
45 except ImportError:
45 except ImportError:
46 pass
46 pass
47
47
48 def geturl(path):
48 def geturl(path):
49 try:
49 try:
50 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
50 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
51 except SubversionException:
51 except SubversionException:
52 pass
52 pass
53 if os.path.isdir(path):
53 if os.path.isdir(path):
54 path = os.path.normpath(os.path.abspath(path))
54 path = os.path.normpath(os.path.abspath(path))
55 if os.name == 'nt':
55 if os.name == 'nt':
56 path = '/' + util.normpath(path)
56 path = '/' + util.normpath(path)
57 return 'file://%s' % path
57 return 'file://%s' % path
58 return path
58 return path
59
59
60 def optrev(number):
60 def optrev(number):
61 optrev = svn.core.svn_opt_revision_t()
61 optrev = svn.core.svn_opt_revision_t()
62 optrev.kind = svn.core.svn_opt_revision_number
62 optrev.kind = svn.core.svn_opt_revision_number
63 optrev.value.number = number
63 optrev.value.number = number
64 return optrev
64 return optrev
65
65
66 class changedpath(object):
66 class changedpath(object):
67 def __init__(self, p):
67 def __init__(self, p):
68 self.copyfrom_path = p.copyfrom_path
68 self.copyfrom_path = p.copyfrom_path
69 self.copyfrom_rev = p.copyfrom_rev
69 self.copyfrom_rev = p.copyfrom_rev
70 self.action = p.action
70 self.action = p.action
71
71
72 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
72 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
73 strict_node_history=False):
73 strict_node_history=False):
74 protocol = -1
74 protocol = -1
75 def receiver(orig_paths, revnum, author, date, message, pool):
75 def receiver(orig_paths, revnum, author, date, message, pool):
76 if orig_paths is not None:
76 if orig_paths is not None:
77 for k, v in orig_paths.iteritems():
77 for k, v in orig_paths.iteritems():
78 orig_paths[k] = changedpath(v)
78 orig_paths[k] = changedpath(v)
79 pickle.dump((orig_paths, revnum, author, date, message),
79 pickle.dump((orig_paths, revnum, author, date, message),
80 fp, protocol)
80 fp, protocol)
81
81
82 try:
82 try:
83 # Use an ra of our own so that our parent can consume
83 # Use an ra of our own so that our parent can consume
84 # our results without confusing the server.
84 # our results without confusing the server.
85 t = transport.SvnRaTransport(url=url)
85 t = transport.SvnRaTransport(url=url)
86 svn.ra.get_log(t.ra, paths, start, end, limit,
86 svn.ra.get_log(t.ra, paths, start, end, limit,
87 discover_changed_paths,
87 discover_changed_paths,
88 strict_node_history,
88 strict_node_history,
89 receiver)
89 receiver)
90 except SubversionException, (inst, num):
90 except SubversionException, (inst, num):
91 pickle.dump(num, fp, protocol)
91 pickle.dump(num, fp, protocol)
92 else:
92 else:
93 pickle.dump(None, fp, protocol)
93 pickle.dump(None, fp, protocol)
94 fp.close()
94 fp.close()
95
95
96 def debugsvnlog(ui, **opts):
96 def debugsvnlog(ui, **opts):
97 """Fetch SVN log in a subprocess and channel them back to parent to
97 """Fetch SVN log in a subprocess and channel them back to parent to
98 avoid memory collection issues.
98 avoid memory collection issues.
99 """
99 """
100 util.set_binary(sys.stdin)
100 util.set_binary(sys.stdin)
101 util.set_binary(sys.stdout)
101 util.set_binary(sys.stdout)
102 args = decodeargs(sys.stdin.read())
102 args = decodeargs(sys.stdin.read())
103 get_log_child(sys.stdout, *args)
103 get_log_child(sys.stdout, *args)
104
104
105 # SVN conversion code stolen from bzr-svn and tailor
105 # SVN conversion code stolen from bzr-svn and tailor
106 class svn_source(converter_source):
106 class svn_source(converter_source):
107 def __init__(self, ui, url, rev=None):
107 def __init__(self, ui, url, rev=None):
108 super(svn_source, self).__init__(ui, url, rev=rev)
108 super(svn_source, self).__init__(ui, url, rev=rev)
109
109
110 try:
110 try:
111 SubversionException
111 SubversionException
112 except NameError:
112 except NameError:
113 raise NoRepo('Subversion python bindings could not be loaded')
113 raise NoRepo('Subversion python bindings could not be loaded')
114
114
115 self.encoding = locale.getpreferredencoding()
115 self.encoding = locale.getpreferredencoding()
116 self.lastrevs = {}
116 self.lastrevs = {}
117
117
118 latest = None
118 latest = None
119 try:
119 try:
120 # Support file://path@rev syntax. Useful e.g. to convert
120 # Support file://path@rev syntax. Useful e.g. to convert
121 # deleted branches.
121 # deleted branches.
122 at = url.rfind('@')
122 at = url.rfind('@')
123 if at >= 0:
123 if at >= 0:
124 latest = int(url[at+1:])
124 latest = int(url[at+1:])
125 url = url[:at]
125 url = url[:at]
126 except ValueError, e:
126 except ValueError, e:
127 pass
127 pass
128 self.url = geturl(url)
128 self.url = geturl(url)
129 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
129 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
130 try:
130 try:
131 self.transport = transport.SvnRaTransport(url=self.url)
131 self.transport = transport.SvnRaTransport(url=self.url)
132 self.ra = self.transport.ra
132 self.ra = self.transport.ra
133 self.ctx = self.transport.client
133 self.ctx = self.transport.client
134 self.base = svn.ra.get_repos_root(self.ra)
134 self.base = svn.ra.get_repos_root(self.ra)
135 self.module = self.url[len(self.base):]
135 self.module = self.url[len(self.base):]
136 self.modulemap = {} # revision, module
136 self.modulemap = {} # revision, module
137 self.commits = {}
137 self.commits = {}
138 self.paths = {}
138 self.paths = {}
139 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
139 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
140 except SubversionException, e:
140 except SubversionException, e:
141 ui.print_exc()
141 ui.print_exc()
142 raise NoRepo("%s does not look like a Subversion repo" % self.url)
142 raise NoRepo("%s does not look like a Subversion repo" % self.url)
143
143
144 if rev:
144 if rev:
145 try:
145 try:
146 latest = int(rev)
146 latest = int(rev)
147 except ValueError:
147 except ValueError:
148 raise util.Abort('svn: revision %s is not an integer' % rev)
148 raise util.Abort('svn: revision %s is not an integer' % rev)
149
149
150 try:
150 try:
151 self.get_blacklist()
151 self.get_blacklist()
152 except IOError, e:
152 except IOError, e:
153 pass
153 pass
154
154
155 self.last_changed = self.latest(self.module, latest)
155 self.last_changed = self.latest(self.module, latest)
156
156
157 self.head = self.revid(self.last_changed)
157 self.head = self.revid(self.last_changed)
158 self._changescache = None
158 self._changescache = None
159
159
160 if os.path.exists(os.path.join(url, '.svn/entries')):
160 if os.path.exists(os.path.join(url, '.svn/entries')):
161 self.wc = url
161 self.wc = url
162 else:
162 else:
163 self.wc = None
163 self.wc = None
164 self.convertfp = None
164 self.convertfp = None
165
165
166 def setrevmap(self, revmap):
166 def setrevmap(self, revmap):
167 lastrevs = {}
167 lastrevs = {}
168 for revid in revmap.iterkeys():
168 for revid in revmap.iterkeys():
169 uuid, module, revnum = self.revsplit(revid)
169 uuid, module, revnum = self.revsplit(revid)
170 lastrevnum = lastrevs.setdefault(module, revnum)
170 lastrevnum = lastrevs.setdefault(module, revnum)
171 if revnum > lastrevnum:
171 if revnum > lastrevnum:
172 lastrevs[module] = revnum
172 lastrevs[module] = revnum
173 self.lastrevs = lastrevs
173 self.lastrevs = lastrevs
174
174
175 def exists(self, path, optrev):
175 def exists(self, path, optrev):
176 try:
176 try:
177 svn.client.ls(self.url.rstrip('/') + '/' + path,
177 svn.client.ls(self.url.rstrip('/') + '/' + path,
178 optrev, False, self.ctx)
178 optrev, False, self.ctx)
179 return True
179 return True
180 except SubversionException, err:
180 except SubversionException, err:
181 return False
181 return False
182
182
183 def getheads(self):
183 def getheads(self):
184 # detect standard /branches, /tags, /trunk layout
184
185 def getcfgpath(name, rev):
186 cfgpath = self.ui.config('convert', 'svn.' + name)
187 path = (cfgpath or name).strip('/')
188 if not self.exists(path, rev):
189 if cfgpath:
190 raise util.Abort(_('expected %s to be at %r, but not found')
191 % (name, path))
192 return None
193 self.ui.note(_('found %s at %r\n') % (name, path))
194 return path
195
185 rev = optrev(self.last_changed)
196 rev = optrev(self.last_changed)
186 rpath = self.url.strip('/')
197 oldmodule = ''
187 cfgtrunk = self.ui.config('convert', 'svn.trunk')
198 trunk = getcfgpath('trunk', rev)
188 cfgbranches = self.ui.config('convert', 'svn.branches')
199 tags = getcfgpath('tags', rev)
189 cfgtags = self.ui.config('convert', 'svn.tags')
200 branches = getcfgpath('branches', rev)
190 trunk = (cfgtrunk or 'trunk').strip('/')
201
191 branches = (cfgbranches or 'branches').strip('/')
202 # If the project has a trunk or branches, we will extract heads
192 tags = (cfgtags or 'tags').strip('/')
203 # from them. We keep the project root otherwise.
193 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
204 if trunk:
194 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
205 oldmodule = self.module or ''
195 (trunk, branches, tags))
196 oldmodule = self.module
197 self.module += '/' + trunk
206 self.module += '/' + trunk
198 lt = self.latest(self.module, self.last_changed)
207 lt = self.latest(self.module, self.last_changed)
199 self.head = self.revid(lt)
208 self.head = self.revid(lt)
209
210 # First head in the list is the module's head
200 self.heads = [self.head]
211 self.heads = [self.head]
212 self.tags = '%s/%s' % (oldmodule , (tags or 'tags'))
213
214 # Check if branches bring a few more heads to the list
215 if branches:
216 rpath = self.url.strip('/')
201 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
217 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
202 self.ctx)
218 self.ctx)
203 for branch in branchnames.keys():
219 for branch in branchnames.keys():
204 if oldmodule:
220 module = '%s/%s/%s' % (oldmodule, branches, branch)
205 module = oldmodule + '/' + branches + '/' + branch
206 else:
207 module = '/' + branches + '/' + branch
208 brevnum = self.latest(module, self.last_changed)
221 brevnum = self.latest(module, self.last_changed)
209 brev = self.revid(brevnum, module)
222 brev = self.revid(brevnum, module)
210 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
223 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
211 self.heads.append(brev)
224 self.heads.append(brev)
212
225
213 if oldmodule:
214 self.tags = '%s/%s' % (oldmodule, tags)
215 else:
216 self.tags = '/%s' % tags
217
218 elif cfgtrunk or cfgbranches or cfgtags:
219 raise util.Abort('trunk/branch/tags layout expected, but not found')
220 else:
221 self.ui.note('working with one branch\n')
222 self.heads = [self.head]
223 self.tags = tags
224 return self.heads
226 return self.heads
225
227
226 def getfile(self, file, rev):
228 def getfile(self, file, rev):
227 data, mode = self._getfile(file, rev)
229 data, mode = self._getfile(file, rev)
228 self.modecache[(file, rev)] = mode
230 self.modecache[(file, rev)] = mode
229 return data
231 return data
230
232
231 def getmode(self, file, rev):
233 def getmode(self, file, rev):
232 return self.modecache[(file, rev)]
234 return self.modecache[(file, rev)]
233
235
234 def getchanges(self, rev):
236 def getchanges(self, rev):
235 if self._changescache and self._changescache[0] == rev:
237 if self._changescache and self._changescache[0] == rev:
236 return self._changescache[1]
238 return self._changescache[1]
237 self._changescache = None
239 self._changescache = None
238 self.modecache = {}
240 self.modecache = {}
239 (paths, parents) = self.paths[rev]
241 (paths, parents) = self.paths[rev]
240 files, copies = self.expandpaths(rev, paths, parents)
242 files, copies = self.expandpaths(rev, paths, parents)
241 files.sort()
243 files.sort()
242 files = zip(files, [rev] * len(files))
244 files = zip(files, [rev] * len(files))
243
245
244 # caller caches the result, so free it here to release memory
246 # caller caches the result, so free it here to release memory
245 del self.paths[rev]
247 del self.paths[rev]
246 return (files, copies)
248 return (files, copies)
247
249
248 def getchangedfiles(self, rev, i):
250 def getchangedfiles(self, rev, i):
249 changes = self.getchanges(rev)
251 changes = self.getchanges(rev)
250 self._changescache = (rev, changes)
252 self._changescache = (rev, changes)
251 return [f[0] for f in changes[0]]
253 return [f[0] for f in changes[0]]
252
254
253 def getcommit(self, rev):
255 def getcommit(self, rev):
254 if rev not in self.commits:
256 if rev not in self.commits:
255 uuid, module, revnum = self.revsplit(rev)
257 uuid, module, revnum = self.revsplit(rev)
256 self.module = module
258 self.module = module
257 self.reparent(module)
259 self.reparent(module)
258 stop = self.lastrevs.get(module, 0)
260 stop = self.lastrevs.get(module, 0)
259 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
261 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
260 commit = self.commits[rev]
262 commit = self.commits[rev]
261 # caller caches the result, so free it here to release memory
263 # caller caches the result, so free it here to release memory
262 del self.commits[rev]
264 del self.commits[rev]
263 return commit
265 return commit
264
266
265 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
267 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
266 strict_node_history=False):
268 strict_node_history=False):
267
269
268 def parent(fp):
270 def parent(fp):
269 while True:
271 while True:
270 entry = pickle.load(fp)
272 entry = pickle.load(fp)
271 try:
273 try:
272 orig_paths, revnum, author, date, message = entry
274 orig_paths, revnum, author, date, message = entry
273 except:
275 except:
274 if entry is None:
276 if entry is None:
275 break
277 break
276 raise SubversionException("child raised exception", entry)
278 raise SubversionException("child raised exception", entry)
277 yield entry
279 yield entry
278
280
279 args = [self.url, paths, start, end, limit, discover_changed_paths,
281 args = [self.url, paths, start, end, limit, discover_changed_paths,
280 strict_node_history]
282 strict_node_history]
281 arg = encodeargs(args)
283 arg = encodeargs(args)
282 hgexe = util.hgexecutable()
284 hgexe = util.hgexecutable()
283 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
285 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
284 stdin, stdout = os.popen2(cmd, 'b')
286 stdin, stdout = os.popen2(cmd, 'b')
285
287
286 stdin.write(arg)
288 stdin.write(arg)
287 stdin.close()
289 stdin.close()
288
290
289 for p in parent(stdout):
291 for p in parent(stdout):
290 yield p
292 yield p
291
293
292 def gettags(self):
294 def gettags(self):
293 tags = {}
295 tags = {}
294 start = self.revnum(self.head)
296 start = self.revnum(self.head)
295 try:
297 try:
296 for entry in self.get_log([self.tags], 0, start):
298 for entry in self.get_log([self.tags], 0, start):
297 orig_paths, revnum, author, date, message = entry
299 orig_paths, revnum, author, date, message = entry
298 for path in orig_paths:
300 for path in orig_paths:
299 if not path.startswith(self.tags+'/'):
301 if not path.startswith(self.tags+'/'):
300 continue
302 continue
301 ent = orig_paths[path]
303 ent = orig_paths[path]
302 source = ent.copyfrom_path
304 source = ent.copyfrom_path
303 rev = ent.copyfrom_rev
305 rev = ent.copyfrom_rev
304 tag = path.split('/')[-1]
306 tag = path.split('/')[-1]
305 tags[tag] = self.revid(rev, module=source)
307 tags[tag] = self.revid(rev, module=source)
306 except SubversionException, (inst, num):
308 except SubversionException, (inst, num):
307 self.ui.note('no tags found at revision %d\n' % start)
309 self.ui.note('no tags found at revision %d\n' % start)
308 return tags
310 return tags
309
311
310 def converted(self, rev, destrev):
312 def converted(self, rev, destrev):
311 if not self.wc:
313 if not self.wc:
312 return
314 return
313 if self.convertfp is None:
315 if self.convertfp is None:
314 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
316 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
315 'a')
317 'a')
316 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
318 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
317 self.convertfp.flush()
319 self.convertfp.flush()
318
320
319 # -- helper functions --
321 # -- helper functions --
320
322
321 def revid(self, revnum, module=None):
323 def revid(self, revnum, module=None):
322 if not module:
324 if not module:
323 module = self.module
325 module = self.module
324 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
326 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
325 revnum)
327 revnum)
326
328
327 def revnum(self, rev):
329 def revnum(self, rev):
328 return int(rev.split('@')[-1])
330 return int(rev.split('@')[-1])
329
331
330 def revsplit(self, rev):
332 def revsplit(self, rev):
331 url, revnum = rev.encode(self.encoding).split('@', 1)
333 url, revnum = rev.encode(self.encoding).split('@', 1)
332 revnum = int(revnum)
334 revnum = int(revnum)
333 parts = url.split('/', 1)
335 parts = url.split('/', 1)
334 uuid = parts.pop(0)[4:]
336 uuid = parts.pop(0)[4:]
335 mod = ''
337 mod = ''
336 if parts:
338 if parts:
337 mod = '/' + parts[0]
339 mod = '/' + parts[0]
338 return uuid, mod, revnum
340 return uuid, mod, revnum
339
341
340 def latest(self, path, stop=0):
342 def latest(self, path, stop=0):
341 'find the latest revision affecting path, up to stop'
343 'find the latest revision affecting path, up to stop'
342 if not stop:
344 if not stop:
343 stop = svn.ra.get_latest_revnum(self.ra)
345 stop = svn.ra.get_latest_revnum(self.ra)
344 try:
346 try:
345 self.reparent('')
347 self.reparent('')
346 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
348 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
347 self.reparent(self.module)
349 self.reparent(self.module)
348 except SubversionException:
350 except SubversionException:
349 dirent = None
351 dirent = None
350 if not dirent:
352 if not dirent:
351 raise util.Abort('%s not found up to revision %d' % (path, stop))
353 raise util.Abort('%s not found up to revision %d' % (path, stop))
352
354
353 return dirent.created_rev
355 return dirent.created_rev
354
356
355 def get_blacklist(self):
357 def get_blacklist(self):
356 """Avoid certain revision numbers.
358 """Avoid certain revision numbers.
357 It is not uncommon for two nearby revisions to cancel each other
359 It is not uncommon for two nearby revisions to cancel each other
358 out, e.g. 'I copied trunk into a subdirectory of itself instead
360 out, e.g. 'I copied trunk into a subdirectory of itself instead
359 of making a branch'. The converted repository is significantly
361 of making a branch'. The converted repository is significantly
360 smaller if we ignore such revisions."""
362 smaller if we ignore such revisions."""
361 self.blacklist = util.set()
363 self.blacklist = util.set()
362 blacklist = self.blacklist
364 blacklist = self.blacklist
363 for line in file("blacklist.txt", "r"):
365 for line in file("blacklist.txt", "r"):
364 if not line.startswith("#"):
366 if not line.startswith("#"):
365 try:
367 try:
366 svn_rev = int(line.strip())
368 svn_rev = int(line.strip())
367 blacklist.add(svn_rev)
369 blacklist.add(svn_rev)
368 except ValueError, e:
370 except ValueError, e:
369 pass # not an integer or a comment
371 pass # not an integer or a comment
370
372
371 def is_blacklisted(self, svn_rev):
373 def is_blacklisted(self, svn_rev):
372 return svn_rev in self.blacklist
374 return svn_rev in self.blacklist
373
375
374 def reparent(self, module):
376 def reparent(self, module):
375 svn_url = self.base + module
377 svn_url = self.base + module
376 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
378 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
377 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
379 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
378
380
379 def expandpaths(self, rev, paths, parents):
381 def expandpaths(self, rev, paths, parents):
380 def get_entry_from_path(path, module=self.module):
382 def get_entry_from_path(path, module=self.module):
381 # Given the repository url of this wc, say
383 # Given the repository url of this wc, say
382 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
384 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
383 # extract the "entry" portion (a relative path) from what
385 # extract the "entry" portion (a relative path) from what
384 # svn log --xml says, ie
386 # svn log --xml says, ie
385 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
387 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
386 # that is to say "tests/PloneTestCase.py"
388 # that is to say "tests/PloneTestCase.py"
387 if path.startswith(module):
389 if path.startswith(module):
388 relative = path[len(module):]
390 relative = path[len(module):]
389 if relative.startswith('/'):
391 if relative.startswith('/'):
390 return relative[1:]
392 return relative[1:]
391 else:
393 else:
392 return relative
394 return relative
393
395
394 # The path is outside our tracked tree...
396 # The path is outside our tracked tree...
395 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
397 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
396 return None
398 return None
397
399
398 entries = []
400 entries = []
399 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
401 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
400 copies = {}
402 copies = {}
401 revnum = self.revnum(rev)
403 revnum = self.revnum(rev)
402
404
403 if revnum in self.modulemap:
405 if revnum in self.modulemap:
404 new_module = self.modulemap[revnum]
406 new_module = self.modulemap[revnum]
405 if new_module != self.module:
407 if new_module != self.module:
406 self.module = new_module
408 self.module = new_module
407 self.reparent(self.module)
409 self.reparent(self.module)
408
410
409 for path, ent in paths:
411 for path, ent in paths:
410 entrypath = get_entry_from_path(path, module=self.module)
412 entrypath = get_entry_from_path(path, module=self.module)
411 entry = entrypath.decode(self.encoding)
413 entry = entrypath.decode(self.encoding)
412
414
413 kind = svn.ra.check_path(self.ra, entrypath, revnum)
415 kind = svn.ra.check_path(self.ra, entrypath, revnum)
414 if kind == svn.core.svn_node_file:
416 if kind == svn.core.svn_node_file:
415 if ent.copyfrom_path:
417 if ent.copyfrom_path:
416 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
418 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
417 if copyfrom_path:
419 if copyfrom_path:
418 self.ui.debug("Copied to %s from %s@%s\n" %
420 self.ui.debug("Copied to %s from %s@%s\n" %
419 (entrypath, copyfrom_path,
421 (entrypath, copyfrom_path,
420 ent.copyfrom_rev))
422 ent.copyfrom_rev))
421 # It's probably important for hg that the source
423 # It's probably important for hg that the source
422 # exists in the revision's parent, not just the
424 # exists in the revision's parent, not just the
423 # ent.copyfrom_rev
425 # ent.copyfrom_rev
424 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
426 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
425 if fromkind != 0:
427 if fromkind != 0:
426 copies[self.recode(entry)] = self.recode(copyfrom_path)
428 copies[self.recode(entry)] = self.recode(copyfrom_path)
427 entries.append(self.recode(entry))
429 entries.append(self.recode(entry))
428 elif kind == 0: # gone, but had better be a deleted *file*
430 elif kind == 0: # gone, but had better be a deleted *file*
429 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
431 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
430
432
431 # if a branch is created but entries are removed in the same
433 # if a branch is created but entries are removed in the same
432 # changeset, get the right fromrev
434 # changeset, get the right fromrev
433 if parents:
435 if parents:
434 uuid, old_module, fromrev = self.revsplit(parents[0])
436 uuid, old_module, fromrev = self.revsplit(parents[0])
435 else:
437 else:
436 fromrev = revnum - 1
438 fromrev = revnum - 1
437 # might always need to be revnum - 1 in these 3 lines?
439 # might always need to be revnum - 1 in these 3 lines?
438 old_module = self.modulemap.get(fromrev, self.module)
440 old_module = self.modulemap.get(fromrev, self.module)
439
441
440 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
442 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
441 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
443 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
442
444
443 def lookup_parts(p):
445 def lookup_parts(p):
444 rc = None
446 rc = None
445 parts = p.split("/")
447 parts = p.split("/")
446 for i in range(len(parts)):
448 for i in range(len(parts)):
447 part = "/".join(parts[:i])
449 part = "/".join(parts[:i])
448 info = part, copyfrom.get(part, None)
450 info = part, copyfrom.get(part, None)
449 if info[1] is not None:
451 if info[1] is not None:
450 self.ui.debug("Found parent directory %s\n" % info[1])
452 self.ui.debug("Found parent directory %s\n" % info[1])
451 rc = info
453 rc = info
452 return rc
454 return rc
453
455
454 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
456 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
455
457
456 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
458 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
457
459
458 # need to remove fragment from lookup_parts and replace with copyfrom_path
460 # need to remove fragment from lookup_parts and replace with copyfrom_path
459 if frompath is not None:
461 if frompath is not None:
460 self.ui.debug("munge-o-matic\n")
462 self.ui.debug("munge-o-matic\n")
461 self.ui.debug(entrypath + '\n')
463 self.ui.debug(entrypath + '\n')
462 self.ui.debug(entrypath[len(frompath):] + '\n')
464 self.ui.debug(entrypath[len(frompath):] + '\n')
463 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
465 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
464 fromrev = froment.copyfrom_rev
466 fromrev = froment.copyfrom_rev
465 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
467 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
466
468
467 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
469 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
468 if fromkind == svn.core.svn_node_file: # a deleted file
470 if fromkind == svn.core.svn_node_file: # a deleted file
469 entries.append(self.recode(entry))
471 entries.append(self.recode(entry))
470 elif fromkind == svn.core.svn_node_dir:
472 elif fromkind == svn.core.svn_node_dir:
471 # print "Deleted/moved non-file:", revnum, path, ent
473 # print "Deleted/moved non-file:", revnum, path, ent
472 # children = self._find_children(path, revnum - 1)
474 # children = self._find_children(path, revnum - 1)
473 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
475 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
474 # Sometimes this is tricky. For example: in
476 # Sometimes this is tricky. For example: in
475 # The Subversion Repository revision 6940 a dir
477 # The Subversion Repository revision 6940 a dir
476 # was copied and one of its files was deleted
478 # was copied and one of its files was deleted
477 # from the new location in the same commit. This
479 # from the new location in the same commit. This
478 # code can't deal with that yet.
480 # code can't deal with that yet.
479 if ent.action == 'C':
481 if ent.action == 'C':
480 children = self._find_children(path, fromrev)
482 children = self._find_children(path, fromrev)
481 else:
483 else:
482 oroot = entrypath.strip('/')
484 oroot = entrypath.strip('/')
483 nroot = path.strip('/')
485 nroot = path.strip('/')
484 children = self._find_children(oroot, fromrev)
486 children = self._find_children(oroot, fromrev)
485 children = [s.replace(oroot,nroot) for s in children]
487 children = [s.replace(oroot,nroot) for s in children]
486 # Mark all [files, not directories] as deleted.
488 # Mark all [files, not directories] as deleted.
487 for child in children:
489 for child in children:
488 # Can we move a child directory and its
490 # Can we move a child directory and its
489 # parent in the same commit? (probably can). Could
491 # parent in the same commit? (probably can). Could
490 # cause problems if instead of revnum -1,
492 # cause problems if instead of revnum -1,
491 # we have to look in (copyfrom_path, revnum - 1)
493 # we have to look in (copyfrom_path, revnum - 1)
492 entrypath = get_entry_from_path("/" + child, module=old_module)
494 entrypath = get_entry_from_path("/" + child, module=old_module)
493 if entrypath:
495 if entrypath:
494 entry = self.recode(entrypath.decode(self.encoding))
496 entry = self.recode(entrypath.decode(self.encoding))
495 if entry in copies:
497 if entry in copies:
496 # deleted file within a copy
498 # deleted file within a copy
497 del copies[entry]
499 del copies[entry]
498 else:
500 else:
499 entries.append(entry)
501 entries.append(entry)
500 else:
502 else:
501 self.ui.debug('unknown path in revision %d: %s\n' % \
503 self.ui.debug('unknown path in revision %d: %s\n' % \
502 (revnum, path))
504 (revnum, path))
503 elif kind == svn.core.svn_node_dir:
505 elif kind == svn.core.svn_node_dir:
504 # Should probably synthesize normal file entries
506 # Should probably synthesize normal file entries
505 # and handle as above to clean up copy/rename handling.
507 # and handle as above to clean up copy/rename handling.
506
508
507 # If the directory just had a prop change,
509 # If the directory just had a prop change,
508 # then we shouldn't need to look for its children.
510 # then we shouldn't need to look for its children.
509 # Also this could create duplicate entries. Not sure
511 # Also this could create duplicate entries. Not sure
510 # whether this will matter. Maybe should make entries a set.
512 # whether this will matter. Maybe should make entries a set.
511 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
513 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
512 # This will fail if a directory was copied
514 # This will fail if a directory was copied
513 # from another branch and then some of its files
515 # from another branch and then some of its files
514 # were deleted in the same transaction.
516 # were deleted in the same transaction.
515 children = self._find_children(path, revnum)
517 children = self._find_children(path, revnum)
516 children.sort()
518 children.sort()
517 for child in children:
519 for child in children:
518 # Can we move a child directory and its
520 # Can we move a child directory and its
519 # parent in the same commit? (probably can). Could
521 # parent in the same commit? (probably can). Could
520 # cause problems if instead of revnum -1,
522 # cause problems if instead of revnum -1,
521 # we have to look in (copyfrom_path, revnum - 1)
523 # we have to look in (copyfrom_path, revnum - 1)
522 entrypath = get_entry_from_path("/" + child, module=self.module)
524 entrypath = get_entry_from_path("/" + child, module=self.module)
523 # print child, self.module, entrypath
525 # print child, self.module, entrypath
524 if entrypath:
526 if entrypath:
525 # Need to filter out directories here...
527 # Need to filter out directories here...
526 kind = svn.ra.check_path(self.ra, entrypath, revnum)
528 kind = svn.ra.check_path(self.ra, entrypath, revnum)
527 if kind != svn.core.svn_node_dir:
529 if kind != svn.core.svn_node_dir:
528 entries.append(self.recode(entrypath))
530 entries.append(self.recode(entrypath))
529
531
530 # Copies here (must copy all from source)
532 # Copies here (must copy all from source)
531 # Probably not a real problem for us if
533 # Probably not a real problem for us if
532 # source does not exist
534 # source does not exist
533
535
534 # Can do this with the copy command "hg copy"
536 # Can do this with the copy command "hg copy"
535 # if ent.copyfrom_path:
537 # if ent.copyfrom_path:
536 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
538 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
537 # module=self.module)
539 # module=self.module)
538 # copyto_entry = entrypath
540 # copyto_entry = entrypath
539 #
541 #
540 # print "copy directory", copyfrom_entry, 'to', copyto_entry
542 # print "copy directory", copyfrom_entry, 'to', copyto_entry
541 #
543 #
542 # copies.append((copyfrom_entry, copyto_entry))
544 # copies.append((copyfrom_entry, copyto_entry))
543
545
544 if ent.copyfrom_path:
546 if ent.copyfrom_path:
545 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
547 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
546 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
548 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
547 if copyfrom_entry:
549 if copyfrom_entry:
548 copyfrom[path] = ent
550 copyfrom[path] = ent
549 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
551 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
550
552
551 # Good, /probably/ a regular copy. Really should check
553 # Good, /probably/ a regular copy. Really should check
552 # to see whether the parent revision actually contains
554 # to see whether the parent revision actually contains
553 # the directory in question.
555 # the directory in question.
554 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
556 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
555 children.sort()
557 children.sort()
556 for child in children:
558 for child in children:
557 entrypath = get_entry_from_path("/" + child, module=self.module)
559 entrypath = get_entry_from_path("/" + child, module=self.module)
558 if entrypath:
560 if entrypath:
559 entry = entrypath.decode(self.encoding)
561 entry = entrypath.decode(self.encoding)
560 # print "COPY COPY From", copyfrom_entry, entry
562 # print "COPY COPY From", copyfrom_entry, entry
561 copyto_path = path + entry[len(copyfrom_entry):]
563 copyto_path = path + entry[len(copyfrom_entry):]
562 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
564 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
563 # print "COPY", entry, "COPY To", copyto_entry
565 # print "COPY", entry, "COPY To", copyto_entry
564 copies[self.recode(copyto_entry)] = self.recode(entry)
566 copies[self.recode(copyto_entry)] = self.recode(entry)
565 # copy from quux splort/quuxfile
567 # copy from quux splort/quuxfile
566
568
567 return (entries, copies)
569 return (entries, copies)
568
570
569 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
571 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
570 self.child_cset = None
572 self.child_cset = None
571 def parselogentry(orig_paths, revnum, author, date, message):
573 def parselogentry(orig_paths, revnum, author, date, message):
572 self.ui.debug("parsing revision %d (%d changes)\n" %
574 self.ui.debug("parsing revision %d (%d changes)\n" %
573 (revnum, len(orig_paths)))
575 (revnum, len(orig_paths)))
574
576
575 if revnum in self.modulemap:
577 if revnum in self.modulemap:
576 new_module = self.modulemap[revnum]
578 new_module = self.modulemap[revnum]
577 if new_module != self.module:
579 if new_module != self.module:
578 self.module = new_module
580 self.module = new_module
579 self.reparent(self.module)
581 self.reparent(self.module)
580
582
581 rev = self.revid(revnum)
583 rev = self.revid(revnum)
582 # branch log might return entries for a parent we already have
584 # branch log might return entries for a parent we already have
583 if (rev in self.commits or
585 if (rev in self.commits or
584 (revnum < self.lastrevs.get(self.module, 0))):
586 (revnum < self.lastrevs.get(self.module, 0))):
585 return
587 return
586
588
587 parents = []
589 parents = []
588 # check whether this revision is the start of a branch
590 # check whether this revision is the start of a branch
589 if self.module in orig_paths:
591 if self.module in orig_paths:
590 ent = orig_paths[self.module]
592 ent = orig_paths[self.module]
591 if ent.copyfrom_path:
593 if ent.copyfrom_path:
592 # ent.copyfrom_rev may not be the actual last revision
594 # ent.copyfrom_rev may not be the actual last revision
593 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
595 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
594 self.modulemap[prev] = ent.copyfrom_path
596 self.modulemap[prev] = ent.copyfrom_path
595 parents = [self.revid(prev, ent.copyfrom_path)]
597 parents = [self.revid(prev, ent.copyfrom_path)]
596 self.ui.note('found parent of branch %s at %d: %s\n' % \
598 self.ui.note('found parent of branch %s at %d: %s\n' % \
597 (self.module, prev, ent.copyfrom_path))
599 (self.module, prev, ent.copyfrom_path))
598 else:
600 else:
599 self.ui.debug("No copyfrom path, don't know what to do.\n")
601 self.ui.debug("No copyfrom path, don't know what to do.\n")
600
602
601 self.modulemap[revnum] = self.module # track backwards in time
603 self.modulemap[revnum] = self.module # track backwards in time
602
604
603 orig_paths = orig_paths.items()
605 orig_paths = orig_paths.items()
604 orig_paths.sort()
606 orig_paths.sort()
605 paths = []
607 paths = []
606 # filter out unrelated paths
608 # filter out unrelated paths
607 for path, ent in orig_paths:
609 for path, ent in orig_paths:
608 if not path.startswith(self.module):
610 if not path.startswith(self.module):
609 self.ui.debug("boring@%s: %s\n" % (revnum, path))
611 self.ui.debug("boring@%s: %s\n" % (revnum, path))
610 continue
612 continue
611 paths.append((path, ent))
613 paths.append((path, ent))
612
614
613 self.paths[rev] = (paths, parents)
615 self.paths[rev] = (paths, parents)
614
616
615 # Example SVN datetime. Includes microseconds.
617 # Example SVN datetime. Includes microseconds.
616 # ISO-8601 conformant
618 # ISO-8601 conformant
617 # '2007-01-04T17:35:00.902377Z'
619 # '2007-01-04T17:35:00.902377Z'
618 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
620 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
619
621
620 log = message and self.recode(message)
622 log = message and self.recode(message)
621 author = author and self.recode(author) or ''
623 author = author and self.recode(author) or ''
622 try:
624 try:
623 branch = self.module.split("/")[-1]
625 branch = self.module.split("/")[-1]
624 if branch == 'trunk':
626 if branch == 'trunk':
625 branch = ''
627 branch = ''
626 except IndexError:
628 except IndexError:
627 branch = None
629 branch = None
628
630
629 cset = commit(author=author,
631 cset = commit(author=author,
630 date=util.datestr(date),
632 date=util.datestr(date),
631 desc=log,
633 desc=log,
632 parents=parents,
634 parents=parents,
633 branch=branch,
635 branch=branch,
634 rev=rev.encode('utf-8'))
636 rev=rev.encode('utf-8'))
635
637
636 self.commits[rev] = cset
638 self.commits[rev] = cset
637 if self.child_cset and not self.child_cset.parents:
639 if self.child_cset and not self.child_cset.parents:
638 self.child_cset.parents = [rev]
640 self.child_cset.parents = [rev]
639 self.child_cset = cset
641 self.child_cset = cset
640
642
641 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
643 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
642 (self.module, from_revnum, to_revnum))
644 (self.module, from_revnum, to_revnum))
643
645
644 try:
646 try:
645 for entry in self.get_log([self.module], from_revnum, to_revnum):
647 for entry in self.get_log([self.module], from_revnum, to_revnum):
646 orig_paths, revnum, author, date, message = entry
648 orig_paths, revnum, author, date, message = entry
647 if self.is_blacklisted(revnum):
649 if self.is_blacklisted(revnum):
648 self.ui.note('skipping blacklisted revision %d\n' % revnum)
650 self.ui.note('skipping blacklisted revision %d\n' % revnum)
649 continue
651 continue
650 if orig_paths is None:
652 if orig_paths is None:
651 self.ui.debug('revision %d has no entries\n' % revnum)
653 self.ui.debug('revision %d has no entries\n' % revnum)
652 continue
654 continue
653 parselogentry(orig_paths, revnum, author, date, message)
655 parselogentry(orig_paths, revnum, author, date, message)
654 except SubversionException, (inst, num):
656 except SubversionException, (inst, num):
655 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
657 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
656 raise NoSuchRevision(branch=self,
658 raise NoSuchRevision(branch=self,
657 revision="Revision number %d" % to_revnum)
659 revision="Revision number %d" % to_revnum)
658 raise
660 raise
659
661
660 def _getfile(self, file, rev):
662 def _getfile(self, file, rev):
661 io = StringIO()
663 io = StringIO()
662 # TODO: ra.get_file transmits the whole file instead of diffs.
664 # TODO: ra.get_file transmits the whole file instead of diffs.
663 mode = ''
665 mode = ''
664 try:
666 try:
665 revnum = self.revnum(rev)
667 revnum = self.revnum(rev)
666 if self.module != self.modulemap[revnum]:
668 if self.module != self.modulemap[revnum]:
667 self.module = self.modulemap[revnum]
669 self.module = self.modulemap[revnum]
668 self.reparent(self.module)
670 self.reparent(self.module)
669 info = svn.ra.get_file(self.ra, file, revnum, io)
671 info = svn.ra.get_file(self.ra, file, revnum, io)
670 if isinstance(info, list):
672 if isinstance(info, list):
671 info = info[-1]
673 info = info[-1]
672 mode = ("svn:executable" in info) and 'x' or ''
674 mode = ("svn:executable" in info) and 'x' or ''
673 mode = ("svn:special" in info) and 'l' or mode
675 mode = ("svn:special" in info) and 'l' or mode
674 except SubversionException, e:
676 except SubversionException, e:
675 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
677 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
676 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
678 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
677 if e.apr_err in notfound: # File not found
679 if e.apr_err in notfound: # File not found
678 raise IOError()
680 raise IOError()
679 raise
681 raise
680 data = io.getvalue()
682 data = io.getvalue()
681 if mode == 'l':
683 if mode == 'l':
682 link_prefix = "link "
684 link_prefix = "link "
683 if data.startswith(link_prefix):
685 if data.startswith(link_prefix):
684 data = data[len(link_prefix):]
686 data = data[len(link_prefix):]
685 return data, mode
687 return data, mode
686
688
687 def _find_children(self, path, revnum):
689 def _find_children(self, path, revnum):
688 path = path.strip('/')
690 path = path.strip('/')
689 pool = Pool()
691 pool = Pool()
690 rpath = '/'.join([self.base, path]).strip('/')
692 rpath = '/'.join([self.base, path]).strip('/')
691 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
693 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
692
694
693 pre_revprop_change = '''#!/bin/sh
695 pre_revprop_change = '''#!/bin/sh
694
696
695 REPOS="$1"
697 REPOS="$1"
696 REV="$2"
698 REV="$2"
697 USER="$3"
699 USER="$3"
698 PROPNAME="$4"
700 PROPNAME="$4"
699 ACTION="$5"
701 ACTION="$5"
700
702
701 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
703 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
702 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
704 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
703 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
705 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
704
706
705 echo "Changing prohibited revision property" >&2
707 echo "Changing prohibited revision property" >&2
706 exit 1
708 exit 1
707 '''
709 '''
708
710
709 class svn_sink(converter_sink, commandline):
711 class svn_sink(converter_sink, commandline):
710 commit_re = re.compile(r'Committed revision (\d+).', re.M)
712 commit_re = re.compile(r'Committed revision (\d+).', re.M)
711
713
712 def prerun(self):
714 def prerun(self):
713 if self.wc:
715 if self.wc:
714 os.chdir(self.wc)
716 os.chdir(self.wc)
715
717
716 def postrun(self):
718 def postrun(self):
717 if self.wc:
719 if self.wc:
718 os.chdir(self.cwd)
720 os.chdir(self.cwd)
719
721
720 def join(self, name):
722 def join(self, name):
721 return os.path.join(self.wc, '.svn', name)
723 return os.path.join(self.wc, '.svn', name)
722
724
723 def revmapfile(self):
725 def revmapfile(self):
724 return self.join('hg-shamap')
726 return self.join('hg-shamap')
725
727
726 def authorfile(self):
728 def authorfile(self):
727 return self.join('hg-authormap')
729 return self.join('hg-authormap')
728
730
729 def __init__(self, ui, path):
731 def __init__(self, ui, path):
730 converter_sink.__init__(self, ui, path)
732 converter_sink.__init__(self, ui, path)
731 commandline.__init__(self, ui, 'svn')
733 commandline.__init__(self, ui, 'svn')
732 self.delete = []
734 self.delete = []
733 self.setexec = []
735 self.setexec = []
734 self.delexec = []
736 self.delexec = []
735 self.copies = []
737 self.copies = []
736 self.wc = None
738 self.wc = None
737 self.cwd = os.getcwd()
739 self.cwd = os.getcwd()
738
740
739 path = os.path.realpath(path)
741 path = os.path.realpath(path)
740
742
741 created = False
743 created = False
742 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
744 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
743 self.wc = path
745 self.wc = path
744 self.run0('update')
746 self.run0('update')
745 else:
747 else:
746 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
748 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
747
749
748 if os.path.isdir(os.path.dirname(path)):
750 if os.path.isdir(os.path.dirname(path)):
749 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
751 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
750 ui.status(_('initializing svn repo %r\n') %
752 ui.status(_('initializing svn repo %r\n') %
751 os.path.basename(path))
753 os.path.basename(path))
752 commandline(ui, 'svnadmin').run0('create', path)
754 commandline(ui, 'svnadmin').run0('create', path)
753 created = path
755 created = path
754 path = util.normpath(path)
756 path = util.normpath(path)
755 if not path.startswith('/'):
757 if not path.startswith('/'):
756 path = '/' + path
758 path = '/' + path
757 path = 'file://' + path
759 path = 'file://' + path
758
760
759 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
761 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
760 self.run0('checkout', path, wcpath)
762 self.run0('checkout', path, wcpath)
761
763
762 self.wc = wcpath
764 self.wc = wcpath
763 self.opener = util.opener(self.wc)
765 self.opener = util.opener(self.wc)
764 self.wopener = util.opener(self.wc)
766 self.wopener = util.opener(self.wc)
765 self.childmap = mapfile(ui, self.join('hg-childmap'))
767 self.childmap = mapfile(ui, self.join('hg-childmap'))
766 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
768 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
767
769
768 if created:
770 if created:
769 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
771 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
770 fp = open(hook, 'w')
772 fp = open(hook, 'w')
771 fp.write(pre_revprop_change)
773 fp.write(pre_revprop_change)
772 fp.close()
774 fp.close()
773 util.set_flags(hook, "x")
775 util.set_flags(hook, "x")
774
776
775 xport = transport.SvnRaTransport(url=geturl(path))
777 xport = transport.SvnRaTransport(url=geturl(path))
776 self.uuid = svn.ra.get_uuid(xport.ra)
778 self.uuid = svn.ra.get_uuid(xport.ra)
777
779
778 def wjoin(self, *names):
780 def wjoin(self, *names):
779 return os.path.join(self.wc, *names)
781 return os.path.join(self.wc, *names)
780
782
781 def putfile(self, filename, flags, data):
783 def putfile(self, filename, flags, data):
782 if 'l' in flags:
784 if 'l' in flags:
783 self.wopener.symlink(data, filename)
785 self.wopener.symlink(data, filename)
784 else:
786 else:
785 try:
787 try:
786 if os.path.islink(self.wjoin(filename)):
788 if os.path.islink(self.wjoin(filename)):
787 os.unlink(filename)
789 os.unlink(filename)
788 except OSError:
790 except OSError:
789 pass
791 pass
790 self.wopener(filename, 'w').write(data)
792 self.wopener(filename, 'w').write(data)
791
793
792 if self.is_exec:
794 if self.is_exec:
793 was_exec = self.is_exec(self.wjoin(filename))
795 was_exec = self.is_exec(self.wjoin(filename))
794 else:
796 else:
795 # On filesystems not supporting execute-bit, there is no way
797 # On filesystems not supporting execute-bit, there is no way
796 # to know if it is set but asking subversion. Setting it
798 # to know if it is set but asking subversion. Setting it
797 # systematically is just as expensive and much simpler.
799 # systematically is just as expensive and much simpler.
798 was_exec = 'x' not in flags
800 was_exec = 'x' not in flags
799
801
800 util.set_flags(self.wjoin(filename), flags)
802 util.set_flags(self.wjoin(filename), flags)
801 if was_exec:
803 if was_exec:
802 if 'x' not in flags:
804 if 'x' not in flags:
803 self.delexec.append(filename)
805 self.delexec.append(filename)
804 else:
806 else:
805 if 'x' in flags:
807 if 'x' in flags:
806 self.setexec.append(filename)
808 self.setexec.append(filename)
807
809
808 def delfile(self, name):
810 def delfile(self, name):
809 self.delete.append(name)
811 self.delete.append(name)
810
812
811 def copyfile(self, source, dest):
813 def copyfile(self, source, dest):
812 self.copies.append([source, dest])
814 self.copies.append([source, dest])
813
815
814 def _copyfile(self, source, dest):
816 def _copyfile(self, source, dest):
815 # SVN's copy command pukes if the destination file exists, but
817 # SVN's copy command pukes if the destination file exists, but
816 # our copyfile method expects to record a copy that has
818 # our copyfile method expects to record a copy that has
817 # already occurred. Cross the semantic gap.
819 # already occurred. Cross the semantic gap.
818 wdest = self.wjoin(dest)
820 wdest = self.wjoin(dest)
819 exists = os.path.exists(wdest)
821 exists = os.path.exists(wdest)
820 if exists:
822 if exists:
821 fd, tempname = tempfile.mkstemp(
823 fd, tempname = tempfile.mkstemp(
822 prefix='hg-copy-', dir=os.path.dirname(wdest))
824 prefix='hg-copy-', dir=os.path.dirname(wdest))
823 os.close(fd)
825 os.close(fd)
824 os.unlink(tempname)
826 os.unlink(tempname)
825 os.rename(wdest, tempname)
827 os.rename(wdest, tempname)
826 try:
828 try:
827 self.run0('copy', source, dest)
829 self.run0('copy', source, dest)
828 finally:
830 finally:
829 if exists:
831 if exists:
830 try:
832 try:
831 os.unlink(wdest)
833 os.unlink(wdest)
832 except OSError:
834 except OSError:
833 pass
835 pass
834 os.rename(tempname, wdest)
836 os.rename(tempname, wdest)
835
837
836 def dirs_of(self, files):
838 def dirs_of(self, files):
837 dirs = set()
839 dirs = set()
838 for f in files:
840 for f in files:
839 if os.path.isdir(self.wjoin(f)):
841 if os.path.isdir(self.wjoin(f)):
840 dirs.add(f)
842 dirs.add(f)
841 for i in strutil.rfindall(f, '/'):
843 for i in strutil.rfindall(f, '/'):
842 dirs.add(f[:i])
844 dirs.add(f[:i])
843 return dirs
845 return dirs
844
846
845 def add_dirs(self, files):
847 def add_dirs(self, files):
846 add_dirs = [d for d in self.dirs_of(files)
848 add_dirs = [d for d in self.dirs_of(files)
847 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
849 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
848 if add_dirs:
850 if add_dirs:
849 add_dirs.sort()
851 add_dirs.sort()
850 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
852 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
851 return add_dirs
853 return add_dirs
852
854
853 def add_files(self, files):
855 def add_files(self, files):
854 if files:
856 if files:
855 self.xargs(files, 'add', quiet=True)
857 self.xargs(files, 'add', quiet=True)
856 return files
858 return files
857
859
858 def tidy_dirs(self, names):
860 def tidy_dirs(self, names):
859 dirs = list(self.dirs_of(names))
861 dirs = list(self.dirs_of(names))
860 dirs.sort(reverse=True)
862 dirs.sort(reverse=True)
861 deleted = []
863 deleted = []
862 for d in dirs:
864 for d in dirs:
863 wd = self.wjoin(d)
865 wd = self.wjoin(d)
864 if os.listdir(wd) == '.svn':
866 if os.listdir(wd) == '.svn':
865 self.run0('delete', d)
867 self.run0('delete', d)
866 deleted.append(d)
868 deleted.append(d)
867 return deleted
869 return deleted
868
870
869 def addchild(self, parent, child):
871 def addchild(self, parent, child):
870 self.childmap[parent] = child
872 self.childmap[parent] = child
871
873
872 def revid(self, rev):
874 def revid(self, rev):
873 return u"svn:%s@%s" % (self.uuid, rev)
875 return u"svn:%s@%s" % (self.uuid, rev)
874
876
875 def putcommit(self, files, parents, commit):
877 def putcommit(self, files, parents, commit):
876 for parent in parents:
878 for parent in parents:
877 try:
879 try:
878 return self.revid(self.childmap[parent])
880 return self.revid(self.childmap[parent])
879 except KeyError:
881 except KeyError:
880 pass
882 pass
881 entries = set(self.delete)
883 entries = set(self.delete)
882 files = util.frozenset(files)
884 files = util.frozenset(files)
883 entries.update(self.add_dirs(files.difference(entries)))
885 entries.update(self.add_dirs(files.difference(entries)))
884 if self.copies:
886 if self.copies:
885 for s, d in self.copies:
887 for s, d in self.copies:
886 self._copyfile(s, d)
888 self._copyfile(s, d)
887 self.copies = []
889 self.copies = []
888 if self.delete:
890 if self.delete:
889 self.xargs(self.delete, 'delete')
891 self.xargs(self.delete, 'delete')
890 self.delete = []
892 self.delete = []
891 entries.update(self.add_files(files.difference(entries)))
893 entries.update(self.add_files(files.difference(entries)))
892 entries.update(self.tidy_dirs(entries))
894 entries.update(self.tidy_dirs(entries))
893 if self.delexec:
895 if self.delexec:
894 self.xargs(self.delexec, 'propdel', 'svn:executable')
896 self.xargs(self.delexec, 'propdel', 'svn:executable')
895 self.delexec = []
897 self.delexec = []
896 if self.setexec:
898 if self.setexec:
897 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
899 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
898 self.setexec = []
900 self.setexec = []
899
901
900 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
902 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
901 fp = os.fdopen(fd, 'w')
903 fp = os.fdopen(fd, 'w')
902 fp.write(commit.desc)
904 fp.write(commit.desc)
903 fp.close()
905 fp.close()
904 try:
906 try:
905 output = self.run0('commit',
907 output = self.run0('commit',
906 username=util.shortuser(commit.author),
908 username=util.shortuser(commit.author),
907 file=messagefile,
909 file=messagefile,
908 encoding='utf-8')
910 encoding='utf-8')
909 try:
911 try:
910 rev = self.commit_re.search(output).group(1)
912 rev = self.commit_re.search(output).group(1)
911 except AttributeError:
913 except AttributeError:
912 self.ui.warn(_('unexpected svn output:\n'))
914 self.ui.warn(_('unexpected svn output:\n'))
913 self.ui.warn(output)
915 self.ui.warn(output)
914 raise util.Abort(_('unable to cope with svn output'))
916 raise util.Abort(_('unable to cope with svn output'))
915 if commit.rev:
917 if commit.rev:
916 self.run('propset', 'hg:convert-rev', commit.rev,
918 self.run('propset', 'hg:convert-rev', commit.rev,
917 revprop=True, revision=rev)
919 revprop=True, revision=rev)
918 if commit.branch and commit.branch != 'default':
920 if commit.branch and commit.branch != 'default':
919 self.run('propset', 'hg:convert-branch', commit.branch,
921 self.run('propset', 'hg:convert-branch', commit.branch,
920 revprop=True, revision=rev)
922 revprop=True, revision=rev)
921 for parent in parents:
923 for parent in parents:
922 self.addchild(parent, rev)
924 self.addchild(parent, rev)
923 return self.revid(rev)
925 return self.revid(rev)
924 finally:
926 finally:
925 os.unlink(messagefile)
927 os.unlink(messagefile)
926
928
927 def puttags(self, tags):
929 def puttags(self, tags):
928 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
930 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
@@ -1,122 +1,177
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/test
29 svnurl=file://$svnpath/svn-repo/trunk/test
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 echo % test stop revision
61 echo % test stop revision
62 hg convert --rev 1 $svnurl stoprev
62 hg convert --rev 1 $svnurl stoprev
63 # Check convert_revision extra-records.
63 # Check convert_revision extra-records.
64 # This is also the only place testing more than one extra field
64 # This is also the only place testing more than one extra field
65 # in a revision.
65 # in a revision.
66 hg --cwd stoprev tip --debug | grep extra | sed 's/=.*/=/'
66 hg --cwd stoprev tip --debug | grep extra | sed 's/=.*/=/'
67
67
68 ########################################
68 ########################################
69
69
70 echo "# now tests that it works with trunk/branches/tags layout"
70 echo "# now tests that it works with trunk/branches/tags layout"
71 echo
71 echo
72 echo % initial svn import
72 echo % initial svn import
73 mkdir projA
73 mkdir projA
74 cd projA
74 cd projA
75 mkdir trunk
75 mkdir trunk
76 mkdir branches
76 mkdir branches
77 mkdir tags
77 mkdir tags
78 cd ..
78 cd ..
79
79
80 svnurl=file://$svnpath/svn-repo/projA
80 svnurl=file://$svnpath/svn-repo/projA
81 svn import -m "init projA" projA $svnurl | fix_path
81 svn import -m "init projA" projA $svnurl | fix_path
82
82
83
83
84 echo % update svn repository
84 echo % update svn repository
85 svn co $svnurl/trunk A | fix_path
85 svn co $svnurl/trunk A | fix_path
86 cd A
86 cd A
87 echo hello > letter.txt
87 echo hello > letter.txt
88 svn add letter.txt
88 svn add letter.txt
89 svn ci -m hello
89 svn ci -m hello
90
90
91 echo world >> letter.txt
91 echo world >> letter.txt
92 svn ci -m world
92 svn ci -m world
93
93
94 svn copy -m "tag v0.1" $svnurl/trunk $svnurl/tags/v0.1
94 svn copy -m "tag v0.1" $svnurl/trunk $svnurl/tags/v0.1
95
95
96 echo 'nice day today!' >> letter.txt
96 echo 'nice day today!' >> letter.txt
97 svn ci -m "nice day"
97 svn ci -m "nice day"
98 cd ..
98 cd ..
99
99
100 echo % convert to hg once
100 echo % convert to hg once
101 hg convert $svnurl A-hg
101 hg convert $svnurl A-hg
102
102
103 echo % update svn repository again
103 echo % update svn repository again
104 cd A
104 cd A
105 echo "see second letter" >> letter.txt
105 echo "see second letter" >> letter.txt
106 echo "nice to meet you" > letter2.txt
106 echo "nice to meet you" > letter2.txt
107 svn add letter2.txt
107 svn add letter2.txt
108 svn ci -m "second letter"
108 svn ci -m "second letter"
109
109
110 svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
110 svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
111
111
112 echo "blah-blah-blah" >> letter2.txt
112 echo "blah-blah-blah" >> letter2.txt
113 svn ci -m "work in progress"
113 svn ci -m "work in progress"
114 cd ..
114 cd ..
115
115
116 echo % test incremental conversion
116 echo % test incremental conversion
117 hg convert $svnurl A-hg
117 hg convert $svnurl A-hg
118
118
119 cd A-hg
119 cd A-hg
120 hg glog --template '#rev# #desc|firstline# files: #files#\n'
120 hg glog --template '#rev# #desc|firstline# files: #files#\n'
121 hg tags -q
121 hg tags -q
122 cd ..
122 cd ..
123
124 ########################################
125
126 echo "# now tests that it works with trunk/tags layout, but no branches yet"
127 echo
128 echo % initial svn import
129 mkdir projB
130 cd projB
131 mkdir trunk
132 mkdir tags
133 cd ..
134
135 svnurl=file://$svnpath/svn-repo/projB
136 svn import -m "init projB" projB $svnurl | fix_path
137
138
139 echo % update svn repository
140 svn co $svnurl/trunk B | fix_path
141 cd B
142 echo hello > letter.txt
143 svn add letter.txt
144 svn ci -m hello
145
146 echo world >> letter.txt
147 svn ci -m world
148
149 svn copy -m "tag v0.1" $svnurl/trunk $svnurl/tags/v0.1
150
151 echo 'nice day today!' >> letter.txt
152 svn ci -m "nice day"
153 cd ..
154
155 echo % convert to hg once
156 hg convert $svnurl B-hg
157
158 echo % update svn repository again
159 cd B
160 echo "see second letter" >> letter.txt
161 echo "nice to meet you" > letter2.txt
162 svn add letter2.txt
163 svn ci -m "second letter"
164
165 svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
166
167 echo "blah-blah-blah" >> letter2.txt
168 svn ci -m "work in progress"
169 cd ..
170
171 echo % test incremental conversion
172 hg convert $svnurl B-hg
173
174 cd B-hg
175 hg glog --template '#rev# #desc|firstline# files: #files#\n'
176 hg tags -q
177 cd ..
@@ -1,120 +1,188
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 test-hg
14 assuming destination test-hg
15 initializing destination test-hg repository
15 initializing destination test-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 test-hg
27 assuming destination test-hg
28 scanning source...
28 scanning source...
29 sorting...
29 sorting...
30 converting...
30 converting...
31 0 changeb
31 0 changeb
32 % test filemap
32 % test filemap
33 initializing destination fmap repository
33 initializing destination fmap repository
34 scanning source...
34 scanning source...
35 sorting...
35 sorting...
36 converting...
36 converting...
37 2 init
37 2 init
38 1 changea
38 1 changea
39 0 changeb
39 0 changeb
40 o 1 changeb files: b
40 o 1 changeb files: b
41 |
41 |
42 o 0 changea files: b
42 o 0 changea files: b
43
43
44 % test stop revision
44 % test stop revision
45 initializing destination stoprev repository
45 initializing destination stoprev repository
46 scanning source...
46 scanning source...
47 sorting...
47 sorting...
48 converting...
48 converting...
49 0 init
49 0 init
50 extra: branch=
50 extra: branch=
51 extra: convert_revision=
51 extra: convert_revision=
52 # now tests that it works with trunk/branches/tags layout
52 # now tests that it works with trunk/branches/tags layout
53
53
54 % initial svn import
54 % initial svn import
55 Adding projA/trunk
55 Adding projA/trunk
56 Adding projA/branches
56 Adding projA/branches
57 Adding projA/tags
57 Adding projA/tags
58
58
59 Committed revision 4.
59 Committed revision 4.
60 % update svn repository
60 % update svn repository
61 Checked out revision 4.
61 Checked out revision 4.
62 A letter.txt
62 A letter.txt
63 Adding letter.txt
63 Adding letter.txt
64 Transmitting file data .
64 Transmitting file data .
65 Committed revision 5.
65 Committed revision 5.
66 Sending letter.txt
66 Sending letter.txt
67 Transmitting file data .
67 Transmitting file data .
68 Committed revision 6.
68 Committed revision 6.
69
69
70 Committed revision 7.
70 Committed revision 7.
71 Sending letter.txt
71 Sending letter.txt
72 Transmitting file data .
72 Transmitting file data .
73 Committed revision 8.
73 Committed revision 8.
74 % convert to hg once
74 % convert to hg once
75 initializing destination A-hg repository
75 initializing destination A-hg repository
76 scanning source...
76 scanning source...
77 sorting...
77 sorting...
78 converting...
78 converting...
79 3 init projA
79 3 init projA
80 2 hello
80 2 hello
81 1 world
81 1 world
82 0 nice day
82 0 nice day
83 updating tags
83 updating tags
84 % update svn repository again
84 % update svn repository again
85 A letter2.txt
85 A letter2.txt
86 Sending letter.txt
86 Sending letter.txt
87 Adding letter2.txt
87 Adding letter2.txt
88 Transmitting file data ..
88 Transmitting file data ..
89 Committed revision 9.
89 Committed revision 9.
90
90
91 Committed revision 10.
91 Committed revision 10.
92 Sending letter2.txt
92 Sending letter2.txt
93 Transmitting file data .
93 Transmitting file data .
94 Committed revision 11.
94 Committed revision 11.
95 % test incremental conversion
95 % test incremental conversion
96 scanning source...
96 scanning source...
97 sorting...
97 sorting...
98 converting...
98 converting...
99 1 second letter
99 1 second letter
100 0 work in progress
100 0 work in progress
101 updating tags
101 updating tags
102 o 7 update tags files: .hgtags
102 o 7 update tags files: .hgtags
103 |
103 |
104 o 6 work in progress files: letter2.txt
104 o 6 work in progress files: letter2.txt
105 |
105 |
106 o 5 second letter files: letter.txt letter2.txt
106 o 5 second letter files: letter.txt letter2.txt
107 |
107 |
108 o 4 update tags files: .hgtags
108 o 4 update tags files: .hgtags
109 |
109 |
110 o 3 nice day files: letter.txt
110 o 3 nice day files: letter.txt
111 |
111 |
112 o 2 world files: letter.txt
112 o 2 world files: letter.txt
113 |
113 |
114 o 1 hello files: letter.txt
114 o 1 hello files: letter.txt
115 |
115 |
116 o 0 init projA files:
116 o 0 init projA files:
117
117
118 tip
118 tip
119 v0.2
119 v0.2
120 v0.1
120 v0.1
121 # now tests that it works with trunk/tags layout, but no branches yet
122
123 % initial svn import
124 Adding projB/trunk
125 Adding projB/tags
126
127 Committed revision 12.
128 % update svn repository
129 Checked out revision 12.
130 A letter.txt
131 Adding letter.txt
132 Transmitting file data .
133 Committed revision 13.
134 Sending letter.txt
135 Transmitting file data .
136 Committed revision 14.
137
138 Committed revision 15.
139 Sending letter.txt
140 Transmitting file data .
141 Committed revision 16.
142 % convert to hg once
143 initializing destination B-hg repository
144 scanning source...
145 sorting...
146 converting...
147 3 init projB
148 2 hello
149 1 world
150 0 nice day
151 updating tags
152 % update svn repository again
153 A letter2.txt
154 Sending letter.txt
155 Adding letter2.txt
156 Transmitting file data ..
157 Committed revision 17.
158
159 Committed revision 18.
160 Sending letter2.txt
161 Transmitting file data .
162 Committed revision 19.
163 % test incremental conversion
164 scanning source...
165 sorting...
166 converting...
167 1 second letter
168 0 work in progress
169 updating tags
170 o 7 update tags files: .hgtags
171 |
172 o 6 work in progress files: letter2.txt
173 |
174 o 5 second letter files: letter.txt letter2.txt
175 |
176 o 4 update tags files: .hgtags
177 |
178 o 3 nice day files: letter.txt
179 |
180 o 2 world files: letter.txt
181 |
182 o 1 hello files: letter.txt
183 |
184 o 0 init projB files:
185
186 tip
187 v0.2
188 v0.1
General Comments 0
You need to be logged in to leave comments. Login now