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