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