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