##// END OF EJS Templates
convert: refactor head calculation for hg sources
Mads Kiilerich -
r19890:9057855d default
parent child Browse files
Show More
@@ -1,403 +1,403 b''
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 or any later version.
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, bookmarks, 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 repository')
40 40 % path)
41 41 except error.RepoError, err:
42 42 ui.traceback()
43 43 raise NoRepo(err.args[0])
44 44 else:
45 45 try:
46 46 ui.status(_('initializing destination %s repository\n') % path)
47 47 self.repo = hg.repository(self.ui, path, create=True)
48 48 if not self.repo.local():
49 49 raise NoRepo(_('%s is not a local Mercurial repository')
50 50 % path)
51 51 self.created.append(path)
52 52 except error.RepoError:
53 53 ui.traceback()
54 54 raise NoRepo(_("could not create hg repository %s as sink")
55 55 % path)
56 56 self.lock = None
57 57 self.wlock = None
58 58 self.filemapmode = False
59 59
60 60 def before(self):
61 61 self.ui.debug('run hg sink pre-conversion action\n')
62 62 self.wlock = self.repo.wlock()
63 63 self.lock = self.repo.lock()
64 64
65 65 def after(self):
66 66 self.ui.debug('run hg sink post-conversion action\n')
67 67 if self.lock:
68 68 self.lock.release()
69 69 if self.wlock:
70 70 self.wlock.release()
71 71
72 72 def revmapfile(self):
73 73 return self.repo.join("shamap")
74 74
75 75 def authorfile(self):
76 76 return self.repo.join("authormap")
77 77
78 78 def getheads(self):
79 79 h = self.repo.changelog.heads()
80 80 return [hex(x) for x in h]
81 81
82 82 def setbranch(self, branch, pbranches):
83 83 if not self.clonebranches:
84 84 return
85 85
86 86 setbranch = (branch != self.lastbranch)
87 87 self.lastbranch = branch
88 88 if not branch:
89 89 branch = 'default'
90 90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
91 91 pbranch = pbranches and pbranches[0][1] or 'default'
92 92
93 93 branchpath = os.path.join(self.path, branch)
94 94 if setbranch:
95 95 self.after()
96 96 try:
97 97 self.repo = hg.repository(self.ui, branchpath)
98 98 except Exception:
99 99 self.repo = hg.repository(self.ui, branchpath, create=True)
100 100 self.before()
101 101
102 102 # pbranches may bring revisions from other branches (merge parents)
103 103 # Make sure we have them, or pull them.
104 104 missings = {}
105 105 for b in pbranches:
106 106 try:
107 107 self.repo.lookup(b[0])
108 108 except Exception:
109 109 missings.setdefault(b[1], []).append(b[0])
110 110
111 111 if missings:
112 112 self.after()
113 113 for pbranch, heads in sorted(missings.iteritems()):
114 114 pbranchpath = os.path.join(self.path, pbranch)
115 115 prepo = hg.peer(self.ui, {}, pbranchpath)
116 116 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
117 117 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
118 118 self.before()
119 119
120 120 def _rewritetags(self, source, revmap, data):
121 121 fp = cStringIO.StringIO()
122 122 for line in data.splitlines():
123 123 s = line.split(' ', 1)
124 124 if len(s) != 2:
125 125 continue
126 126 revid = revmap.get(source.lookuprev(s[0]))
127 127 if not revid:
128 128 continue
129 129 fp.write('%s %s\n' % (revid, s[1]))
130 130 return fp.getvalue()
131 131
132 132 def putcommit(self, files, copies, parents, commit, source, revmap):
133 133
134 134 files = dict(files)
135 135 def getfilectx(repo, memctx, f):
136 136 v = files[f]
137 137 data, mode = source.getfile(f, v)
138 138 if f == '.hgtags':
139 139 data = self._rewritetags(source, revmap, data)
140 140 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
141 141 copies.get(f))
142 142
143 143 pl = []
144 144 for p in parents:
145 145 if p not in pl:
146 146 pl.append(p)
147 147 parents = pl
148 148 nparents = len(parents)
149 149 if self.filemapmode and nparents == 1:
150 150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
151 151 parent = parents[0]
152 152
153 153 if len(parents) < 2:
154 154 parents.append(nullid)
155 155 if len(parents) < 2:
156 156 parents.append(nullid)
157 157 p2 = parents.pop(0)
158 158
159 159 text = commit.desc
160 160 extra = commit.extra.copy()
161 161 if self.branchnames and commit.branch:
162 162 extra['branch'] = commit.branch
163 163 if commit.rev:
164 164 extra['convert_revision'] = commit.rev
165 165
166 166 while parents:
167 167 p1 = p2
168 168 p2 = parents.pop(0)
169 169 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
170 170 getfilectx, commit.author, commit.date, extra)
171 171 self.repo.commitctx(ctx)
172 172 text = "(octopus merge fixup)\n"
173 173 p2 = hex(self.repo.changelog.tip())
174 174
175 175 if self.filemapmode and nparents == 1:
176 176 man = self.repo.manifest
177 177 mnode = self.repo.changelog.read(bin(p2))[0]
178 178 closed = 'close' in commit.extra
179 179 if not closed and not man.cmp(m1node, man.revision(mnode)):
180 180 self.ui.status(_("filtering out empty revision\n"))
181 181 self.repo.rollback(force=True)
182 182 return parent
183 183 return p2
184 184
185 185 def puttags(self, tags):
186 186 try:
187 187 parentctx = self.repo[self.tagsbranch]
188 188 tagparent = parentctx.node()
189 189 except error.RepoError:
190 190 parentctx = None
191 191 tagparent = nullid
192 192
193 193 try:
194 194 oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
195 195 except Exception:
196 196 oldlines = []
197 197
198 198 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
199 199 if newlines == oldlines:
200 200 return None, None
201 201 data = "".join(newlines)
202 202 def getfilectx(repo, memctx, f):
203 203 return context.memfilectx(f, data, False, False, None)
204 204
205 205 self.ui.status(_("updating tags\n"))
206 206 date = "%s 0" % int(time.mktime(time.gmtime()))
207 207 extra = {'branch': self.tagsbranch}
208 208 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
209 209 [".hgtags"], getfilectx, "convert-repo", date,
210 210 extra)
211 211 self.repo.commitctx(ctx)
212 212 return hex(self.repo.changelog.tip()), hex(tagparent)
213 213
214 214 def setfilemapmode(self, active):
215 215 self.filemapmode = active
216 216
217 217 def putbookmarks(self, updatedbookmark):
218 218 if not len(updatedbookmark):
219 219 return
220 220
221 221 self.ui.status(_("updating bookmarks\n"))
222 222 destmarks = self.repo._bookmarks
223 223 for bookmark in updatedbookmark:
224 224 destmarks[bookmark] = bin(updatedbookmark[bookmark])
225 225 destmarks.write()
226 226
227 227 def hascommit(self, rev):
228 228 if rev not in self.repo and self.clonebranches:
229 229 raise util.Abort(_('revision %s not found in destination '
230 230 'repository (lookups with clonebranches=true '
231 231 'are not implemented)') % rev)
232 232 return rev in self.repo
233 233
234 234 class mercurial_source(converter_source):
235 235 def __init__(self, ui, path, rev=None):
236 236 converter_source.__init__(self, ui, path, rev)
237 237 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
238 238 self.ignored = set()
239 239 self.saverev = ui.configbool('convert', 'hg.saverev', False)
240 240 try:
241 241 self.repo = hg.repository(self.ui, path)
242 242 # try to provoke an exception if this isn't really a hg
243 243 # repo, but some other bogus compatible-looking url
244 244 if not self.repo.local():
245 245 raise error.RepoError
246 246 except error.RepoError:
247 247 ui.traceback()
248 248 raise NoRepo(_("%s is not a local Mercurial repository") % path)
249 249 self.lastrev = None
250 250 self.lastctx = None
251 251 self._changescache = None
252 252 self.convertfp = None
253 253 # Restrict converted revisions to startrev descendants
254 254 startnode = ui.config('convert', 'hg.startrev')
255 255 if startnode is not None:
256 256 try:
257 257 startnode = self.repo.lookup(startnode)
258 258 except error.RepoError:
259 259 raise util.Abort(_('%s is not a valid start revision')
260 260 % startnode)
261 261 startrev = self.repo.changelog.rev(startnode)
262 262 children = {startnode: 1}
263 for rev in self.repo.changelog.descendants([startrev]):
264 children[self.repo.changelog.node(rev)] = 1
263 for r in self.repo.changelog.descendants([startrev]):
264 children[self.repo.changelog.node(r)] = 1
265 265 self.keep = children.__contains__
266 266 else:
267 267 self.keep = util.always
268 if rev:
269 self._heads = [self.repo[rev].node()]
270 else:
271 self._heads = self.repo.heads()
268 272
269 273 def changectx(self, rev):
270 274 if self.lastrev != rev:
271 275 self.lastctx = self.repo[rev]
272 276 self.lastrev = rev
273 277 return self.lastctx
274 278
275 279 def parents(self, ctx):
276 280 return [p for p in ctx.parents() if p and self.keep(p.node())]
277 281
278 282 def getheads(self):
279 if self.rev:
280 heads = [self.repo[self.rev].node()]
281 else:
282 heads = self.repo.heads()
283 return [hex(h) for h in heads if self.keep(h)]
283 return [hex(h) for h in self._heads if self.keep(h)]
284 284
285 285 def getfile(self, name, rev):
286 286 try:
287 287 fctx = self.changectx(rev)[name]
288 288 return fctx.data(), fctx.flags()
289 289 except error.LookupError, err:
290 290 raise IOError(err)
291 291
292 292 def getchanges(self, rev):
293 293 ctx = self.changectx(rev)
294 294 parents = self.parents(ctx)
295 295 if not parents:
296 296 files = sorted(ctx.manifest())
297 297 # getcopies() is not needed for roots, but it is a simple way to
298 298 # detect missing revlogs and abort on errors or populate
299 299 # self.ignored
300 300 self.getcopies(ctx, parents, files)
301 301 return [(f, rev) for f in files if f not in self.ignored], {}
302 302 if self._changescache and self._changescache[0] == rev:
303 303 m, a, r = self._changescache[1]
304 304 else:
305 305 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
306 306 # getcopies() detects missing revlogs early, run it before
307 307 # filtering the changes.
308 308 copies = self.getcopies(ctx, parents, m + a)
309 309 changes = [(name, rev) for name in m + a + r
310 310 if name not in self.ignored]
311 311 return sorted(changes), copies
312 312
313 313 def getcopies(self, ctx, parents, files):
314 314 copies = {}
315 315 for name in files:
316 316 if name in self.ignored:
317 317 continue
318 318 try:
319 319 copysource, _copynode = ctx.filectx(name).renamed()
320 320 if copysource in self.ignored:
321 321 continue
322 322 # Ignore copy sources not in parent revisions
323 323 found = False
324 324 for p in parents:
325 325 if copysource in p:
326 326 found = True
327 327 break
328 328 if not found:
329 329 continue
330 330 copies[name] = copysource
331 331 except TypeError:
332 332 pass
333 333 except error.LookupError, e:
334 334 if not self.ignoreerrors:
335 335 raise
336 336 self.ignored.add(name)
337 337 self.ui.warn(_('ignoring: %s\n') % e)
338 338 return copies
339 339
340 340 def getcommit(self, rev):
341 341 ctx = self.changectx(rev)
342 342 parents = [p.hex() for p in self.parents(ctx)]
343 343 if self.saverev:
344 344 crev = rev
345 345 else:
346 346 crev = None
347 347 return commit(author=ctx.user(),
348 348 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
349 349 desc=ctx.description(), rev=crev, parents=parents,
350 350 branch=ctx.branch(), extra=ctx.extra(),
351 351 sortkey=ctx.rev())
352 352
353 353 def gettags(self):
354 354 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
355 355 return dict([(name, hex(node)) for name, node in tags
356 356 if self.keep(node)])
357 357
358 358 def getchangedfiles(self, rev, i):
359 359 ctx = self.changectx(rev)
360 360 parents = self.parents(ctx)
361 361 if not parents and i is None:
362 362 i = 0
363 363 changes = [], ctx.manifest().keys(), []
364 364 else:
365 365 i = i or 0
366 366 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
367 367 changes = [[f for f in l if f not in self.ignored] for l in changes]
368 368
369 369 if i == 0:
370 370 self._changescache = (rev, changes)
371 371
372 372 return changes[0] + changes[1] + changes[2]
373 373
374 374 def converted(self, rev, destrev):
375 375 if self.convertfp is None:
376 376 self.convertfp = open(self.repo.join('shamap'), 'a')
377 377 self.convertfp.write('%s %s\n' % (destrev, rev))
378 378 self.convertfp.flush()
379 379
380 380 def before(self):
381 381 self.ui.debug('run hg source pre-conversion action\n')
382 382
383 383 def after(self):
384 384 self.ui.debug('run hg source post-conversion action\n')
385 385
386 386 def hasnativeorder(self):
387 387 return True
388 388
389 389 def hasnativeclose(self):
390 390 return True
391 391
392 392 def lookuprev(self, rev):
393 393 try:
394 394 return hex(self.repo.lookup(rev))
395 395 except error.RepoError:
396 396 return None
397 397
398 398 def getbookmarks(self):
399 399 return bookmarks.listbookmarks(self.repo)
400 400
401 401 def checkrevformat(self, revstr):
402 402 """ Mercurial, revision string is a 40 byte hex """
403 403 self.checkhexformat(revstr)
General Comments 0
You need to be logged in to leave comments. Login now