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