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