##// END OF EJS Templates
convert: replace cache of (m,a,r) by (ma,r)...
Martin von Zweigbergk -
r27718:6e1fba0f default
parent child Browse files
Show More
@@ -1,625 +1,629
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, scmutil, exchange
24 from mercurial import hg, util, context, bookmarks, error, scmutil, exchange
25 from mercurial import phases
25 from mercurial import phases
26 from mercurial import lock as lockmod
26 from mercurial import lock as lockmod
27 from mercurial import merge as mergemod
27 from mercurial import merge as mergemod
28
28
29 from common import NoRepo, commit, converter_source, converter_sink, mapfile
29 from common import NoRepo, commit, converter_source, converter_sink, mapfile
30
30
31 import re
31 import re
32 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
32 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
33
33
34 class mercurial_sink(converter_sink):
34 class mercurial_sink(converter_sink):
35 def __init__(self, ui, path):
35 def __init__(self, ui, path):
36 converter_sink.__init__(self, ui, path)
36 converter_sink.__init__(self, ui, path)
37 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
37 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
38 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
38 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
39 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
39 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
40 self.lastbranch = None
40 self.lastbranch = None
41 if os.path.isdir(path) and len(os.listdir(path)) > 0:
41 if os.path.isdir(path) and len(os.listdir(path)) > 0:
42 try:
42 try:
43 self.repo = hg.repository(self.ui, path)
43 self.repo = hg.repository(self.ui, path)
44 if not self.repo.local():
44 if not self.repo.local():
45 raise NoRepo(_('%s is not a local Mercurial repository')
45 raise NoRepo(_('%s is not a local Mercurial repository')
46 % path)
46 % path)
47 except error.RepoError as err:
47 except error.RepoError as err:
48 ui.traceback()
48 ui.traceback()
49 raise NoRepo(err.args[0])
49 raise NoRepo(err.args[0])
50 else:
50 else:
51 try:
51 try:
52 ui.status(_('initializing destination %s repository\n') % path)
52 ui.status(_('initializing destination %s repository\n') % path)
53 self.repo = hg.repository(self.ui, path, create=True)
53 self.repo = hg.repository(self.ui, path, create=True)
54 if not self.repo.local():
54 if not self.repo.local():
55 raise NoRepo(_('%s is not a local Mercurial repository')
55 raise NoRepo(_('%s is not a local Mercurial repository')
56 % path)
56 % path)
57 self.created.append(path)
57 self.created.append(path)
58 except error.RepoError:
58 except error.RepoError:
59 ui.traceback()
59 ui.traceback()
60 raise NoRepo(_("could not create hg repository %s as sink")
60 raise NoRepo(_("could not create hg repository %s as sink")
61 % path)
61 % path)
62 self.lock = None
62 self.lock = None
63 self.wlock = None
63 self.wlock = None
64 self.filemapmode = False
64 self.filemapmode = False
65 self.subrevmaps = {}
65 self.subrevmaps = {}
66
66
67 def before(self):
67 def before(self):
68 self.ui.debug('run hg sink pre-conversion action\n')
68 self.ui.debug('run hg sink pre-conversion action\n')
69 self.wlock = self.repo.wlock()
69 self.wlock = self.repo.wlock()
70 self.lock = self.repo.lock()
70 self.lock = self.repo.lock()
71
71
72 def after(self):
72 def after(self):
73 self.ui.debug('run hg sink post-conversion action\n')
73 self.ui.debug('run hg sink post-conversion action\n')
74 if self.lock:
74 if self.lock:
75 self.lock.release()
75 self.lock.release()
76 if self.wlock:
76 if self.wlock:
77 self.wlock.release()
77 self.wlock.release()
78
78
79 def revmapfile(self):
79 def revmapfile(self):
80 return self.repo.join("shamap")
80 return self.repo.join("shamap")
81
81
82 def authorfile(self):
82 def authorfile(self):
83 return self.repo.join("authormap")
83 return self.repo.join("authormap")
84
84
85 def setbranch(self, branch, pbranches):
85 def setbranch(self, branch, pbranches):
86 if not self.clonebranches:
86 if not self.clonebranches:
87 return
87 return
88
88
89 setbranch = (branch != self.lastbranch)
89 setbranch = (branch != self.lastbranch)
90 self.lastbranch = branch
90 self.lastbranch = branch
91 if not branch:
91 if not branch:
92 branch = 'default'
92 branch = 'default'
93 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
93 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
94 if pbranches:
94 if pbranches:
95 pbranch = pbranches[0][1]
95 pbranch = pbranches[0][1]
96 else:
96 else:
97 pbranch = 'default'
97 pbranch = 'default'
98
98
99 branchpath = os.path.join(self.path, branch)
99 branchpath = os.path.join(self.path, branch)
100 if setbranch:
100 if setbranch:
101 self.after()
101 self.after()
102 try:
102 try:
103 self.repo = hg.repository(self.ui, branchpath)
103 self.repo = hg.repository(self.ui, branchpath)
104 except Exception:
104 except Exception:
105 self.repo = hg.repository(self.ui, branchpath, create=True)
105 self.repo = hg.repository(self.ui, branchpath, create=True)
106 self.before()
106 self.before()
107
107
108 # pbranches may bring revisions from other branches (merge parents)
108 # pbranches may bring revisions from other branches (merge parents)
109 # Make sure we have them, or pull them.
109 # Make sure we have them, or pull them.
110 missings = {}
110 missings = {}
111 for b in pbranches:
111 for b in pbranches:
112 try:
112 try:
113 self.repo.lookup(b[0])
113 self.repo.lookup(b[0])
114 except Exception:
114 except Exception:
115 missings.setdefault(b[1], []).append(b[0])
115 missings.setdefault(b[1], []).append(b[0])
116
116
117 if missings:
117 if missings:
118 self.after()
118 self.after()
119 for pbranch, heads in sorted(missings.iteritems()):
119 for pbranch, heads in sorted(missings.iteritems()):
120 pbranchpath = os.path.join(self.path, pbranch)
120 pbranchpath = os.path.join(self.path, pbranch)
121 prepo = hg.peer(self.ui, {}, pbranchpath)
121 prepo = hg.peer(self.ui, {}, pbranchpath)
122 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
122 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
123 exchange.pull(self.repo, prepo,
123 exchange.pull(self.repo, prepo,
124 [prepo.lookup(h) for h in heads])
124 [prepo.lookup(h) for h in heads])
125 self.before()
125 self.before()
126
126
127 def _rewritetags(self, source, revmap, data):
127 def _rewritetags(self, source, revmap, data):
128 fp = cStringIO.StringIO()
128 fp = cStringIO.StringIO()
129 for line in data.splitlines():
129 for line in data.splitlines():
130 s = line.split(' ', 1)
130 s = line.split(' ', 1)
131 if len(s) != 2:
131 if len(s) != 2:
132 continue
132 continue
133 revid = revmap.get(source.lookuprev(s[0]))
133 revid = revmap.get(source.lookuprev(s[0]))
134 if not revid:
134 if not revid:
135 if s[0] == hex(nullid):
135 if s[0] == hex(nullid):
136 revid = s[0]
136 revid = s[0]
137 else:
137 else:
138 continue
138 continue
139 fp.write('%s %s\n' % (revid, s[1]))
139 fp.write('%s %s\n' % (revid, s[1]))
140 return fp.getvalue()
140 return fp.getvalue()
141
141
142 def _rewritesubstate(self, source, data):
142 def _rewritesubstate(self, source, data):
143 fp = cStringIO.StringIO()
143 fp = cStringIO.StringIO()
144 for line in data.splitlines():
144 for line in data.splitlines():
145 s = line.split(' ', 1)
145 s = line.split(' ', 1)
146 if len(s) != 2:
146 if len(s) != 2:
147 continue
147 continue
148
148
149 revid = s[0]
149 revid = s[0]
150 subpath = s[1]
150 subpath = s[1]
151 if revid != hex(nullid):
151 if revid != hex(nullid):
152 revmap = self.subrevmaps.get(subpath)
152 revmap = self.subrevmaps.get(subpath)
153 if revmap is None:
153 if revmap is None:
154 revmap = mapfile(self.ui,
154 revmap = mapfile(self.ui,
155 self.repo.wjoin(subpath, '.hg/shamap'))
155 self.repo.wjoin(subpath, '.hg/shamap'))
156 self.subrevmaps[subpath] = revmap
156 self.subrevmaps[subpath] = revmap
157
157
158 # It is reasonable that one or more of the subrepos don't
158 # It is reasonable that one or more of the subrepos don't
159 # need to be converted, in which case they can be cloned
159 # need to be converted, in which case they can be cloned
160 # into place instead of converted. Therefore, only warn
160 # into place instead of converted. Therefore, only warn
161 # once.
161 # once.
162 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
162 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
163 if len(revmap) == 0:
163 if len(revmap) == 0:
164 sub = self.repo.wvfs.reljoin(subpath, '.hg')
164 sub = self.repo.wvfs.reljoin(subpath, '.hg')
165
165
166 if self.repo.wvfs.exists(sub):
166 if self.repo.wvfs.exists(sub):
167 self.ui.warn(msg % subpath)
167 self.ui.warn(msg % subpath)
168
168
169 newid = revmap.get(revid)
169 newid = revmap.get(revid)
170 if not newid:
170 if not newid:
171 if len(revmap) > 0:
171 if len(revmap) > 0:
172 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
172 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
173 (revid, subpath))
173 (revid, subpath))
174 else:
174 else:
175 revid = newid
175 revid = newid
176
176
177 fp.write('%s %s\n' % (revid, subpath))
177 fp.write('%s %s\n' % (revid, subpath))
178
178
179 return fp.getvalue()
179 return fp.getvalue()
180
180
181 def _calculatemergedfiles(self, source, p1ctx, p2ctx):
181 def _calculatemergedfiles(self, source, p1ctx, p2ctx):
182 """Calculates the files from p2 that we need to pull in when merging p1
182 """Calculates the files from p2 that we need to pull in when merging p1
183 and p2, given that the merge is coming from the given source.
183 and p2, given that the merge is coming from the given source.
184
184
185 This prevents us from losing files that only exist in the target p2 and
185 This prevents us from losing files that only exist in the target p2 and
186 that don't come from the source repo (like if you're merging multiple
186 that don't come from the source repo (like if you're merging multiple
187 repositories together).
187 repositories together).
188 """
188 """
189 anc = [p1ctx.ancestor(p2ctx)]
189 anc = [p1ctx.ancestor(p2ctx)]
190 # Calculate what files are coming from p2
190 # Calculate what files are coming from p2
191 actions, diverge, rename = mergemod.calculateupdates(
191 actions, diverge, rename = mergemod.calculateupdates(
192 self.repo, p1ctx, p2ctx, anc,
192 self.repo, p1ctx, p2ctx, anc,
193 True, # branchmerge
193 True, # branchmerge
194 True, # force
194 True, # force
195 False, # acceptremote
195 False, # acceptremote
196 False, # followcopies
196 False, # followcopies
197 )
197 )
198
198
199 for file, (action, info, msg) in actions.iteritems():
199 for file, (action, info, msg) in actions.iteritems():
200 if source.targetfilebelongstosource(file):
200 if source.targetfilebelongstosource(file):
201 # If the file belongs to the source repo, ignore the p2
201 # If the file belongs to the source repo, ignore the p2
202 # since it will be covered by the existing fileset.
202 # since it will be covered by the existing fileset.
203 continue
203 continue
204
204
205 # If the file requires actual merging, abort. We don't have enough
205 # If the file requires actual merging, abort. We don't have enough
206 # context to resolve merges correctly.
206 # context to resolve merges correctly.
207 if action in ['m', 'dm', 'cd', 'dc']:
207 if action in ['m', 'dm', 'cd', 'dc']:
208 raise error.Abort(_("unable to convert merge commit "
208 raise error.Abort(_("unable to convert merge commit "
209 "since target parents do not merge cleanly (file "
209 "since target parents do not merge cleanly (file "
210 "%s, parents %s and %s)") % (file, p1ctx,
210 "%s, parents %s and %s)") % (file, p1ctx,
211 p2ctx))
211 p2ctx))
212 elif action == 'k':
212 elif action == 'k':
213 # 'keep' means nothing changed from p1
213 # 'keep' means nothing changed from p1
214 continue
214 continue
215 else:
215 else:
216 # Any other change means we want to take the p2 version
216 # Any other change means we want to take the p2 version
217 yield file
217 yield file
218
218
219 def putcommit(self, files, copies, parents, commit, source, revmap, full,
219 def putcommit(self, files, copies, parents, commit, source, revmap, full,
220 cleanp2):
220 cleanp2):
221 files = dict(files)
221 files = dict(files)
222
222
223 def getfilectx(repo, memctx, f):
223 def getfilectx(repo, memctx, f):
224 if p2ctx and f in p2files and f not in copies:
224 if p2ctx and f in p2files and f not in copies:
225 self.ui.debug('reusing %s from p2\n' % f)
225 self.ui.debug('reusing %s from p2\n' % f)
226 try:
226 try:
227 return p2ctx[f]
227 return p2ctx[f]
228 except error.ManifestLookupError:
228 except error.ManifestLookupError:
229 # If the file doesn't exist in p2, then we're syncing a
229 # If the file doesn't exist in p2, then we're syncing a
230 # delete, so just return None.
230 # delete, so just return None.
231 return None
231 return None
232 try:
232 try:
233 v = files[f]
233 v = files[f]
234 except KeyError:
234 except KeyError:
235 return None
235 return None
236 data, mode = source.getfile(f, v)
236 data, mode = source.getfile(f, v)
237 if data is None:
237 if data is None:
238 return None
238 return None
239 if f == '.hgtags':
239 if f == '.hgtags':
240 data = self._rewritetags(source, revmap, data)
240 data = self._rewritetags(source, revmap, data)
241 if f == '.hgsubstate':
241 if f == '.hgsubstate':
242 data = self._rewritesubstate(source, data)
242 data = self._rewritesubstate(source, data)
243 return context.memfilectx(self.repo, f, data, 'l' in mode,
243 return context.memfilectx(self.repo, f, data, 'l' in mode,
244 'x' in mode, copies.get(f))
244 'x' in mode, copies.get(f))
245
245
246 pl = []
246 pl = []
247 for p in parents:
247 for p in parents:
248 if p not in pl:
248 if p not in pl:
249 pl.append(p)
249 pl.append(p)
250 parents = pl
250 parents = pl
251 nparents = len(parents)
251 nparents = len(parents)
252 if self.filemapmode and nparents == 1:
252 if self.filemapmode and nparents == 1:
253 m1node = self.repo.changelog.read(bin(parents[0]))[0]
253 m1node = self.repo.changelog.read(bin(parents[0]))[0]
254 parent = parents[0]
254 parent = parents[0]
255
255
256 if len(parents) < 2:
256 if len(parents) < 2:
257 parents.append(nullid)
257 parents.append(nullid)
258 if len(parents) < 2:
258 if len(parents) < 2:
259 parents.append(nullid)
259 parents.append(nullid)
260 p2 = parents.pop(0)
260 p2 = parents.pop(0)
261
261
262 text = commit.desc
262 text = commit.desc
263
263
264 sha1s = re.findall(sha1re, text)
264 sha1s = re.findall(sha1re, text)
265 for sha1 in sha1s:
265 for sha1 in sha1s:
266 oldrev = source.lookuprev(sha1)
266 oldrev = source.lookuprev(sha1)
267 newrev = revmap.get(oldrev)
267 newrev = revmap.get(oldrev)
268 if newrev is not None:
268 if newrev is not None:
269 text = text.replace(sha1, newrev[:len(sha1)])
269 text = text.replace(sha1, newrev[:len(sha1)])
270
270
271 extra = commit.extra.copy()
271 extra = commit.extra.copy()
272
272
273 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
273 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
274 if sourcename:
274 if sourcename:
275 extra['convert_source'] = sourcename
275 extra['convert_source'] = sourcename
276
276
277 for label in ('source', 'transplant_source', 'rebase_source',
277 for label in ('source', 'transplant_source', 'rebase_source',
278 'intermediate-source'):
278 'intermediate-source'):
279 node = extra.get(label)
279 node = extra.get(label)
280
280
281 if node is None:
281 if node is None:
282 continue
282 continue
283
283
284 # Only transplant stores its reference in binary
284 # Only transplant stores its reference in binary
285 if label == 'transplant_source':
285 if label == 'transplant_source':
286 node = hex(node)
286 node = hex(node)
287
287
288 newrev = revmap.get(node)
288 newrev = revmap.get(node)
289 if newrev is not None:
289 if newrev is not None:
290 if label == 'transplant_source':
290 if label == 'transplant_source':
291 newrev = bin(newrev)
291 newrev = bin(newrev)
292
292
293 extra[label] = newrev
293 extra[label] = newrev
294
294
295 if self.branchnames and commit.branch:
295 if self.branchnames and commit.branch:
296 extra['branch'] = commit.branch
296 extra['branch'] = commit.branch
297 if commit.rev and commit.saverev:
297 if commit.rev and commit.saverev:
298 extra['convert_revision'] = commit.rev
298 extra['convert_revision'] = commit.rev
299
299
300 while parents:
300 while parents:
301 p1 = p2
301 p1 = p2
302 p2 = parents.pop(0)
302 p2 = parents.pop(0)
303 p1ctx = self.repo[p1]
303 p1ctx = self.repo[p1]
304 p2ctx = None
304 p2ctx = None
305 if p2 != nullid:
305 if p2 != nullid:
306 p2ctx = self.repo[p2]
306 p2ctx = self.repo[p2]
307 fileset = set(files)
307 fileset = set(files)
308 if full:
308 if full:
309 fileset.update(self.repo[p1])
309 fileset.update(self.repo[p1])
310 fileset.update(self.repo[p2])
310 fileset.update(self.repo[p2])
311
311
312 if p2ctx:
312 if p2ctx:
313 p2files = set(cleanp2)
313 p2files = set(cleanp2)
314 for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
314 for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
315 p2files.add(file)
315 p2files.add(file)
316 fileset.add(file)
316 fileset.add(file)
317
317
318 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
318 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
319 getfilectx, commit.author, commit.date, extra)
319 getfilectx, commit.author, commit.date, extra)
320
320
321 # We won't know if the conversion changes the node until after the
321 # We won't know if the conversion changes the node until after the
322 # commit, so copy the source's phase for now.
322 # commit, so copy the source's phase for now.
323 self.repo.ui.setconfig('phases', 'new-commit',
323 self.repo.ui.setconfig('phases', 'new-commit',
324 phases.phasenames[commit.phase], 'convert')
324 phases.phasenames[commit.phase], 'convert')
325
325
326 tr = self.repo.transaction("convert")
326 tr = self.repo.transaction("convert")
327
327
328 try:
328 try:
329 node = hex(self.repo.commitctx(ctx))
329 node = hex(self.repo.commitctx(ctx))
330
330
331 # If the node value has changed, but the phase is lower than
331 # If the node value has changed, but the phase is lower than
332 # draft, set it back to draft since it hasn't been exposed
332 # draft, set it back to draft since it hasn't been exposed
333 # anywhere.
333 # anywhere.
334 if commit.rev != node:
334 if commit.rev != node:
335 ctx = self.repo[node]
335 ctx = self.repo[node]
336 if ctx.phase() < phases.draft:
336 if ctx.phase() < phases.draft:
337 phases.retractboundary(self.repo, tr, phases.draft,
337 phases.retractboundary(self.repo, tr, phases.draft,
338 [ctx.node()])
338 [ctx.node()])
339 tr.close()
339 tr.close()
340 finally:
340 finally:
341 tr.release()
341 tr.release()
342
342
343 text = "(octopus merge fixup)\n"
343 text = "(octopus merge fixup)\n"
344 p2 = node
344 p2 = node
345
345
346 if self.filemapmode and nparents == 1:
346 if self.filemapmode and nparents == 1:
347 man = self.repo.manifest
347 man = self.repo.manifest
348 mnode = self.repo.changelog.read(bin(p2))[0]
348 mnode = self.repo.changelog.read(bin(p2))[0]
349 closed = 'close' in commit.extra
349 closed = 'close' in commit.extra
350 if not closed and not man.cmp(m1node, man.revision(mnode)):
350 if not closed and not man.cmp(m1node, man.revision(mnode)):
351 self.ui.status(_("filtering out empty revision\n"))
351 self.ui.status(_("filtering out empty revision\n"))
352 self.repo.rollback(force=True)
352 self.repo.rollback(force=True)
353 return parent
353 return parent
354 return p2
354 return p2
355
355
356 def puttags(self, tags):
356 def puttags(self, tags):
357 try:
357 try:
358 parentctx = self.repo[self.tagsbranch]
358 parentctx = self.repo[self.tagsbranch]
359 tagparent = parentctx.node()
359 tagparent = parentctx.node()
360 except error.RepoError:
360 except error.RepoError:
361 parentctx = None
361 parentctx = None
362 tagparent = nullid
362 tagparent = nullid
363
363
364 oldlines = set()
364 oldlines = set()
365 for branch, heads in self.repo.branchmap().iteritems():
365 for branch, heads in self.repo.branchmap().iteritems():
366 for h in heads:
366 for h in heads:
367 if '.hgtags' in self.repo[h]:
367 if '.hgtags' in self.repo[h]:
368 oldlines.update(
368 oldlines.update(
369 set(self.repo[h]['.hgtags'].data().splitlines(True)))
369 set(self.repo[h]['.hgtags'].data().splitlines(True)))
370 oldlines = sorted(list(oldlines))
370 oldlines = sorted(list(oldlines))
371
371
372 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
372 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
373 if newlines == oldlines:
373 if newlines == oldlines:
374 return None, None
374 return None, None
375
375
376 # if the old and new tags match, then there is nothing to update
376 # if the old and new tags match, then there is nothing to update
377 oldtags = set()
377 oldtags = set()
378 newtags = set()
378 newtags = set()
379 for line in oldlines:
379 for line in oldlines:
380 s = line.strip().split(' ', 1)
380 s = line.strip().split(' ', 1)
381 if len(s) != 2:
381 if len(s) != 2:
382 continue
382 continue
383 oldtags.add(s[1])
383 oldtags.add(s[1])
384 for line in newlines:
384 for line in newlines:
385 s = line.strip().split(' ', 1)
385 s = line.strip().split(' ', 1)
386 if len(s) != 2:
386 if len(s) != 2:
387 continue
387 continue
388 if s[1] not in oldtags:
388 if s[1] not in oldtags:
389 newtags.add(s[1].strip())
389 newtags.add(s[1].strip())
390
390
391 if not newtags:
391 if not newtags:
392 return None, None
392 return None, None
393
393
394 data = "".join(newlines)
394 data = "".join(newlines)
395 def getfilectx(repo, memctx, f):
395 def getfilectx(repo, memctx, f):
396 return context.memfilectx(repo, f, data, False, False, None)
396 return context.memfilectx(repo, f, data, False, False, None)
397
397
398 self.ui.status(_("updating tags\n"))
398 self.ui.status(_("updating tags\n"))
399 date = "%s 0" % int(time.mktime(time.gmtime()))
399 date = "%s 0" % int(time.mktime(time.gmtime()))
400 extra = {'branch': self.tagsbranch}
400 extra = {'branch': self.tagsbranch}
401 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
401 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
402 [".hgtags"], getfilectx, "convert-repo", date,
402 [".hgtags"], getfilectx, "convert-repo", date,
403 extra)
403 extra)
404 node = self.repo.commitctx(ctx)
404 node = self.repo.commitctx(ctx)
405 return hex(node), hex(tagparent)
405 return hex(node), hex(tagparent)
406
406
407 def setfilemapmode(self, active):
407 def setfilemapmode(self, active):
408 self.filemapmode = active
408 self.filemapmode = active
409
409
410 def putbookmarks(self, updatedbookmark):
410 def putbookmarks(self, updatedbookmark):
411 if not len(updatedbookmark):
411 if not len(updatedbookmark):
412 return
412 return
413 wlock = lock = tr = None
413 wlock = lock = tr = None
414 try:
414 try:
415 wlock = self.repo.wlock()
415 wlock = self.repo.wlock()
416 lock = self.repo.lock()
416 lock = self.repo.lock()
417 tr = self.repo.transaction('bookmark')
417 tr = self.repo.transaction('bookmark')
418 self.ui.status(_("updating bookmarks\n"))
418 self.ui.status(_("updating bookmarks\n"))
419 destmarks = self.repo._bookmarks
419 destmarks = self.repo._bookmarks
420 for bookmark in updatedbookmark:
420 for bookmark in updatedbookmark:
421 destmarks[bookmark] = bin(updatedbookmark[bookmark])
421 destmarks[bookmark] = bin(updatedbookmark[bookmark])
422 destmarks.recordchange(tr)
422 destmarks.recordchange(tr)
423 tr.close()
423 tr.close()
424 finally:
424 finally:
425 lockmod.release(lock, wlock, tr)
425 lockmod.release(lock, wlock, tr)
426
426
427 def hascommitfrommap(self, rev):
427 def hascommitfrommap(self, rev):
428 # the exact semantics of clonebranches is unclear so we can't say no
428 # the exact semantics of clonebranches is unclear so we can't say no
429 return rev in self.repo or self.clonebranches
429 return rev in self.repo or self.clonebranches
430
430
431 def hascommitforsplicemap(self, rev):
431 def hascommitforsplicemap(self, rev):
432 if rev not in self.repo and self.clonebranches:
432 if rev not in self.repo and self.clonebranches:
433 raise error.Abort(_('revision %s not found in destination '
433 raise error.Abort(_('revision %s not found in destination '
434 'repository (lookups with clonebranches=true '
434 'repository (lookups with clonebranches=true '
435 'are not implemented)') % rev)
435 'are not implemented)') % rev)
436 return rev in self.repo
436 return rev in self.repo
437
437
438 class mercurial_source(converter_source):
438 class mercurial_source(converter_source):
439 def __init__(self, ui, path, revs=None):
439 def __init__(self, ui, path, revs=None):
440 converter_source.__init__(self, ui, path, revs)
440 converter_source.__init__(self, ui, path, revs)
441 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
441 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
442 self.ignored = set()
442 self.ignored = set()
443 self.saverev = ui.configbool('convert', 'hg.saverev', False)
443 self.saverev = ui.configbool('convert', 'hg.saverev', False)
444 try:
444 try:
445 self.repo = hg.repository(self.ui, path)
445 self.repo = hg.repository(self.ui, path)
446 # try to provoke an exception if this isn't really a hg
446 # try to provoke an exception if this isn't really a hg
447 # repo, but some other bogus compatible-looking url
447 # repo, but some other bogus compatible-looking url
448 if not self.repo.local():
448 if not self.repo.local():
449 raise error.RepoError
449 raise error.RepoError
450 except error.RepoError:
450 except error.RepoError:
451 ui.traceback()
451 ui.traceback()
452 raise NoRepo(_("%s is not a local Mercurial repository") % path)
452 raise NoRepo(_("%s is not a local Mercurial repository") % path)
453 self.lastrev = None
453 self.lastrev = None
454 self.lastctx = None
454 self.lastctx = None
455 self._changescache = None, None
455 self._changescache = None, None
456 self.convertfp = None
456 self.convertfp = None
457 # Restrict converted revisions to startrev descendants
457 # Restrict converted revisions to startrev descendants
458 startnode = ui.config('convert', 'hg.startrev')
458 startnode = ui.config('convert', 'hg.startrev')
459 hgrevs = ui.config('convert', 'hg.revs')
459 hgrevs = ui.config('convert', 'hg.revs')
460 if hgrevs is None:
460 if hgrevs is None:
461 if startnode is not None:
461 if startnode is not None:
462 try:
462 try:
463 startnode = self.repo.lookup(startnode)
463 startnode = self.repo.lookup(startnode)
464 except error.RepoError:
464 except error.RepoError:
465 raise error.Abort(_('%s is not a valid start revision')
465 raise error.Abort(_('%s is not a valid start revision')
466 % startnode)
466 % startnode)
467 startrev = self.repo.changelog.rev(startnode)
467 startrev = self.repo.changelog.rev(startnode)
468 children = {startnode: 1}
468 children = {startnode: 1}
469 for r in self.repo.changelog.descendants([startrev]):
469 for r in self.repo.changelog.descendants([startrev]):
470 children[self.repo.changelog.node(r)] = 1
470 children[self.repo.changelog.node(r)] = 1
471 self.keep = children.__contains__
471 self.keep = children.__contains__
472 else:
472 else:
473 self.keep = util.always
473 self.keep = util.always
474 if revs:
474 if revs:
475 self._heads = [self.repo[r].node() for r in revs]
475 self._heads = [self.repo[r].node() for r in revs]
476 else:
476 else:
477 self._heads = self.repo.heads()
477 self._heads = self.repo.heads()
478 else:
478 else:
479 if revs or startnode is not None:
479 if revs or startnode is not None:
480 raise error.Abort(_('hg.revs cannot be combined with '
480 raise error.Abort(_('hg.revs cannot be combined with '
481 'hg.startrev or --rev'))
481 'hg.startrev or --rev'))
482 nodes = set()
482 nodes = set()
483 parents = set()
483 parents = set()
484 for r in scmutil.revrange(self.repo, [hgrevs]):
484 for r in scmutil.revrange(self.repo, [hgrevs]):
485 ctx = self.repo[r]
485 ctx = self.repo[r]
486 nodes.add(ctx.node())
486 nodes.add(ctx.node())
487 parents.update(p.node() for p in ctx.parents())
487 parents.update(p.node() for p in ctx.parents())
488 self.keep = nodes.__contains__
488 self.keep = nodes.__contains__
489 self._heads = nodes - parents
489 self._heads = nodes - parents
490
490
491 def _changectx(self, rev):
491 def _changectx(self, rev):
492 if self.lastrev != rev:
492 if self.lastrev != rev:
493 self.lastctx = self.repo[rev]
493 self.lastctx = self.repo[rev]
494 self.lastrev = rev
494 self.lastrev = rev
495 return self.lastctx
495 return self.lastctx
496
496
497 def _parents(self, ctx):
497 def _parents(self, ctx):
498 return [p for p in ctx.parents() if p and self.keep(p.node())]
498 return [p for p in ctx.parents() if p and self.keep(p.node())]
499
499
500 def getheads(self):
500 def getheads(self):
501 return [hex(h) for h in self._heads if self.keep(h)]
501 return [hex(h) for h in self._heads if self.keep(h)]
502
502
503 def getfile(self, name, rev):
503 def getfile(self, name, rev):
504 try:
504 try:
505 fctx = self._changectx(rev)[name]
505 fctx = self._changectx(rev)[name]
506 return fctx.data(), fctx.flags()
506 return fctx.data(), fctx.flags()
507 except error.LookupError:
507 except error.LookupError:
508 return None, None
508 return None, None
509
509
510 def _changedfiles(self, ctx1, ctx2):
511 m, a, r = ctx1.status(ctx2)[:3]
512 return (m + a, r)
513
510 def getchanges(self, rev, full):
514 def getchanges(self, rev, full):
511 ctx = self._changectx(rev)
515 ctx = self._changectx(rev)
512 parents = self._parents(ctx)
516 parents = self._parents(ctx)
513 if full or not parents:
517 if full or not parents:
514 files = copyfiles = ctx.manifest()
518 files = copyfiles = ctx.manifest()
515 if parents:
519 if parents:
516 if self._changescache[0] == rev:
520 if self._changescache[0] == rev:
517 m, a, r = self._changescache[1]
521 ma, r = self._changescache[1]
518 else:
522 else:
519 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
523 ma, r = self._changedfiles(parents[0], ctx)
520 if not full:
524 if not full:
521 files = m + a + r
525 files = ma + r
522 copyfiles = m + a
526 copyfiles = ma
523 # _getcopies() is also run for roots and before filtering so missing
527 # _getcopies() is also run for roots and before filtering so missing
524 # revlogs are detected early
528 # revlogs are detected early
525 copies = self._getcopies(ctx, parents, copyfiles)
529 copies = self._getcopies(ctx, parents, copyfiles)
526 cleanp2 = set()
530 cleanp2 = set()
527 if len(parents) == 2:
531 if len(parents) == 2:
528 cleanp2.update(self.repo.status(parents[1].node(), ctx.node(),
532 cleanp2.update(self.repo.status(parents[1].node(), ctx.node(),
529 clean=True).clean)
533 clean=True).clean)
530 changes = [(f, rev) for f in files if f not in self.ignored]
534 changes = [(f, rev) for f in files if f not in self.ignored]
531 changes.sort()
535 changes.sort()
532 return changes, copies, cleanp2
536 return changes, copies, cleanp2
533
537
534 def _getcopies(self, ctx, parents, files):
538 def _getcopies(self, ctx, parents, files):
535 copies = {}
539 copies = {}
536 for name in files:
540 for name in files:
537 if name in self.ignored:
541 if name in self.ignored:
538 continue
542 continue
539 try:
543 try:
540 copysource, _copynode = ctx.filectx(name).renamed()
544 copysource, _copynode = ctx.filectx(name).renamed()
541 if copysource in self.ignored:
545 if copysource in self.ignored:
542 continue
546 continue
543 # Ignore copy sources not in parent revisions
547 # Ignore copy sources not in parent revisions
544 found = False
548 found = False
545 for p in parents:
549 for p in parents:
546 if copysource in p:
550 if copysource in p:
547 found = True
551 found = True
548 break
552 break
549 if not found:
553 if not found:
550 continue
554 continue
551 copies[name] = copysource
555 copies[name] = copysource
552 except TypeError:
556 except TypeError:
553 pass
557 pass
554 except error.LookupError as e:
558 except error.LookupError as e:
555 if not self.ignoreerrors:
559 if not self.ignoreerrors:
556 raise
560 raise
557 self.ignored.add(name)
561 self.ignored.add(name)
558 self.ui.warn(_('ignoring: %s\n') % e)
562 self.ui.warn(_('ignoring: %s\n') % e)
559 return copies
563 return copies
560
564
561 def getcommit(self, rev):
565 def getcommit(self, rev):
562 ctx = self._changectx(rev)
566 ctx = self._changectx(rev)
563 parents = [p.hex() for p in self._parents(ctx)]
567 parents = [p.hex() for p in self._parents(ctx)]
564 crev = rev
568 crev = rev
565
569
566 return commit(author=ctx.user(),
570 return commit(author=ctx.user(),
567 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
571 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
568 desc=ctx.description(), rev=crev, parents=parents,
572 desc=ctx.description(), rev=crev, parents=parents,
569 branch=ctx.branch(), extra=ctx.extra(),
573 branch=ctx.branch(), extra=ctx.extra(),
570 sortkey=ctx.rev(), saverev=self.saverev,
574 sortkey=ctx.rev(), saverev=self.saverev,
571 phase=ctx.phase())
575 phase=ctx.phase())
572
576
573 def gettags(self):
577 def gettags(self):
574 # This will get written to .hgtags, filter non global tags out.
578 # This will get written to .hgtags, filter non global tags out.
575 tags = [t for t in self.repo.tagslist()
579 tags = [t for t in self.repo.tagslist()
576 if self.repo.tagtype(t[0]) == 'global']
580 if self.repo.tagtype(t[0]) == 'global']
577 return dict([(name, hex(node)) for name, node in tags
581 return dict([(name, hex(node)) for name, node in tags
578 if self.keep(node)])
582 if self.keep(node)])
579
583
580 def getchangedfiles(self, rev, i):
584 def getchangedfiles(self, rev, i):
581 ctx = self._changectx(rev)
585 ctx = self._changectx(rev)
582 parents = self._parents(ctx)
586 parents = self._parents(ctx)
583 if not parents and i is None:
587 if not parents and i is None:
584 i = 0
588 i = 0
585 changes = [], ctx.manifest().keys(), []
589 ma, r = ctx.manifest().keys(), []
586 else:
590 else:
587 i = i or 0
591 i = i or 0
588 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
592 ma, r = self._changedfiles(parents[i], ctx)
589 changes = [[f for f in l if f not in self.ignored] for l in changes]
593 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
590
594
591 if i == 0:
595 if i == 0:
592 self._changescache = (rev, changes)
596 self._changescache = (rev, (ma, r))
593
597
594 return changes[0] + changes[1] + changes[2]
598 return ma + r
595
599
596 def converted(self, rev, destrev):
600 def converted(self, rev, destrev):
597 if self.convertfp is None:
601 if self.convertfp is None:
598 self.convertfp = open(self.repo.join('shamap'), 'a')
602 self.convertfp = open(self.repo.join('shamap'), 'a')
599 self.convertfp.write('%s %s\n' % (destrev, rev))
603 self.convertfp.write('%s %s\n' % (destrev, rev))
600 self.convertfp.flush()
604 self.convertfp.flush()
601
605
602 def before(self):
606 def before(self):
603 self.ui.debug('run hg source pre-conversion action\n')
607 self.ui.debug('run hg source pre-conversion action\n')
604
608
605 def after(self):
609 def after(self):
606 self.ui.debug('run hg source post-conversion action\n')
610 self.ui.debug('run hg source post-conversion action\n')
607
611
608 def hasnativeorder(self):
612 def hasnativeorder(self):
609 return True
613 return True
610
614
611 def hasnativeclose(self):
615 def hasnativeclose(self):
612 return True
616 return True
613
617
614 def lookuprev(self, rev):
618 def lookuprev(self, rev):
615 try:
619 try:
616 return hex(self.repo.lookup(rev))
620 return hex(self.repo.lookup(rev))
617 except (error.RepoError, error.LookupError):
621 except (error.RepoError, error.LookupError):
618 return None
622 return None
619
623
620 def getbookmarks(self):
624 def getbookmarks(self):
621 return bookmarks.listbookmarks(self.repo)
625 return bookmarks.listbookmarks(self.repo)
622
626
623 def checkrevformat(self, revstr, mapname='splicemap'):
627 def checkrevformat(self, revstr, mapname='splicemap'):
624 """ Mercurial, revision string is a 40 byte hex """
628 """ Mercurial, revision string is a 40 byte hex """
625 self.checkhexformat(revstr, mapname)
629 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now