##// END OF EJS Templates
convert/hg: make parents() return changectx, not nodes
Patrick Mezard -
r9531:a2f36a08 default
parent child Browse files
Show More
@@ -1,363 +1,362
1 1 # hg.py - hg backend for convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 # Notes for hg->hg conversion:
9 9 #
10 10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 11 # of commit messages, but new versions do. Changesets created by
12 12 # those older versions, then converted, may thus have different
13 13 # hashes for changesets that are otherwise identical.
14 14 #
15 15 # * Using "--config convert.hg.saverev=true" will make the source
16 16 # identifier to be stored in the converted revision. This will cause
17 17 # the converted revision to have a different identity than the
18 18 # source.
19 19
20 20
21 21 import os, time, cStringIO
22 22 from mercurial.i18n import _
23 23 from mercurial.node import bin, hex, nullid
24 24 from mercurial import hg, util, context, error
25 25
26 26 from common import NoRepo, commit, converter_source, converter_sink
27 27
28 28 class mercurial_sink(converter_sink):
29 29 def __init__(self, ui, path):
30 30 converter_sink.__init__(self, ui, path)
31 31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
32 32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
33 33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
34 34 self.lastbranch = None
35 35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
36 36 try:
37 37 self.repo = hg.repository(self.ui, path)
38 38 if not self.repo.local():
39 39 raise NoRepo(_('%s is not a local Mercurial repo') % path)
40 40 except error.RepoError, err:
41 41 ui.traceback()
42 42 raise NoRepo(err.args[0])
43 43 else:
44 44 try:
45 45 ui.status(_('initializing destination %s repository\n') % path)
46 46 self.repo = hg.repository(self.ui, path, create=True)
47 47 if not self.repo.local():
48 48 raise NoRepo(_('%s is not a local Mercurial repo') % path)
49 49 self.created.append(path)
50 50 except error.RepoError:
51 51 ui.traceback()
52 52 raise NoRepo("could not create hg repo %s as sink" % path)
53 53 self.lock = None
54 54 self.wlock = None
55 55 self.filemapmode = False
56 56
57 57 def before(self):
58 58 self.ui.debug(_('run hg sink pre-conversion action\n'))
59 59 self.wlock = self.repo.wlock()
60 60 self.lock = self.repo.lock()
61 61
62 62 def after(self):
63 63 self.ui.debug(_('run hg sink post-conversion action\n'))
64 64 self.lock.release()
65 65 self.wlock.release()
66 66
67 67 def revmapfile(self):
68 68 return os.path.join(self.path, ".hg", "shamap")
69 69
70 70 def authorfile(self):
71 71 return os.path.join(self.path, ".hg", "authormap")
72 72
73 73 def getheads(self):
74 74 h = self.repo.changelog.heads()
75 75 return [ hex(x) for x in h ]
76 76
77 77 def setbranch(self, branch, pbranches):
78 78 if not self.clonebranches:
79 79 return
80 80
81 81 setbranch = (branch != self.lastbranch)
82 82 self.lastbranch = branch
83 83 if not branch:
84 84 branch = 'default'
85 85 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
86 86 pbranch = pbranches and pbranches[0][1] or 'default'
87 87
88 88 branchpath = os.path.join(self.path, branch)
89 89 if setbranch:
90 90 self.after()
91 91 try:
92 92 self.repo = hg.repository(self.ui, branchpath)
93 93 except:
94 94 self.repo = hg.repository(self.ui, branchpath, create=True)
95 95 self.before()
96 96
97 97 # pbranches may bring revisions from other branches (merge parents)
98 98 # Make sure we have them, or pull them.
99 99 missings = {}
100 100 for b in pbranches:
101 101 try:
102 102 self.repo.lookup(b[0])
103 103 except:
104 104 missings.setdefault(b[1], []).append(b[0])
105 105
106 106 if missings:
107 107 self.after()
108 108 for pbranch, heads in missings.iteritems():
109 109 pbranchpath = os.path.join(self.path, pbranch)
110 110 prepo = hg.repository(self.ui, pbranchpath)
111 111 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
112 112 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
113 113 self.before()
114 114
115 115 def _rewritetags(self, source, revmap, data):
116 116 fp = cStringIO.StringIO()
117 117 for line in data.splitlines():
118 118 s = line.split(' ', 1)
119 119 if len(s) != 2:
120 120 continue
121 121 revid = revmap.get(source.lookuprev(s[0]))
122 122 if not revid:
123 123 continue
124 124 fp.write('%s %s\n' % (revid, s[1]))
125 125 return fp.getvalue()
126 126
127 127 def putcommit(self, files, copies, parents, commit, source, revmap):
128 128
129 129 files = dict(files)
130 130 def getfilectx(repo, memctx, f):
131 131 v = files[f]
132 132 data = source.getfile(f, v)
133 133 e = source.getmode(f, v)
134 134 if f == '.hgtags':
135 135 data = self._rewritetags(source, revmap, data)
136 136 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
137 137
138 138 pl = []
139 139 for p in parents:
140 140 if p not in pl:
141 141 pl.append(p)
142 142 parents = pl
143 143 nparents = len(parents)
144 144 if self.filemapmode and nparents == 1:
145 145 m1node = self.repo.changelog.read(bin(parents[0]))[0]
146 146 parent = parents[0]
147 147
148 148 if len(parents) < 2: parents.append(nullid)
149 149 if len(parents) < 2: parents.append(nullid)
150 150 p2 = parents.pop(0)
151 151
152 152 text = commit.desc
153 153 extra = commit.extra.copy()
154 154 if self.branchnames and commit.branch:
155 155 extra['branch'] = commit.branch
156 156 if commit.rev:
157 157 extra['convert_revision'] = commit.rev
158 158
159 159 while parents:
160 160 p1 = p2
161 161 p2 = parents.pop(0)
162 162 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx,
163 163 commit.author, commit.date, extra)
164 164 self.repo.commitctx(ctx)
165 165 text = "(octopus merge fixup)\n"
166 166 p2 = hex(self.repo.changelog.tip())
167 167
168 168 if self.filemapmode and nparents == 1:
169 169 man = self.repo.manifest
170 170 mnode = self.repo.changelog.read(bin(p2))[0]
171 171 if not man.cmp(m1node, man.revision(mnode)):
172 172 self.ui.status(_("filtering out empty revision\n"))
173 173 self.repo.rollback()
174 174 return parent
175 175 return p2
176 176
177 177 def puttags(self, tags):
178 178 try:
179 179 parentctx = self.repo[self.tagsbranch]
180 180 tagparent = parentctx.node()
181 181 except error.RepoError:
182 182 parentctx = None
183 183 tagparent = nullid
184 184
185 185 try:
186 186 oldlines = sorted(parentctx['.hgtags'].data().splitlines(1))
187 187 except:
188 188 oldlines = []
189 189
190 190 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
191 191 if newlines == oldlines:
192 192 return None, None
193 193 data = "".join(newlines)
194 194 def getfilectx(repo, memctx, f):
195 195 return context.memfilectx(f, data, False, False, None)
196 196
197 197 self.ui.status(_("updating tags\n"))
198 198 date = "%s 0" % int(time.mktime(time.gmtime()))
199 199 extra = {'branch': self.tagsbranch}
200 200 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
201 201 [".hgtags"], getfilectx, "convert-repo", date,
202 202 extra)
203 203 self.repo.commitctx(ctx)
204 204 return hex(self.repo.changelog.tip()), hex(tagparent)
205 205
206 206 def setfilemapmode(self, active):
207 207 self.filemapmode = active
208 208
209 209 class mercurial_source(converter_source):
210 210 def __init__(self, ui, path, rev=None):
211 211 converter_source.__init__(self, ui, path, rev)
212 212 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
213 213 self.ignored = set()
214 214 self.saverev = ui.configbool('convert', 'hg.saverev', False)
215 215 try:
216 216 self.repo = hg.repository(self.ui, path)
217 217 # try to provoke an exception if this isn't really a hg
218 218 # repo, but some other bogus compatible-looking url
219 219 if not self.repo.local():
220 220 raise error.RepoError()
221 221 except error.RepoError:
222 222 ui.traceback()
223 223 raise NoRepo("%s is not a local Mercurial repo" % path)
224 224 self.lastrev = None
225 225 self.lastctx = None
226 226 self._changescache = None
227 227 self.convertfp = None
228 228 # Restrict converted revisions to startrev descendants
229 229 startnode = ui.config('convert', 'hg.startrev')
230 230 if startnode is not None:
231 231 try:
232 232 startnode = self.repo.lookup(startnode)
233 233 except error.RepoError:
234 234 raise util.Abort(_('%s is not a valid start revision')
235 235 % startnode)
236 236 startrev = self.repo.changelog.rev(startnode)
237 237 children = {startnode: 1}
238 238 for rev in self.repo.changelog.descendants(startrev):
239 239 children[self.repo.changelog.node(rev)] = 1
240 240 self.keep = children.__contains__
241 241 else:
242 242 self.keep = util.always
243 243
244 244 def changectx(self, rev):
245 245 if self.lastrev != rev:
246 246 self.lastctx = self.repo[rev]
247 247 self.lastrev = rev
248 248 return self.lastctx
249 249
250 250 def parents(self, ctx):
251 return [p.node() for p in ctx.parents()
252 if p and self.keep(p.node())]
251 return [p for p in ctx.parents() if p and self.keep(p.node())]
253 252
254 253 def getheads(self):
255 254 if self.rev:
256 255 heads = [self.repo[self.rev].node()]
257 256 else:
258 257 heads = self.repo.heads()
259 258 return [hex(h) for h in heads if self.keep(h)]
260 259
261 260 def getfile(self, name, rev):
262 261 try:
263 262 return self.changectx(rev)[name].data()
264 263 except error.LookupError, err:
265 264 raise IOError(err)
266 265
267 266 def getmode(self, name, rev):
268 267 return self.changectx(rev).manifest().flags(name)
269 268
270 269 def getchanges(self, rev):
271 270 ctx = self.changectx(rev)
272 271 parents = self.parents(ctx)
273 272 if not parents:
274 273 files = sorted(ctx.manifest())
275 274 if self.ignoreerrors:
276 275 # calling getcopies() is a simple way to detect missing
277 276 # revlogs and populate self.ignored
278 277 self.getcopies(ctx, files)
279 278 return [(f, rev) for f in files if f not in self.ignored], {}
280 279 if self._changescache and self._changescache[0] == rev:
281 280 m, a, r = self._changescache[1]
282 281 else:
283 m, a, r = self.repo.status(parents[0], ctx.node())[:3]
282 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
284 283 # getcopies() detects missing revlogs early, run it before
285 284 # filtering the changes.
286 285 copies = self.getcopies(ctx, m + a)
287 286 changes = [(name, rev) for name in m + a + r
288 287 if name not in self.ignored]
289 288 return sorted(changes), copies
290 289
291 290 def getcopies(self, ctx, files):
292 291 copies = {}
293 292 for name in files:
294 293 if name in self.ignored:
295 294 continue
296 295 try:
297 296 copysource, copynode = ctx.filectx(name).renamed()
298 297 if copysource in self.ignored or not self.keep(copynode):
299 298 continue
300 299 copies[name] = copysource
301 300 except TypeError:
302 301 pass
303 302 except error.LookupError, e:
304 303 if not self.ignoreerrors:
305 304 raise
306 305 self.ignored.add(name)
307 306 self.ui.warn(_('ignoring: %s\n') % e)
308 307 return copies
309 308
310 309 def getcommit(self, rev):
311 310 ctx = self.changectx(rev)
312 parents = [hex(p) for p in self.parents(ctx)]
311 parents = [p.hex() for p in self.parents(ctx)]
313 312 if self.saverev:
314 313 crev = rev
315 314 else:
316 315 crev = None
317 316 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
318 317 desc=ctx.description(), rev=crev, parents=parents,
319 318 branch=ctx.branch(), extra=ctx.extra(),
320 319 sortkey=ctx.rev())
321 320
322 321 def gettags(self):
323 322 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
324 323 return dict([(name, hex(node)) for name, node in tags
325 324 if self.keep(node)])
326 325
327 326 def getchangedfiles(self, rev, i):
328 327 ctx = self.changectx(rev)
329 328 parents = self.parents(ctx)
330 329 if not parents and i is None:
331 330 i = 0
332 331 changes = [], ctx.manifest().keys(), []
333 332 else:
334 333 i = i or 0
335 changes = self.repo.status(parents[i], ctx.node())[:3]
334 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
336 335 changes = [[f for f in l if f not in self.ignored] for l in changes]
337 336
338 337 if i == 0:
339 338 self._changescache = (rev, changes)
340 339
341 340 return changes[0] + changes[1] + changes[2]
342 341
343 342 def converted(self, rev, destrev):
344 343 if self.convertfp is None:
345 344 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
346 345 'a')
347 346 self.convertfp.write('%s %s\n' % (destrev, rev))
348 347 self.convertfp.flush()
349 348
350 349 def before(self):
351 350 self.ui.debug(_('run hg source pre-conversion action\n'))
352 351
353 352 def after(self):
354 353 self.ui.debug(_('run hg source post-conversion action\n'))
355 354
356 355 def hasnativeorder(self):
357 356 return True
358 357
359 358 def lookuprev(self, rev):
360 359 try:
361 360 return hex(self.repo.lookup(rev))
362 361 except error.RepoError:
363 362 return None
General Comments 0
You need to be logged in to leave comments. Login now