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