##// END OF EJS Templates
convert: replace revision references in messages if they are >= short hashes...
Mads Kiilerich -
r23972:c408bf3b stable
parent child Browse files
Show More
@@ -1,478 +1,478 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, scmutil, exchange
25 25
26 26 from common import NoRepo, commit, converter_source, converter_sink
27 27
28 28 import re
29 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
29 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
30 30
31 31 class mercurial_sink(converter_sink):
32 32 def __init__(self, ui, path):
33 33 converter_sink.__init__(self, ui, path)
34 34 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
35 35 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
36 36 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
37 37 self.lastbranch = None
38 38 if os.path.isdir(path) and len(os.listdir(path)) > 0:
39 39 try:
40 40 self.repo = hg.repository(self.ui, path)
41 41 if not self.repo.local():
42 42 raise NoRepo(_('%s is not a local Mercurial repository')
43 43 % path)
44 44 except error.RepoError, err:
45 45 ui.traceback()
46 46 raise NoRepo(err.args[0])
47 47 else:
48 48 try:
49 49 ui.status(_('initializing destination %s repository\n') % path)
50 50 self.repo = hg.repository(self.ui, path, create=True)
51 51 if not self.repo.local():
52 52 raise NoRepo(_('%s is not a local Mercurial repository')
53 53 % path)
54 54 self.created.append(path)
55 55 except error.RepoError:
56 56 ui.traceback()
57 57 raise NoRepo(_("could not create hg repository %s as sink")
58 58 % path)
59 59 self.lock = None
60 60 self.wlock = None
61 61 self.filemapmode = False
62 62
63 63 def before(self):
64 64 self.ui.debug('run hg sink pre-conversion action\n')
65 65 self.wlock = self.repo.wlock()
66 66 self.lock = self.repo.lock()
67 67
68 68 def after(self):
69 69 self.ui.debug('run hg sink post-conversion action\n')
70 70 if self.lock:
71 71 self.lock.release()
72 72 if self.wlock:
73 73 self.wlock.release()
74 74
75 75 def revmapfile(self):
76 76 return self.repo.join("shamap")
77 77
78 78 def authorfile(self):
79 79 return self.repo.join("authormap")
80 80
81 81 def setbranch(self, branch, pbranches):
82 82 if not self.clonebranches:
83 83 return
84 84
85 85 setbranch = (branch != self.lastbranch)
86 86 self.lastbranch = branch
87 87 if not branch:
88 88 branch = 'default'
89 89 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
90 90 pbranch = pbranches and pbranches[0][1] or 'default'
91 91
92 92 branchpath = os.path.join(self.path, branch)
93 93 if setbranch:
94 94 self.after()
95 95 try:
96 96 self.repo = hg.repository(self.ui, branchpath)
97 97 except Exception:
98 98 self.repo = hg.repository(self.ui, branchpath, create=True)
99 99 self.before()
100 100
101 101 # pbranches may bring revisions from other branches (merge parents)
102 102 # Make sure we have them, or pull them.
103 103 missings = {}
104 104 for b in pbranches:
105 105 try:
106 106 self.repo.lookup(b[0])
107 107 except Exception:
108 108 missings.setdefault(b[1], []).append(b[0])
109 109
110 110 if missings:
111 111 self.after()
112 112 for pbranch, heads in sorted(missings.iteritems()):
113 113 pbranchpath = os.path.join(self.path, pbranch)
114 114 prepo = hg.peer(self.ui, {}, pbranchpath)
115 115 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
116 116 exchange.pull(self.repo, prepo,
117 117 [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, full):
133 133 files = dict(files)
134 134 def getfilectx(repo, memctx, f):
135 135 try:
136 136 v = files[f]
137 137 except KeyError:
138 138 return None
139 139 data, mode = source.getfile(f, v)
140 140 if data is None:
141 141 return None
142 142 if f == '.hgtags':
143 143 data = self._rewritetags(source, revmap, data)
144 144 return context.memfilectx(self.repo, f, data, 'l' in mode,
145 145 'x' in mode, copies.get(f))
146 146
147 147 pl = []
148 148 for p in parents:
149 149 if p not in pl:
150 150 pl.append(p)
151 151 parents = pl
152 152 nparents = len(parents)
153 153 if self.filemapmode and nparents == 1:
154 154 m1node = self.repo.changelog.read(bin(parents[0]))[0]
155 155 parent = parents[0]
156 156
157 157 if len(parents) < 2:
158 158 parents.append(nullid)
159 159 if len(parents) < 2:
160 160 parents.append(nullid)
161 161 p2 = parents.pop(0)
162 162
163 163 text = commit.desc
164 164
165 165 sha1s = re.findall(sha1re, text)
166 166 for sha1 in sha1s:
167 167 oldrev = source.lookuprev(sha1)
168 168 newrev = revmap.get(oldrev)
169 169 if newrev is not None:
170 170 text = text.replace(sha1, newrev[:len(sha1)])
171 171
172 172 extra = commit.extra.copy()
173 173
174 174 for label in ('source', 'transplant_source', 'rebase_source'):
175 175 node = extra.get(label)
176 176
177 177 if node is None:
178 178 continue
179 179
180 180 # Only transplant stores its reference in binary
181 181 if label == 'transplant_source':
182 182 node = hex(node)
183 183
184 184 newrev = revmap.get(node)
185 185 if newrev is not None:
186 186 if label == 'transplant_source':
187 187 newrev = bin(newrev)
188 188
189 189 extra[label] = newrev
190 190
191 191 if self.branchnames and commit.branch:
192 192 extra['branch'] = commit.branch
193 193 if commit.rev:
194 194 extra['convert_revision'] = commit.rev
195 195
196 196 while parents:
197 197 p1 = p2
198 198 p2 = parents.pop(0)
199 199 fileset = set(files)
200 200 if full:
201 201 fileset.update(self.repo[p1])
202 202 fileset.update(self.repo[p2])
203 203 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
204 204 getfilectx, commit.author, commit.date, extra)
205 205 self.repo.commitctx(ctx)
206 206 text = "(octopus merge fixup)\n"
207 207 p2 = hex(self.repo.changelog.tip())
208 208
209 209 if self.filemapmode and nparents == 1:
210 210 man = self.repo.manifest
211 211 mnode = self.repo.changelog.read(bin(p2))[0]
212 212 closed = 'close' in commit.extra
213 213 if not closed and not man.cmp(m1node, man.revision(mnode)):
214 214 self.ui.status(_("filtering out empty revision\n"))
215 215 self.repo.rollback(force=True)
216 216 return parent
217 217 return p2
218 218
219 219 def puttags(self, tags):
220 220 try:
221 221 parentctx = self.repo[self.tagsbranch]
222 222 tagparent = parentctx.node()
223 223 except error.RepoError:
224 224 parentctx = None
225 225 tagparent = nullid
226 226
227 227 oldlines = set()
228 228 for branch, heads in self.repo.branchmap().iteritems():
229 229 for h in heads:
230 230 if '.hgtags' in self.repo[h]:
231 231 oldlines.update(
232 232 set(self.repo[h]['.hgtags'].data().splitlines(True)))
233 233 oldlines = sorted(list(oldlines))
234 234
235 235 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
236 236 if newlines == oldlines:
237 237 return None, None
238 238
239 239 # if the old and new tags match, then there is nothing to update
240 240 oldtags = set()
241 241 newtags = set()
242 242 for line in oldlines:
243 243 s = line.strip().split(' ', 1)
244 244 if len(s) != 2:
245 245 continue
246 246 oldtags.add(s[1])
247 247 for line in newlines:
248 248 s = line.strip().split(' ', 1)
249 249 if len(s) != 2:
250 250 continue
251 251 if s[1] not in oldtags:
252 252 newtags.add(s[1].strip())
253 253
254 254 if not newtags:
255 255 return None, None
256 256
257 257 data = "".join(newlines)
258 258 def getfilectx(repo, memctx, f):
259 259 return context.memfilectx(repo, f, data, False, False, None)
260 260
261 261 self.ui.status(_("updating tags\n"))
262 262 date = "%s 0" % int(time.mktime(time.gmtime()))
263 263 extra = {'branch': self.tagsbranch}
264 264 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
265 265 [".hgtags"], getfilectx, "convert-repo", date,
266 266 extra)
267 267 self.repo.commitctx(ctx)
268 268 return hex(self.repo.changelog.tip()), hex(tagparent)
269 269
270 270 def setfilemapmode(self, active):
271 271 self.filemapmode = active
272 272
273 273 def putbookmarks(self, updatedbookmark):
274 274 if not len(updatedbookmark):
275 275 return
276 276
277 277 self.ui.status(_("updating bookmarks\n"))
278 278 destmarks = self.repo._bookmarks
279 279 for bookmark in updatedbookmark:
280 280 destmarks[bookmark] = bin(updatedbookmark[bookmark])
281 281 destmarks.write()
282 282
283 283 def hascommitfrommap(self, rev):
284 284 # the exact semantics of clonebranches is unclear so we can't say no
285 285 return rev in self.repo or self.clonebranches
286 286
287 287 def hascommitforsplicemap(self, rev):
288 288 if rev not in self.repo and self.clonebranches:
289 289 raise util.Abort(_('revision %s not found in destination '
290 290 'repository (lookups with clonebranches=true '
291 291 'are not implemented)') % rev)
292 292 return rev in self.repo
293 293
294 294 class mercurial_source(converter_source):
295 295 def __init__(self, ui, path, rev=None):
296 296 converter_source.__init__(self, ui, path, rev)
297 297 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
298 298 self.ignored = set()
299 299 self.saverev = ui.configbool('convert', 'hg.saverev', False)
300 300 try:
301 301 self.repo = hg.repository(self.ui, path)
302 302 # try to provoke an exception if this isn't really a hg
303 303 # repo, but some other bogus compatible-looking url
304 304 if not self.repo.local():
305 305 raise error.RepoError
306 306 except error.RepoError:
307 307 ui.traceback()
308 308 raise NoRepo(_("%s is not a local Mercurial repository") % path)
309 309 self.lastrev = None
310 310 self.lastctx = None
311 311 self._changescache = None, None
312 312 self.convertfp = None
313 313 # Restrict converted revisions to startrev descendants
314 314 startnode = ui.config('convert', 'hg.startrev')
315 315 hgrevs = ui.config('convert', 'hg.revs')
316 316 if hgrevs is None:
317 317 if startnode is not None:
318 318 try:
319 319 startnode = self.repo.lookup(startnode)
320 320 except error.RepoError:
321 321 raise util.Abort(_('%s is not a valid start revision')
322 322 % startnode)
323 323 startrev = self.repo.changelog.rev(startnode)
324 324 children = {startnode: 1}
325 325 for r in self.repo.changelog.descendants([startrev]):
326 326 children[self.repo.changelog.node(r)] = 1
327 327 self.keep = children.__contains__
328 328 else:
329 329 self.keep = util.always
330 330 if rev:
331 331 self._heads = [self.repo[rev].node()]
332 332 else:
333 333 self._heads = self.repo.heads()
334 334 else:
335 335 if rev or startnode is not None:
336 336 raise util.Abort(_('hg.revs cannot be combined with '
337 337 'hg.startrev or --rev'))
338 338 nodes = set()
339 339 parents = set()
340 340 for r in scmutil.revrange(self.repo, [hgrevs]):
341 341 ctx = self.repo[r]
342 342 nodes.add(ctx.node())
343 343 parents.update(p.node() for p in ctx.parents())
344 344 self.keep = nodes.__contains__
345 345 self._heads = nodes - parents
346 346
347 347 def changectx(self, rev):
348 348 if self.lastrev != rev:
349 349 self.lastctx = self.repo[rev]
350 350 self.lastrev = rev
351 351 return self.lastctx
352 352
353 353 def parents(self, ctx):
354 354 return [p for p in ctx.parents() if p and self.keep(p.node())]
355 355
356 356 def getheads(self):
357 357 return [hex(h) for h in self._heads if self.keep(h)]
358 358
359 359 def getfile(self, name, rev):
360 360 try:
361 361 fctx = self.changectx(rev)[name]
362 362 return fctx.data(), fctx.flags()
363 363 except error.LookupError:
364 364 return None, None
365 365
366 366 def getchanges(self, rev, full):
367 367 ctx = self.changectx(rev)
368 368 parents = self.parents(ctx)
369 369 if full or not parents:
370 370 files = copyfiles = ctx.manifest()
371 371 if parents:
372 372 if self._changescache[0] == rev:
373 373 m, a, r = self._changescache[1]
374 374 else:
375 375 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
376 376 if not full:
377 377 files = m + a + r
378 378 copyfiles = m + a
379 379 # getcopies() is also run for roots and before filtering so missing
380 380 # revlogs are detected early
381 381 copies = self.getcopies(ctx, parents, copyfiles)
382 382 changes = [(f, rev) for f in files if f not in self.ignored]
383 383 changes.sort()
384 384 return changes, copies
385 385
386 386 def getcopies(self, ctx, parents, files):
387 387 copies = {}
388 388 for name in files:
389 389 if name in self.ignored:
390 390 continue
391 391 try:
392 392 copysource, _copynode = ctx.filectx(name).renamed()
393 393 if copysource in self.ignored:
394 394 continue
395 395 # Ignore copy sources not in parent revisions
396 396 found = False
397 397 for p in parents:
398 398 if copysource in p:
399 399 found = True
400 400 break
401 401 if not found:
402 402 continue
403 403 copies[name] = copysource
404 404 except TypeError:
405 405 pass
406 406 except error.LookupError, e:
407 407 if not self.ignoreerrors:
408 408 raise
409 409 self.ignored.add(name)
410 410 self.ui.warn(_('ignoring: %s\n') % e)
411 411 return copies
412 412
413 413 def getcommit(self, rev):
414 414 ctx = self.changectx(rev)
415 415 parents = [p.hex() for p in self.parents(ctx)]
416 416 if self.saverev:
417 417 crev = rev
418 418 else:
419 419 crev = None
420 420 return commit(author=ctx.user(),
421 421 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
422 422 desc=ctx.description(), rev=crev, parents=parents,
423 423 branch=ctx.branch(), extra=ctx.extra(),
424 424 sortkey=ctx.rev())
425 425
426 426 def gettags(self):
427 427 # This will get written to .hgtags, filter non global tags out.
428 428 tags = [t for t in self.repo.tagslist()
429 429 if self.repo.tagtype(t[0]) == 'global']
430 430 return dict([(name, hex(node)) for name, node in tags
431 431 if self.keep(node)])
432 432
433 433 def getchangedfiles(self, rev, i):
434 434 ctx = self.changectx(rev)
435 435 parents = self.parents(ctx)
436 436 if not parents and i is None:
437 437 i = 0
438 438 changes = [], ctx.manifest().keys(), []
439 439 else:
440 440 i = i or 0
441 441 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
442 442 changes = [[f for f in l if f not in self.ignored] for l in changes]
443 443
444 444 if i == 0:
445 445 self._changescache = (rev, changes)
446 446
447 447 return changes[0] + changes[1] + changes[2]
448 448
449 449 def converted(self, rev, destrev):
450 450 if self.convertfp is None:
451 451 self.convertfp = open(self.repo.join('shamap'), 'a')
452 452 self.convertfp.write('%s %s\n' % (destrev, rev))
453 453 self.convertfp.flush()
454 454
455 455 def before(self):
456 456 self.ui.debug('run hg source pre-conversion action\n')
457 457
458 458 def after(self):
459 459 self.ui.debug('run hg source post-conversion action\n')
460 460
461 461 def hasnativeorder(self):
462 462 return True
463 463
464 464 def hasnativeclose(self):
465 465 return True
466 466
467 467 def lookuprev(self, rev):
468 468 try:
469 469 return hex(self.repo.lookup(rev))
470 470 except (error.RepoError, error.LookupError):
471 471 return None
472 472
473 473 def getbookmarks(self):
474 474 return bookmarks.listbookmarks(self.repo)
475 475
476 476 def checkrevformat(self, revstr, mapname='splicemap'):
477 477 """ Mercurial, revision string is a 40 byte hex """
478 478 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now