def hide(ui, repo, *revs, **opts): """hide commits and their descendants Mark the specified commits as hidden. Hidden commits are not included in the output of most Mercurial commands, including :hg:`log` and :hg:`smartlog.` Any descendants of the specified commits will also be hidden. Hidden commits are not deleted. They will remain in the repo indefinitely and are still accessible by their hashes. However, :hg:`hide` will delete any bookmarks pointing to hidden commits. Use the :hg:`unhide` command to make hidden commits visible again. See :hg:`help unhide` for more information. To view hidden commits, run :hg:`journal`. When you hide the current commit, the most recent visible ancestor is checked out. To hide obsolete stacks (stacks that have a newer version), run :hg:`hide --cleanup`. This command is equivalent to: :hg:`hide 'obsolete() - ancestors(draft() & not obsolete())'` --cleanup skips obsolete commits with non-obsolete descendants. """ if opts.get("cleanup") and len(opts.get("rev") + list(revs)) != 0: raise error.Abort(_("--rev and --cleanup are incompatible")) elif opts.get("cleanup"): # hides all the draft, obsolete commits that # don't have non-obsolete descendants revs = ["obsolete() - (draft() & ::(draft() & not obsolete()))"] else: revs = list(revs) + opts.pop("rev", []) with repo.wlock(), repo.lock(), repo.transaction("hide") as tr: revs = repo.revs("(%ld)::", scmutil.revrange(repo, revs)) bookmarks = set(opts.get("bookmark", ())) if bookmarks: revs += bookmarksmod.reachablerevs(repo, bookmarks) if not revs: # No revs are reachable exclusively from these bookmarks, just # delete the bookmarks. if not ui.quiet: for bookmark in sorted(bookmarks): ui.status( _("removing bookmark '%s' (was at: %s)\n") % (bookmark, short(repo._bookmarks[bookmark])) ) bookmarksmod.delete(repo, tr, bookmarks) ui.status( _n( "%i bookmark removed\n", "%i bookmarks removed\n", len(bookmarks), ) % len(bookmarks) ) return 0 if not revs: raise error.Abort(_("nothing to hide")) hidectxs = [repo[r] for r in revs] # revs to be hidden for ctx in hidectxs: if not ctx.mutable(): raise error.Abort( _("cannot hide immutable changeset: %s") % ctx, hint="see 'hg help phases' for details", ) if not ui.quiet: ui.status( _('hiding commit %s "%s"\n') % (ctx, ctx.description().split("\n")[0][:50]) ) wdp = repo["."] newnode = wdp while newnode in hidectxs: newnode = newnode.parents()[0] if newnode.node() != wdp.node(): cmdutil.bailifchanged(repo, merge=False) hg.update(repo, newnode, False) ui.status( _("working directory now at %s\n") % ui.label(str(newnode), "node") ) # create markers if obsolete.isenabled(repo, obsolete.createmarkersopt): obsolete.createmarkers(repo, [(r, []) for r in hidectxs], operation="hide") visibility.remove(repo, [c.node() for c in hidectxs]) ui.status( _n("%i changeset hidden\n", "%i changesets hidden\n", len(hidectxs)) % len(hidectxs) ) # remove bookmarks pointing to hidden changesets hnodes = [r.node() for r in hidectxs] deletebookmarks = set(bookmarks) for bookmark, node in sorted(bookmarksmod.listbinbookmarks(repo)): if node in hnodes: deletebookmarks.add(bookmark) if deletebookmarks: for bookmark in sorted(deletebookmarks): if not ui.quiet: ui.status( _('removing bookmark "%s (was at: %s)"\n') % (bookmark, short(repo._bookmarks[bookmark])) ) bookmarksmod.delete(repo, tr, deletebookmarks) ui.status( _n( "%i bookmark removed\n", "%i bookmarks removed\n", len(deletebookmarks), ) % len(deletebookmarks) ) hintutil.trigger("undo")
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)