def updatebookmarks(repo, changes, name="git_handler"): """abstract writing bookmarks for backwards compatibility""" bms = repo._bookmarks tr = lock = wlock = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction(name) if hgutil.safehasattr(bms, "applychanges"): # applychanges was added in mercurial 4.3 bms.applychanges(repo, tr, changes) else: for name, node in changes: if node is None: del bms[name] else: bms[name] = node if hgutil.safehasattr(bms, "recordchange"): # recordchange was added in mercurial 3.2 bms.recordchange(tr) else: bms.write() tr.close() finally: lockmod.release(tr, lock, wlock)
def testlock(self): state = teststate(self, tempfile.mkdtemp(dir=os.getcwd())) lock = state.makelock() state.assertacquirecalled(True) lock.release() state.assertreleasecalled(True) state.assertpostreleasecalled(True) state.assertlockexists(False)
def metarewrite(repo, old, newbases, commitopts, copypreds=None): """Return (nodeid, created) where nodeid is the identifier of the changeset generated by the rewrite process, and created is True if nodeid was actually created. If created is False, nodeid references a changeset existing before the rewrite call. """ wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction("rewrite") updatebookmarks = bookmarksupdater(repo, old.node()) message = cmdutil.logmessage(repo, commitopts) if not message: message = old.description() user = commitopts.get("user") or old.user() date = commitopts.get("date") or None # old.date() extra = dict(commitopts.get("extra", old.extra())) extra["branch"] = old.branch() preds = [old.node()] mutop = "metaedit" if copypreds: preds.extend(copypreds) mutop = "metaedit-copy" mutinfo = mutation.record(repo, extra, preds, mutop) loginfo = {"predecessors": old.hex(), "mutation": mutop} new = context.metadataonlyctx( repo, old, parents=newbases, text=message, user=user, date=date, extra=extra, loginfo=loginfo, mutinfo=mutinfo, ) if commitopts.get("edit"): new._text = cmdutil.commitforceeditor(repo, new, []) revcount = len(repo) newid = repo.commitctx(new) new = repo[newid] created = len(repo) != revcount updatebookmarks(newid) tr.close() return newid, created finally: lockmod.release(tr, lock, wlock)
def testinheritcheck(self): d = tempfile.mkdtemp(dir=os.getcwd()) state = teststate(self, d) def check(): raise error.LockInheritanceContractViolation("check failed") lock = state.makelock(inheritchecker=check) state.assertacquirecalled(True) with self.assertRaises(error.LockInheritanceContractViolation): with lock.inherit(): pass lock.release()
def putbookmarks(self, updatedbookmark): if not len(updatedbookmark): return wlock = lock = tr = None try: wlock = self.repo.wlock() lock = self.repo.lock() tr = self.repo.transaction("bookmark") self.ui.status(_("updating bookmarks\n")) destmarks = self.repo._bookmarks changes = [(bookmark, nodemod.bin(updatedbookmark[bookmark])) for bookmark in updatedbookmark] destmarks.applychanges(self.repo, tr, changes) tr.close() finally: lockmod.release(lock, wlock, tr)
def _moveto(repo, bookmark, ctx, clean=False): """Moves the given bookmark and the working copy to the given revision. By default it does not overwrite the working copy contents unless clean is True. Assumes the wlock is already taken. """ # Move working copy over if clean: merge.update( repo, ctx.node(), False, # not a branchmerge True, # force overwriting files None, ) # not a partial update else: # Mark any files that are different between the two as normal-lookup # so they show up correctly in hg status afterwards. wctx = repo[None] m1 = wctx.manifest() m2 = ctx.manifest() diff = m1.diff(m2) changedfiles = [] changedfiles.extend(pycompat.iterkeys(diff)) dirstate = repo.dirstate dirchanges = [f for f in dirstate if dirstate[f] != "n"] changedfiles.extend(dirchanges) if changedfiles or ctx.node() != repo["."].node(): with dirstate.parentchange(): dirstate.rebuild(ctx.node(), m2, changedfiles) # Move bookmark over if bookmark: lock = tr = None try: lock = repo.lock() tr = repo.transaction("reset") changes = [(bookmark, ctx.node())] repo._bookmarks.applychanges(repo, tr, changes) tr.close() finally: lockmod.release(lock, tr)
def testlockfork(self): state = teststate(self, tempfile.mkdtemp(dir=os.getcwd())) lock = state.makelock() state.assertacquirecalled(True) # fake a fork forklock = copy.deepcopy(lock) forklock._pidoffset = 1 forklock.release() state.assertreleasecalled(False) state.assertpostreleasecalled(False) state.assertlockexists(True) # release the actual lock lock.release() state.assertreleasecalled(True) state.assertpostreleasecalled(True) state.assertlockexists(False)
def fixupamend(ui, repo, noconflict=None, noconflictmsg=None): """rebases any children found on the preamend changset and strips the preamend changset """ wlock = None lock = None tr = None try: wlock = repo.wlock() lock = repo.lock() current = repo["."] # Use obsolescence information to fix up the amend. common.restackonce( ui, repo, current.rev(), noconflict=noconflict, noconflictmsg=noconflictmsg ) finally: lockmod.release(wlock, lock, tr)
def testrecursivelock(self): state = teststate(self, tempfile.mkdtemp(dir=os.getcwd())) lock = state.makelock() state.assertacquirecalled(True) state.resetacquirefn() lock.lock() # recursive lock should not call acquirefn again state.assertacquirecalled(False) lock.release() # brings lock refcount down from 2 to 1 state.assertreleasecalled(False) state.assertpostreleasecalled(False) state.assertlockexists(True) lock.release() # releases the lock state.assertreleasecalled(True) state.assertpostreleasecalled(True) state.assertlockexists(False)
def run(self): state = self.state repo, ctxnode = state.repo, state.parentctxnode with repo.wlock(), repo.lock(), repo.transaction("histedit"): hg.update(repo, ctxnode) # release locks so the program can call hg and then relock. lock.release(state.lock, state.wlock) try: ctx = repo[ctxnode] shell = encoding.environ.get("SHELL", None) cmd = self.command if shell and self.repo.ui.config("fbhistedit", "exec_in_user_shell"): cmd = "%s -c -i %s" % (shell, quote(cmd)) rc = repo.ui.system( cmd, environ={"HGNODE": ctx.hex()}, cwd=self.cwd, blockedtag="histedit_exec", ) except OSError as ose: raise error.InterventionRequired( _("Cannot execute command '%s': %s") % (self.command, ose)) finally: # relock the repository state.wlock = repo.wlock() state.lock = repo.lock() repo.invalidateall() if rc != 0: raise error.InterventionRequired( _("Command '%s' failed with exit status %d") % (self.command, rc)) m, a, r, d = self.repo.status()[:4] if m or a or r or d: self.continuedirty() return self.continueclean()
def testlockfork(self): state = teststate(self, tempfile.mkdtemp(dir=os.getcwd())) lock = state.makelock() state.assertacquirecalled(True) pid = os.fork() if pid == 0: lock._pidoffset = os.getpid() lock.release() state.assertreleasecalled(False) state.assertpostreleasecalled(False) state.assertlockexists(True) os._exit(0) _, status = os.waitpid(pid, 0) self.assertTrue(((status >> 8) & 0x7F) == 0) # release the actual lock lock.release() state.assertreleasecalled(True) state.assertpostreleasecalled(True) state.assertlockexists(False)
def prune(ui, repo, *revs, **opts): """hide changesets by marking them obsolete Pruned changesets are obsolete with no successors. If they also have no descendants, they are hidden (invisible to all commands). Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve` to handle this situation. When you prune the parent of your working copy, Mercurial updates the working copy to a non-obsolete parent. You can use ``--succ`` to tell Mercurial that a newer version (successor) of the pruned changeset exists. Mercurial records successor revisions in obsolescence markers. You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between revisions to pruned (precursor) and successor changesets. This option may be removed in a future release (with the functionality provided automatically). If you specify multiple revisions in ``--succ``, you are recording a "split" and must acknowledge it by passing ``--split``. Similarly, when you prune multiple changesets with a single successor, you must pass the ``--fold`` option. """ if opts.get("keep", False): hint = "strip-uncommit" else: hint = "strip-hide" hintutil.trigger(hint) revs = scmutil.revrange(repo, list(revs) + opts.get("rev", [])) succs = opts.get("succ", []) bookmarks = set(opts.get("bookmark", ())) metadata = _getmetadata(**opts) biject = opts.get("biject") fold = opts.get("fold") split = opts.get("split") options = [o for o in ("biject", "fold", "split") if opts.get(o)] if 1 < len(options): raise error.Abort(_("can only specify one of %s") % ", ".join(options)) if bookmarks: revs += bookmarksmod.reachablerevs(repo, bookmarks) if not revs: # No revs are reachable exclusively from these bookmarks, just # delete the bookmarks. with repo.wlock(), repo.lock(), repo.transaction( "prune-bookmarks") as tr: bookmarksmod.delete(repo, tr, bookmarks) for bookmark in sorted(bookmarks): ui.write(_("bookmark '%s' deleted\n") % bookmark) return 0 if not revs: raise error.Abort(_("nothing to prune")) wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction("prune") # defines pruned changesets precs = [] revs.sort() for p in revs: cp = repo[p] if not cp.mutable(): # note: createmarkers() would have raised something anyway raise error.Abort( "cannot prune immutable changeset: %s" % cp, hint="see 'hg help phases' for details", ) precs.append(cp) if not precs: raise error.Abort("nothing to prune") # defines successors changesets sucs = scmutil.revrange(repo, succs) sucs.sort() sucs = tuple(repo[n] for n in sucs) if not biject and len(sucs) > 1 and len(precs) > 1: msg = "Can't use multiple successors for multiple precursors" hint = _("use --biject to mark a series as a replacement" " for another") raise error.Abort(msg, hint=hint) elif biject and len(sucs) != len(precs): msg = "Can't use %d successors for %d precursors" % (len(sucs), len(precs)) raise error.Abort(msg) elif (len(precs) == 1 and len(sucs) > 1) and not split: msg = "please add --split if you want to do a split" raise error.Abort(msg) elif len(sucs) == 1 and len(precs) > 1 and not fold: msg = "please add --fold if you want to do a fold" raise error.Abort(msg) elif biject: relations = [(p, (s, )) for p, s in zip(precs, sucs)] else: relations = [(p, sucs) for p in precs] wdp = repo["."] if len(sucs) == 1 and len(precs) == 1 and wdp in precs: # '.' killed, so update to the successor newnode = sucs[0] else: # update to an unkilled parent newnode = wdp while newnode in precs or newnode.obsolete(): newnode = newnode.parents()[0] if newnode.node() != wdp.node(): if opts.get("keep", False): # This is largely the same as the implementation in # strip.stripcmd(). We might want to refactor this somewhere # common at some point. # only reset the dirstate for files that would actually change # between the working context and uctx descendantrevs = repo.revs("%d::." % newnode.rev()) changedfiles = [] for rev in descendantrevs: # blindly reset the files, regardless of what actually # changed changedfiles.extend(repo[rev].files()) # reset files that only changed in the dirstate too dirstate = repo.dirstate dirchanges = [f for f in dirstate if dirstate[f] != "n"] changedfiles.extend(dirchanges) repo.dirstate.rebuild(newnode.node(), newnode.manifest(), changedfiles) dirstate.write(tr) else: bookactive = repo._activebookmark # Active bookmark that we don't want to delete (with -B option) # we deactivate and move it before the update and reactivate it # after movebookmark = bookactive and not bookmarks if movebookmark: bookmarksmod.deactivate(repo) changes = [(bookactive, newnode.node())] repo._bookmarks.applychanges(repo, tr, changes) commands.update(ui, repo, newnode.hex()) ui.status( _("working directory now at %s\n") % ui.label(str(newnode), "evolve.node")) if movebookmark: bookmarksmod.activate(repo, bookactive) # update bookmarks if bookmarks: with repo.wlock(), repo.lock(), repo.transaction( "prune-bookmarks") as tr: bookmarksmod.delete(repo, tr, bookmarks) for bookmark in sorted(bookmarks): ui.write(_("bookmark '%s' deleted\n") % bookmark) # create markers obsolete.createmarkers(repo, relations, metadata=metadata, operation="prune") # hide nodes visibility.remove(repo, [c.node() for c in precs]) # informs that changeset have been pruned ui.status(_("%i changesets pruned\n") % len(precs)) for ctx in repo.set("bookmark() and %ld", precs): # used to be: # # ldest = list(repo.set('max((::%d) - obsolete())', ctx)) # if ldest: # c = ldest[0] # # but then revset took a lazy arrow in the knee and became much # slower. The new forms makes as much sense and a much faster. for dest in ctx.ancestors(): if not dest.obsolete(): updatebookmarks = common.bookmarksupdater(repo, ctx.node()) updatebookmarks(dest.node()) break tr.close() finally: lockmod.release(tr, lock, wlock)
def _dounshelve(ui, repo, *shelved, **opts): abortf = opts.get("abort") continuef = opts.get("continue") if not abortf and not continuef: cmdutil.checkunfinished(repo) shelved = list(shelved) if opts.get("name"): shelved.append(opts["name"]) if abortf or continuef: if abortf and continuef: raise error.Abort(_("cannot use both abort and continue")) if shelved: raise error.Abort( _("cannot combine abort/continue with " "naming a shelved change")) if abortf and opts.get("tool", False): ui.warn(_("tool option will be ignored\n")) try: state = shelvedstate.load(repo) if opts.get("keep") is None: opts["keep"] = state.keep except IOError as err: if err.errno != errno.ENOENT: raise cmdutil.wrongtooltocontinue(repo, _("unshelve")) except error.CorruptedState as err: ui.debug(str(err) + "\n") if continuef: msg = _("corrupted shelved state file") hint = _("please run hg unshelve --abort to abort unshelve " "operation") raise error.Abort(msg, hint=hint) elif abortf: msg = _("could not read shelved state file, your working copy " "may be in an unexpected state\nplease update to some " "commit\n") ui.warn(msg) shelvedstate.clear(repo) return if abortf: return unshelveabort(ui, repo, state, opts) elif continuef: return unshelvecontinue(ui, repo, state, opts) elif len(shelved) > 1: raise error.Abort(_("can only unshelve one change at a time")) elif not shelved: shelved = listshelves(repo) if not shelved: raise error.Abort(_("no shelved changes to apply!")) basename = util.split(shelved[0][1])[1] ui.status(_("unshelving change '%s'\n") % basename) else: basename = shelved[0] if not shelvedfile(repo, basename, patchextension).exists(): raise error.Abort(_("shelved change '%s' not found") % basename) lock = tr = None obsshelve = True obsshelvedfile = shelvedfile(repo, basename, "oshelve") if not obsshelvedfile.exists(): # although we can unshelve a obs-based shelve technically, # this particular shelve was created using a traditional way obsshelve = False ui.note( _("falling back to traditional unshelve since " "shelve was traditional")) try: lock = repo.lock() tr = repo.transaction("unshelve", report=lambda x: None) oldtiprev = len(repo) pctx = repo["."] tmpwctx = pctx # The goal is to have a commit structure like so: # ...-> pctx -> tmpwctx -> shelvectx # where tmpwctx is an optional commit with the user's pending changes # and shelvectx is the unshelved changes. Then we merge it all down # to the original pctx. activebookmark = _backupactivebookmark(repo) tmpwctx, addedbefore = _commitworkingcopychanges( ui, repo, opts, tmpwctx) repo, shelvectx = _unshelverestorecommit(ui, repo, basename, obsshelve) _checkunshelveuntrackedproblems(ui, repo, shelvectx) branchtorestore = "" if shelvectx.branch() != shelvectx.p1().branch(): branchtorestore = shelvectx.branch() rebaseconfigoverrides = { ("ui", "forcemerge"): opts.get("tool", ""), ("experimental", "rebaseskipobsolete"): "off", } with ui.configoverride(rebaseconfigoverrides, "unshelve"): shelvectx = _rebaserestoredcommit( ui, repo, opts, tr, oldtiprev, basename, pctx, tmpwctx, shelvectx, branchtorestore, activebookmark, obsshelve, ) mergefiles(ui, repo, pctx, shelvectx) restorebranch(ui, repo, branchtorestore) _forgetunknownfiles(repo, shelvectx, addedbefore) if obsshelve: _obsoleteredundantnodes(repo, tr, pctx, shelvectx, tmpwctx) shelvedstate.clear(repo) _finishunshelve(repo, oldtiprev, tr, activebookmark, obsshelve) unshelvecleanup(ui, repo, basename, opts) finally: if tr: tr.release() lockmod.release(lock)
def rewrite(repo, old, updates, head, newbases, commitopts, mutop=None): """Return (nodeid, created) where nodeid is the identifier of the changeset generated by the rewrite process, and created is True if nodeid was actually created. If created is False, nodeid references a changeset existing before the rewrite call. """ wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction("rewrite") if len(old.parents()) > 1: # XXX remove this unnecessary limitation. raise error.Abort(_("cannot amend merge changesets")) base = old.p1() updatebookmarks = bookmarksupdater(repo, [old.node()] + [u.node() for u in updates]) # commit a new version of the old changeset, including the update # collect all files which might be affected files = set(old.files()) for u in updates: files.update(u.files()) # Recompute copies (avoid recording a -> b -> a) copied = copies.pathcopies(base, head) # prune files which were reverted by the updates def samefile(f): if f in head.manifest(): a = head.filectx(f) if f in base.manifest(): b = base.filectx(f) return a.data() == b.data() and a.flags() == b.flags() else: return False else: return f not in base.manifest() files = [f for f in files if not samefile(f)] # commit version of these files as defined by head headmf = head.manifest() def filectxfn(repo, ctx, path): if path in headmf: fctx = head[path] flags = fctx.flags() mctx = context.memfilectx( repo, ctx, fctx.path(), fctx.data(), islink="l" in flags, isexec="x" in flags, copied=copied.get(path), ) return mctx return None message = cmdutil.logmessage(repo, commitopts) if not message: message = old.description() user = commitopts.get("user") or old.user() # TODO: In case not date is given, we should take the old commit date # if we are working one one changeset or mimic the fold behavior about # date date = commitopts.get("date") or None extra = dict(commitopts.get("extra", old.extra())) extra["branch"] = head.branch() mutation.record(repo, extra, [c.node() for c in updates], mutop) loginfo = { "predecessors": " ".join(c.hex() for c in updates), "mutation": mutop, } new = context.memctx( repo, parents=newbases, text=message, files=files, filectxfn=filectxfn, user=user, date=date, extra=extra, loginfo=loginfo, ) if commitopts.get("edit"): new._text = cmdutil.commitforceeditor(repo, new, []) revcount = len(repo) newid = repo.commitctx(new) new = repo[newid] created = len(repo) != revcount updatebookmarks(newid) tr.close() return newid, created finally: lockmod.release(tr, lock, wlock)
def amend(ui, repo, *pats, **opts): """save pending changes to the current commit Replaces your current commit with a new commit that contains the contents of the original commit, plus any pending changes. By default, all pending changes (in other words, those reported by 'hg status') are committed. To commit only some of your changes, you can: - Specify an exact list of files for which you want changes committed. - Use the -I or -X flags to pattern match file names to exclude or include by using a fileset. See 'hg help filesets' for more information. - Specify the --interactive flag to open a UI that will enable you to select individual insertions or deletions. By default, hg amend reuses your existing commit message and does not prompt you for changes. To change your commit message, you can: - Specify --edit / -e to open your configured editor to update the existing commit message. - Specify --message / -m to replace the entire commit message, including any commit template fields, with a string that you specify. .. note:: Specifying -m overwrites all information in the commit message, including information specified as part of a pre-loaded commit template. For example, any information associating this commit with a code review system will be lost and might result in breakages. When you amend a commit that has descendants, those descendants are rebased on top of the amended version of the commit, unless doing so would result in merge conflicts. If this happens, run 'hg restack' to manually trigger the rebase so that you can go through the merge conflict resolution process. """ # 'rebase' is a tristate option: None=auto, True=force, False=disable rebase = opts.get("rebase") to = opts.get("to") if rebase and _histediting(repo): # if a histedit is in flight, it's dangerous to remove old commits hint = _("during histedit, use amend without --rebase") raise error.Abort("histedit in progress", hint=hint) badflags = [flag for flag in ["rebase", "fixup"] if opts.get(flag, None)] if opts.get("interactive") and badflags: raise error.Abort( _("--interactive and --%s are mutually exclusive") % badflags[0]) fixup = opts.get("fixup") badtoflags = [ "rebase", "fixup", "addremove", "edit", "interactive", "include", "exclude", "message", "logfile", "date", "user", "no-move-detection", "stack", "template", ] if to and any(opts.get(flag, None) for flag in badtoflags): raise error.Abort(_("--to cannot be used with any other options")) if fixup: ui.warn( _("warning: --fixup is deprecated and WILL BE REMOVED. use 'hg restack' instead.\n" )) fixupamend(ui, repo) return if to: amendtocommit(ui, repo, to) return old = repo["."] if old.phase() == phases.public: raise error.Abort(_("cannot amend public changesets")) if len(repo[None].parents()) > 1: raise error.Abort(_("cannot amend while merging")) haschildren = len(old.children()) > 0 opts["message"] = cmdutil.logmessage(repo, opts) # Avoid further processing of any logfile. If such a file existed, its # contents have been copied into opts['message'] by logmessage opts["logfile"] = "" if not opts.get("noeditmessage") and not opts.get("message"): opts["message"] = old.description() commitdate = opts.get("date") if not commitdate: if ui.config("amend", "date") == "implicitupdate": commitdate = "now" else: commitdate = old.date() oldbookmarks = old.bookmarks() tr = None wlock = None lock = None try: wlock = repo.wlock() lock = repo.lock() if opts.get("interactive"): # Strip the interactive flag to avoid infinite recursive loop opts.pop("interactive") cmdutil.dorecord(ui, repo, amend, None, False, cmdutil.recordfilter, *pats, **opts) return else: node = cmdutil.amend(ui, repo, old, {}, pats, opts) if node == old.node(): ui.status(_("nothing changed\n")) return 1 conf = ui.config("amend", "autorestack", RESTACK_DEFAULT) noconflict = None # RESTACK_NO_CONFLICT requires IMM. if conf == RESTACK_NO_CONFLICT and not ui.config( "rebase", "experimental.inmemory", False): conf = RESTACK_DEFAULT # If they explicitly disabled the old behavior, disable the new behavior # too, for now. # internal config: commands.amend.autorebase if ui.configbool("commands", "amend.autorebase") is False: # In the future we'll add a nag message here. conf = RESTACK_NEVER if conf not in RESTACK_VALUES: ui.warn( _('invalid amend.autorestack config of "%s"; falling back to %s\n' ) % (conf, RESTACK_DEFAULT)) conf = RESTACK_DEFAULT if haschildren and rebase is None and not _histediting(repo): if conf == RESTACK_ALWAYS: rebase = True elif conf == RESTACK_NO_CONFLICT: if repo[None].dirty(): # For now, only restack if the WC is clean (t31742174). ui.status( _("not restacking because working copy is dirty\n")) rebase = False else: # internal config: amend.autorestackmsg msg = ui.config( "amend", "autorestackmsg", _("restacking children automatically (unless they conflict)" ), ) if msg: ui.status("%s\n" % msg) rebase = True noconflict = True elif conf == RESTACK_ONLY_TRIVIAL: newcommit = repo[node] # If the rebase did not change the manifest and the # working copy is clean, force the children to be # restacked. rebase = (old.manifestnode() == newcommit.manifestnode() and not repo[None].dirty()) if rebase: hintutil.trigger("amend-autorebase") else: rebase = False if haschildren and not rebase and not _histediting(repo): hintutil.trigger("amend-restack", old.node()) changes = [] # move old bookmarks to new node for bm in oldbookmarks: changes.append((bm, node)) tr = repo.transaction("fixupamend") repo._bookmarks.applychanges(repo, tr, changes) tr.close() if rebase and haschildren: noconflictmsg = _( "restacking would create conflicts (%s in %s), so you must run it manually\n(run `hg restack` manually to restack this commit's children)" ) revs = [ c.hex() for c in repo.set("(%n::)-%n", old.node(), old.node()) ] with ui.configoverride({ ("rebase", "noconflictmsg"): noconflictmsg }): # Note: this has effects on linearizing (old:: - old). That can # fail. If that fails, it might make sense to try a plain # rebase -s (old:: - old) -d new. restack.restack(ui, repo, rev=revs, noconflict=noconflict) showtemplate(ui, repo, repo[node], **opts) finally: lockmod.release(wlock, lock, tr)