##// END OF EJS Templates
with: use context manager for transaction in mercurial_sink
Bryan O'Sullivan -
r27863:ed59ae8b default
parent child Browse files
Show More
@@ -1,639 +1,634 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, 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 with self.repo.transaction("convert") as tr:
327
328 try:
329 node = hex(self.repo.commitctx(ctx))
327 node = hex(self.repo.commitctx(ctx))
330
328
331 # If the node value has changed, but the phase is lower than
329 # 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
330 # draft, set it back to draft since it hasn't been exposed
333 # anywhere.
331 # anywhere.
334 if commit.rev != node:
332 if commit.rev != node:
335 ctx = self.repo[node]
333 ctx = self.repo[node]
336 if ctx.phase() < phases.draft:
334 if ctx.phase() < phases.draft:
337 phases.retractboundary(self.repo, tr, phases.draft,
335 phases.retractboundary(self.repo, tr, phases.draft,
338 [ctx.node()])
336 [ctx.node()])
339 tr.close()
340 finally:
341 tr.release()
342
337
343 text = "(octopus merge fixup)\n"
338 text = "(octopus merge fixup)\n"
344 p2 = node
339 p2 = node
345
340
346 if self.filemapmode and nparents == 1:
341 if self.filemapmode and nparents == 1:
347 man = self.repo.manifest
342 man = self.repo.manifest
348 mnode = self.repo.changelog.read(bin(p2))[0]
343 mnode = self.repo.changelog.read(bin(p2))[0]
349 closed = 'close' in commit.extra
344 closed = 'close' in commit.extra
350 if not closed and not man.cmp(m1node, man.revision(mnode)):
345 if not closed and not man.cmp(m1node, man.revision(mnode)):
351 self.ui.status(_("filtering out empty revision\n"))
346 self.ui.status(_("filtering out empty revision\n"))
352 self.repo.rollback(force=True)
347 self.repo.rollback(force=True)
353 return parent
348 return parent
354 return p2
349 return p2
355
350
356 def puttags(self, tags):
351 def puttags(self, tags):
357 try:
352 try:
358 parentctx = self.repo[self.tagsbranch]
353 parentctx = self.repo[self.tagsbranch]
359 tagparent = parentctx.node()
354 tagparent = parentctx.node()
360 except error.RepoError:
355 except error.RepoError:
361 parentctx = None
356 parentctx = None
362 tagparent = nullid
357 tagparent = nullid
363
358
364 oldlines = set()
359 oldlines = set()
365 for branch, heads in self.repo.branchmap().iteritems():
360 for branch, heads in self.repo.branchmap().iteritems():
366 for h in heads:
361 for h in heads:
367 if '.hgtags' in self.repo[h]:
362 if '.hgtags' in self.repo[h]:
368 oldlines.update(
363 oldlines.update(
369 set(self.repo[h]['.hgtags'].data().splitlines(True)))
364 set(self.repo[h]['.hgtags'].data().splitlines(True)))
370 oldlines = sorted(list(oldlines))
365 oldlines = sorted(list(oldlines))
371
366
372 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
367 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
373 if newlines == oldlines:
368 if newlines == oldlines:
374 return None, None
369 return None, None
375
370
376 # if the old and new tags match, then there is nothing to update
371 # if the old and new tags match, then there is nothing to update
377 oldtags = set()
372 oldtags = set()
378 newtags = set()
373 newtags = set()
379 for line in oldlines:
374 for line in oldlines:
380 s = line.strip().split(' ', 1)
375 s = line.strip().split(' ', 1)
381 if len(s) != 2:
376 if len(s) != 2:
382 continue
377 continue
383 oldtags.add(s[1])
378 oldtags.add(s[1])
384 for line in newlines:
379 for line in newlines:
385 s = line.strip().split(' ', 1)
380 s = line.strip().split(' ', 1)
386 if len(s) != 2:
381 if len(s) != 2:
387 continue
382 continue
388 if s[1] not in oldtags:
383 if s[1] not in oldtags:
389 newtags.add(s[1].strip())
384 newtags.add(s[1].strip())
390
385
391 if not newtags:
386 if not newtags:
392 return None, None
387 return None, None
393
388
394 data = "".join(newlines)
389 data = "".join(newlines)
395 def getfilectx(repo, memctx, f):
390 def getfilectx(repo, memctx, f):
396 return context.memfilectx(repo, f, data, False, False, None)
391 return context.memfilectx(repo, f, data, False, False, None)
397
392
398 self.ui.status(_("updating tags\n"))
393 self.ui.status(_("updating tags\n"))
399 date = "%s 0" % int(time.mktime(time.gmtime()))
394 date = "%s 0" % int(time.mktime(time.gmtime()))
400 extra = {'branch': self.tagsbranch}
395 extra = {'branch': self.tagsbranch}
401 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
396 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
402 [".hgtags"], getfilectx, "convert-repo", date,
397 [".hgtags"], getfilectx, "convert-repo", date,
403 extra)
398 extra)
404 node = self.repo.commitctx(ctx)
399 node = self.repo.commitctx(ctx)
405 return hex(node), hex(tagparent)
400 return hex(node), hex(tagparent)
406
401
407 def setfilemapmode(self, active):
402 def setfilemapmode(self, active):
408 self.filemapmode = active
403 self.filemapmode = active
409
404
410 def putbookmarks(self, updatedbookmark):
405 def putbookmarks(self, updatedbookmark):
411 if not len(updatedbookmark):
406 if not len(updatedbookmark):
412 return
407 return
413 wlock = lock = tr = None
408 wlock = lock = tr = None
414 try:
409 try:
415 wlock = self.repo.wlock()
410 wlock = self.repo.wlock()
416 lock = self.repo.lock()
411 lock = self.repo.lock()
417 tr = self.repo.transaction('bookmark')
412 tr = self.repo.transaction('bookmark')
418 self.ui.status(_("updating bookmarks\n"))
413 self.ui.status(_("updating bookmarks\n"))
419 destmarks = self.repo._bookmarks
414 destmarks = self.repo._bookmarks
420 for bookmark in updatedbookmark:
415 for bookmark in updatedbookmark:
421 destmarks[bookmark] = bin(updatedbookmark[bookmark])
416 destmarks[bookmark] = bin(updatedbookmark[bookmark])
422 destmarks.recordchange(tr)
417 destmarks.recordchange(tr)
423 tr.close()
418 tr.close()
424 finally:
419 finally:
425 lockmod.release(lock, wlock, tr)
420 lockmod.release(lock, wlock, tr)
426
421
427 def hascommitfrommap(self, rev):
422 def hascommitfrommap(self, rev):
428 # the exact semantics of clonebranches is unclear so we can't say no
423 # the exact semantics of clonebranches is unclear so we can't say no
429 return rev in self.repo or self.clonebranches
424 return rev in self.repo or self.clonebranches
430
425
431 def hascommitforsplicemap(self, rev):
426 def hascommitforsplicemap(self, rev):
432 if rev not in self.repo and self.clonebranches:
427 if rev not in self.repo and self.clonebranches:
433 raise error.Abort(_('revision %s not found in destination '
428 raise error.Abort(_('revision %s not found in destination '
434 'repository (lookups with clonebranches=true '
429 'repository (lookups with clonebranches=true '
435 'are not implemented)') % rev)
430 'are not implemented)') % rev)
436 return rev in self.repo
431 return rev in self.repo
437
432
438 class mercurial_source(converter_source):
433 class mercurial_source(converter_source):
439 def __init__(self, ui, path, revs=None):
434 def __init__(self, ui, path, revs=None):
440 converter_source.__init__(self, ui, path, revs)
435 converter_source.__init__(self, ui, path, revs)
441 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
436 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
442 self.ignored = set()
437 self.ignored = set()
443 self.saverev = ui.configbool('convert', 'hg.saverev', False)
438 self.saverev = ui.configbool('convert', 'hg.saverev', False)
444 try:
439 try:
445 self.repo = hg.repository(self.ui, path)
440 self.repo = hg.repository(self.ui, path)
446 # try to provoke an exception if this isn't really a hg
441 # try to provoke an exception if this isn't really a hg
447 # repo, but some other bogus compatible-looking url
442 # repo, but some other bogus compatible-looking url
448 if not self.repo.local():
443 if not self.repo.local():
449 raise error.RepoError
444 raise error.RepoError
450 except error.RepoError:
445 except error.RepoError:
451 ui.traceback()
446 ui.traceback()
452 raise NoRepo(_("%s is not a local Mercurial repository") % path)
447 raise NoRepo(_("%s is not a local Mercurial repository") % path)
453 self.lastrev = None
448 self.lastrev = None
454 self.lastctx = None
449 self.lastctx = None
455 self._changescache = None, None
450 self._changescache = None, None
456 self.convertfp = None
451 self.convertfp = None
457 # Restrict converted revisions to startrev descendants
452 # Restrict converted revisions to startrev descendants
458 startnode = ui.config('convert', 'hg.startrev')
453 startnode = ui.config('convert', 'hg.startrev')
459 hgrevs = ui.config('convert', 'hg.revs')
454 hgrevs = ui.config('convert', 'hg.revs')
460 if hgrevs is None:
455 if hgrevs is None:
461 if startnode is not None:
456 if startnode is not None:
462 try:
457 try:
463 startnode = self.repo.lookup(startnode)
458 startnode = self.repo.lookup(startnode)
464 except error.RepoError:
459 except error.RepoError:
465 raise error.Abort(_('%s is not a valid start revision')
460 raise error.Abort(_('%s is not a valid start revision')
466 % startnode)
461 % startnode)
467 startrev = self.repo.changelog.rev(startnode)
462 startrev = self.repo.changelog.rev(startnode)
468 children = {startnode: 1}
463 children = {startnode: 1}
469 for r in self.repo.changelog.descendants([startrev]):
464 for r in self.repo.changelog.descendants([startrev]):
470 children[self.repo.changelog.node(r)] = 1
465 children[self.repo.changelog.node(r)] = 1
471 self.keep = children.__contains__
466 self.keep = children.__contains__
472 else:
467 else:
473 self.keep = util.always
468 self.keep = util.always
474 if revs:
469 if revs:
475 self._heads = [self.repo[r].node() for r in revs]
470 self._heads = [self.repo[r].node() for r in revs]
476 else:
471 else:
477 self._heads = self.repo.heads()
472 self._heads = self.repo.heads()
478 else:
473 else:
479 if revs or startnode is not None:
474 if revs or startnode is not None:
480 raise error.Abort(_('hg.revs cannot be combined with '
475 raise error.Abort(_('hg.revs cannot be combined with '
481 'hg.startrev or --rev'))
476 'hg.startrev or --rev'))
482 nodes = set()
477 nodes = set()
483 parents = set()
478 parents = set()
484 for r in scmutil.revrange(self.repo, [hgrevs]):
479 for r in scmutil.revrange(self.repo, [hgrevs]):
485 ctx = self.repo[r]
480 ctx = self.repo[r]
486 nodes.add(ctx.node())
481 nodes.add(ctx.node())
487 parents.update(p.node() for p in ctx.parents())
482 parents.update(p.node() for p in ctx.parents())
488 self.keep = nodes.__contains__
483 self.keep = nodes.__contains__
489 self._heads = nodes - parents
484 self._heads = nodes - parents
490
485
491 def _changectx(self, rev):
486 def _changectx(self, rev):
492 if self.lastrev != rev:
487 if self.lastrev != rev:
493 self.lastctx = self.repo[rev]
488 self.lastctx = self.repo[rev]
494 self.lastrev = rev
489 self.lastrev = rev
495 return self.lastctx
490 return self.lastctx
496
491
497 def _parents(self, ctx):
492 def _parents(self, ctx):
498 return [p for p in ctx.parents() if p and self.keep(p.node())]
493 return [p for p in ctx.parents() if p and self.keep(p.node())]
499
494
500 def getheads(self):
495 def getheads(self):
501 return [hex(h) for h in self._heads if self.keep(h)]
496 return [hex(h) for h in self._heads if self.keep(h)]
502
497
503 def getfile(self, name, rev):
498 def getfile(self, name, rev):
504 try:
499 try:
505 fctx = self._changectx(rev)[name]
500 fctx = self._changectx(rev)[name]
506 return fctx.data(), fctx.flags()
501 return fctx.data(), fctx.flags()
507 except error.LookupError:
502 except error.LookupError:
508 return None, None
503 return None, None
509
504
510 def _changedfiles(self, ctx1, ctx2):
505 def _changedfiles(self, ctx1, ctx2):
511 ma, r = [], []
506 ma, r = [], []
512 maappend = ma.append
507 maappend = ma.append
513 rappend = r.append
508 rappend = r.append
514 d = ctx1.manifest().diff(ctx2.manifest())
509 d = ctx1.manifest().diff(ctx2.manifest())
515 for f, ((node1, flag1), (node2, flag2)) in d.iteritems():
510 for f, ((node1, flag1), (node2, flag2)) in d.iteritems():
516 if node2 is None:
511 if node2 is None:
517 rappend(f)
512 rappend(f)
518 else:
513 else:
519 maappend(f)
514 maappend(f)
520 return ma, r
515 return ma, r
521
516
522 def getchanges(self, rev, full):
517 def getchanges(self, rev, full):
523 ctx = self._changectx(rev)
518 ctx = self._changectx(rev)
524 parents = self._parents(ctx)
519 parents = self._parents(ctx)
525 if full or not parents:
520 if full or not parents:
526 files = copyfiles = ctx.manifest()
521 files = copyfiles = ctx.manifest()
527 if parents:
522 if parents:
528 if self._changescache[0] == rev:
523 if self._changescache[0] == rev:
529 ma, r = self._changescache[1]
524 ma, r = self._changescache[1]
530 else:
525 else:
531 ma, r = self._changedfiles(parents[0], ctx)
526 ma, r = self._changedfiles(parents[0], ctx)
532 if not full:
527 if not full:
533 files = ma + r
528 files = ma + r
534 copyfiles = ma
529 copyfiles = ma
535 # _getcopies() is also run for roots and before filtering so missing
530 # _getcopies() is also run for roots and before filtering so missing
536 # revlogs are detected early
531 # revlogs are detected early
537 copies = self._getcopies(ctx, parents, copyfiles)
532 copies = self._getcopies(ctx, parents, copyfiles)
538 cleanp2 = set()
533 cleanp2 = set()
539 if len(parents) == 2:
534 if len(parents) == 2:
540 d = parents[1].manifest().diff(ctx.manifest(), clean=True)
535 d = parents[1].manifest().diff(ctx.manifest(), clean=True)
541 for f, value in d.iteritems():
536 for f, value in d.iteritems():
542 if value is None:
537 if value is None:
543 cleanp2.add(f)
538 cleanp2.add(f)
544 changes = [(f, rev) for f in files if f not in self.ignored]
539 changes = [(f, rev) for f in files if f not in self.ignored]
545 changes.sort()
540 changes.sort()
546 return changes, copies, cleanp2
541 return changes, copies, cleanp2
547
542
548 def _getcopies(self, ctx, parents, files):
543 def _getcopies(self, ctx, parents, files):
549 copies = {}
544 copies = {}
550 for name in files:
545 for name in files:
551 if name in self.ignored:
546 if name in self.ignored:
552 continue
547 continue
553 try:
548 try:
554 copysource, _copynode = ctx.filectx(name).renamed()
549 copysource, _copynode = ctx.filectx(name).renamed()
555 if copysource in self.ignored:
550 if copysource in self.ignored:
556 continue
551 continue
557 # Ignore copy sources not in parent revisions
552 # Ignore copy sources not in parent revisions
558 found = False
553 found = False
559 for p in parents:
554 for p in parents:
560 if copysource in p:
555 if copysource in p:
561 found = True
556 found = True
562 break
557 break
563 if not found:
558 if not found:
564 continue
559 continue
565 copies[name] = copysource
560 copies[name] = copysource
566 except TypeError:
561 except TypeError:
567 pass
562 pass
568 except error.LookupError as e:
563 except error.LookupError as e:
569 if not self.ignoreerrors:
564 if not self.ignoreerrors:
570 raise
565 raise
571 self.ignored.add(name)
566 self.ignored.add(name)
572 self.ui.warn(_('ignoring: %s\n') % e)
567 self.ui.warn(_('ignoring: %s\n') % e)
573 return copies
568 return copies
574
569
575 def getcommit(self, rev):
570 def getcommit(self, rev):
576 ctx = self._changectx(rev)
571 ctx = self._changectx(rev)
577 parents = [p.hex() for p in self._parents(ctx)]
572 parents = [p.hex() for p in self._parents(ctx)]
578 crev = rev
573 crev = rev
579
574
580 return commit(author=ctx.user(),
575 return commit(author=ctx.user(),
581 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
576 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
582 desc=ctx.description(), rev=crev, parents=parents,
577 desc=ctx.description(), rev=crev, parents=parents,
583 branch=ctx.branch(), extra=ctx.extra(),
578 branch=ctx.branch(), extra=ctx.extra(),
584 sortkey=ctx.rev(), saverev=self.saverev,
579 sortkey=ctx.rev(), saverev=self.saverev,
585 phase=ctx.phase())
580 phase=ctx.phase())
586
581
587 def gettags(self):
582 def gettags(self):
588 # This will get written to .hgtags, filter non global tags out.
583 # This will get written to .hgtags, filter non global tags out.
589 tags = [t for t in self.repo.tagslist()
584 tags = [t for t in self.repo.tagslist()
590 if self.repo.tagtype(t[0]) == 'global']
585 if self.repo.tagtype(t[0]) == 'global']
591 return dict([(name, hex(node)) for name, node in tags
586 return dict([(name, hex(node)) for name, node in tags
592 if self.keep(node)])
587 if self.keep(node)])
593
588
594 def getchangedfiles(self, rev, i):
589 def getchangedfiles(self, rev, i):
595 ctx = self._changectx(rev)
590 ctx = self._changectx(rev)
596 parents = self._parents(ctx)
591 parents = self._parents(ctx)
597 if not parents and i is None:
592 if not parents and i is None:
598 i = 0
593 i = 0
599 ma, r = ctx.manifest().keys(), []
594 ma, r = ctx.manifest().keys(), []
600 else:
595 else:
601 i = i or 0
596 i = i or 0
602 ma, r = self._changedfiles(parents[i], ctx)
597 ma, r = self._changedfiles(parents[i], ctx)
603 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
598 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
604
599
605 if i == 0:
600 if i == 0:
606 self._changescache = (rev, (ma, r))
601 self._changescache = (rev, (ma, r))
607
602
608 return ma + r
603 return ma + r
609
604
610 def converted(self, rev, destrev):
605 def converted(self, rev, destrev):
611 if self.convertfp is None:
606 if self.convertfp is None:
612 self.convertfp = open(self.repo.join('shamap'), 'a')
607 self.convertfp = open(self.repo.join('shamap'), 'a')
613 self.convertfp.write('%s %s\n' % (destrev, rev))
608 self.convertfp.write('%s %s\n' % (destrev, rev))
614 self.convertfp.flush()
609 self.convertfp.flush()
615
610
616 def before(self):
611 def before(self):
617 self.ui.debug('run hg source pre-conversion action\n')
612 self.ui.debug('run hg source pre-conversion action\n')
618
613
619 def after(self):
614 def after(self):
620 self.ui.debug('run hg source post-conversion action\n')
615 self.ui.debug('run hg source post-conversion action\n')
621
616
622 def hasnativeorder(self):
617 def hasnativeorder(self):
623 return True
618 return True
624
619
625 def hasnativeclose(self):
620 def hasnativeclose(self):
626 return True
621 return True
627
622
628 def lookuprev(self, rev):
623 def lookuprev(self, rev):
629 try:
624 try:
630 return hex(self.repo.lookup(rev))
625 return hex(self.repo.lookup(rev))
631 except (error.RepoError, error.LookupError):
626 except (error.RepoError, error.LookupError):
632 return None
627 return None
633
628
634 def getbookmarks(self):
629 def getbookmarks(self):
635 return bookmarks.listbookmarks(self.repo)
630 return bookmarks.listbookmarks(self.repo)
636
631
637 def checkrevformat(self, revstr, mapname='splicemap'):
632 def checkrevformat(self, revstr, mapname='splicemap'):
638 """ Mercurial, revision string is a 40 byte hex """
633 """ Mercurial, revision string is a 40 byte hex """
639 self.checkhexformat(revstr, mapname)
634 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now