def drop(ui, repo, *revs, **opts): """drop changeset from stack """ if not rebasemod: raise error.Abort(_("required extensions not detected")) cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) revs = scmutil.revrange(repo, list(revs) + opts.get("rev")) if not revs: raise error.Abort(_("no revision to drop was provided")) # currently drop supports dropping only one changeset at a time if len(revs) > 1: raise error.Abort(_("only one revision can be dropped at a time")) revid = revs.first() changectx = repo[revid] if changectx.phase() == phases.public: raise error.Abort(_("public changeset which landed cannot be dropped")) parents = repo.revs("parents(%s)", revid) if len(parents) > 1: raise error.Abort(_("merge changeset cannot be dropped")) elif len(parents) == 0: raise error.Abort(_("root changeset cannot be dropped")) _showrev(ui, repo, revid) descendants = repo.revs("(%d::) - %d", revid, revid) parent = parents.first() with repo.wlock(): with repo.lock(): with repo.transaction("drop"): if len(descendants) > 0: try: rebasemod.rebase(ui, repo, dest=str(parent), rev=descendants) except error.InterventionRequired: ui.warn( _("conflict occurred during drop: " + "please fix it by running " + "'hg rebase --continue', " + "and then re-run 'hg drop'\n")) raise scmutil.cleanupnodes(repo, [changectx.node()], "drop")
def split(ui, repo, *revs, **opts): """split a changeset into smaller changesets Prompt for hunks to be selected until exhausted. Each selection of hunks will form a separate changeset, in order from parent to child: the first selection will form the first changeset, the second selection will form the second changeset, and so on. Operates on the current revision by default. Use --rev to split a given changeset instead. """ newcommits = [] revarg = (list(revs) + opts.get("rev")) or ["."] if len(revarg) != 1: msg = _("more than one revset is given") hnt = _( "use either `hg split <rs>` or `hg split --rev <rs>`, not both") raise error.Abort(msg, hint=hnt) rev = scmutil.revsingle(repo, revarg[0]) if opts.get("no_rebase"): torebase = () else: torebase = list( map(hex, repo.nodes("descendants(%d) - (%d)", rev, rev))) with repo.wlock(), repo.lock(): cmdutil.bailifchanged(repo) if torebase: cmdutil.checkunfinished(repo) ctx = repo[rev] r = ctx.hex() allowunstable = visibility.tracking(repo) or obsolete.isenabled( repo, obsolete.allowunstableopt) if not allowunstable: # XXX We should check head revs if repo.revs("(%d::) - %d", rev, rev): raise error.Abort( _("cannot split commit: %s not a head") % ctx) if len(ctx.parents()) > 1: raise error.Abort(_("cannot split merge commits")) prev = ctx.p1() bmupdate = common.bookmarksupdater(repo, ctx.node()) bookactive = repo._activebookmark if bookactive is not None: repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark) bookmarks.deactivate(repo) hg.update(repo, prev) commands.revert(ui, repo, rev=r, all=True) def haschanges(): modified, added, removed, deleted = repo.status()[:4] return modified or added or removed or deleted # We need to detect the case where the user selects all remaining # changes, as that will end the split. That's the commit we want to # mark as the result of the split. To do this, wrap the recordfilter # function and compare the output to see if it contains all the # originalchunks. shouldrecordmutation = [False] def mutinfo(extra): if shouldrecordmutation[0]: return mutation.record( repo, extra, [ctx.node()], "split", splitting=[c.node() for c in newcommits], ) def recordfilter(ui, originalchunks, operation=None): chunks, newopts = cmdutil.recordfilter(ui, originalchunks, operation) if cmdutil.comparechunks(chunks, originalchunks): shouldrecordmutation[0] = True return chunks, newopts msg = ("HG: This is the original pre-split commit message. " "Edit it as appropriate.\n\n") msg += ctx.description() opts["message"] = msg opts["edit"] = True opts["_commitmutinfofunc"] = mutinfo try: while haschanges(): pats = () with repo.transaction("split"): cmdutil.dorecord(ui, repo, commands.commit, "commit", False, recordfilter, *pats, **opts) # TODO: Does no seem like the best way to do this # We should make dorecord return the newly created commit newcommits.append(repo["."]) if haschanges(): if ui.prompt("Done splitting? [yN]", default="n") == "y": shouldrecordmutation[0] = True with repo.transaction("split"): commands.commit(ui, repo, **opts) newcommits.append(repo["."]) break else: ui.status(_("no more change to split\n")) except Exception: # Rollback everything hg.updaterepo(repo, r, True) # overwrite=True if newcommits: visibility.remove(repo, [c.node() for c in newcommits]) if bookactive is not None: bookmarks.activate(repo, bookactive) raise if newcommits: phabdiffs = {} for c in newcommits: phabdiff = diffprops.parserevfromcommitmsg( repo[c].description()) if phabdiff: phabdiffs.setdefault(phabdiff, []).append(c) if any(len(commits) > 1 for commits in phabdiffs.values()): hintutil.trigger("split-phabricator", ui.config("split", "phabricatoradvice")) tip = repo[newcommits[-1]] with repo.transaction("post-split"): bmupdate(tip.node()) if bookactive is not None: bookmarks.activate(repo, bookactive) if obsolete.isenabled(repo, obsolete.createmarkersopt): obsolete.createmarkers(repo, [(repo[r], newcommits)], operation="split") if torebase: rebaseopts = {"dest": "_destrestack(SRC)", "rev": torebase} rebase.rebase(ui, repo, **rebaseopts) unfi = repo with repo.transaction("post-split-hide"): visibility.remove(repo, [unfi[r].node()])
def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" with repo.wlock(): cmdutil.checkunfinished(repo) return _docreatecmd(ui, repo, pats, opts)
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 redo(ui, repo, *args, **opts): """undo the last undo Reverse the effects of an :hg:`undo` operation. You can run :hg:`redo` multiple times to undo a series of :hg:`undo` commands. Alternatively, you can explicitly specify the number of :hg:`undo` commands to undo by providing a number as a positional argument. Specify --preview to see a graphical display that shows what your smartlog will look like after you run the command. For an interactive interface, run :hg:`undo --interactive`. This command enables you to visually step backwards and forwards in the undo history. Run :hg:`help undo` for more information. """ shiftedindex = _computerelative(repo, 0) preview = opts.get("preview") branch = "" reverseindex = 0 redocount = 0 done = False while not done: # we step back the linear undo log # redoes cancel out undoes, if we have one more undo, we should undo # there, otherwise we continue looking # we are careful to not redo past absolute undoes (bc we loose undoredo # log info) # if we run into something that isn't undo or redo, we Abort (including # gaps in the log) # we extract the --index arguments out of undoes to make sure we update # the undoredo index correctly nodedict = _readindex(repo, reverseindex) commandstr = _readnode(repo, "command.i", nodedict["command"]) commandlist = commandstr.split("\0") if "True" == nodedict["unfinished"]: # don't want to redo to an interupted state reverseindex += 1 elif commandlist[0] == "undo": undoopts = {} fancyopts.fancyopts( commandlist, cmdtable["undo"][1] + commands.globalopts, undoopts, gnu=True, ) if redocount == 0: # want to go to state before the undo (not after) toshift = undoopts["step"] shiftedindex -= toshift reverseindex += 1 branch = undoopts.get("branch") done = True else: if undoopts["absolute"]: raise error.Abort(_("can't redo past absolute undo")) reverseindex += 1 redocount -= 1 elif commandlist[0] == "redo": redocount += 1 reverseindex += 1 else: raise error.Abort(_("nothing to redo")) if preview: _preview(ui, repo, reverseindex) return with repo.wlock(), repo.lock(), repo.transaction("redo"): cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) repo = repo.unfiltered() _undoto(ui, repo, reverseindex) # update undredo by removing what the given undo added _logundoredoindex(repo, shiftedindex, branch)
def undo(ui, repo, *args, **opts): """undo the last local command Reverse the effects of the last local command. A local command is one that changed the currently checked out commit, that modified the contents of local commits, or that changed local bookmarks. Examples of local commands include :hg:`checkout`, :hg:`commit`, :hg:`amend`, and :hg:`rebase`. You cannot use :hg:`undo` to undo uncommited changes in the working copy, or changes to remote bookmarks. You can run :hg:`undo` multiple times to undo a series of local commands. Alternatively, you can explicitly specify the number of local commands to undo using --step. This number can also be specified as a positional argument. To undo the effects of :hg:`undo`, run :hg:`redo`. Run :hg:`help redo` for more information. Include --keep to preserve the state of the working copy. For example, specify --keep when running :hg:`undo` to reverse the effects of an :hg:`commit` or :hg:`amend` operation while still preserving changes in the working copy. These changes will appear as pending changes. Specify --preview to see a graphical display that shows what your smartlog will look like after you run the command. Specify --interactive for an interactive version of this preview in which you can step backwards and forwards in the undo history. .. note:: :hg:`undo` cannot be used with non-local commands, or with commands that are read-only. :hg:`undo` will skip over these commands in the undo history. For hybrid commands that result in both local and remote changes, :hg:`undo` will undo the local changes, but not the remote changes. For example, `hg pull --rebase` might move remote/master and also rebase local commits. In this situation, :hg:`undo` will revert the rebase, but not the change to remote/master. .. container:: verbose Branch limits the scope of an undo to a group of local (draft) changectxs, identified by any one member of this group. """ reverseindex = opts.get("step") relativeundo = not opts.get("absolute") keep = opts.get("keep") branch = opts.get("branch") preview = opts.get("preview") interactive = opts.get("interactive") if interactive and interactiveui is None: raise error.Abort(_("interactive ui is not supported on Windows")) if interactive: preview = True repo = repo.unfiltered() if branch and reverseindex != 1 and reverseindex != -1: raise error.Abort(_("--branch with --index not supported")) if relativeundo: try: reverseindex = _computerelative(repo, reverseindex, absolute=not relativeundo, branch=branch) except IndexError: raise error.Abort( _("cannot undo this far - undo extension was not" " enabled")) if branch and preview: raise error.Abort(_("--branch with --preview not supported")) if interactive: cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) class undopreview(interactiveui.viewframe): def render(self): ui = self.ui ui.pushbuffer() return_code = _preview(ui, self.repo, self.index) if return_code == 1: if self.index < 0: self.index += 1 repo.ui.status(_("Already at newest repo state\a\n")) elif self.index > 0: self.index -= 1 repo.ui.status(_("Already at oldest repo state\a\n")) _preview(ui, self.repo, self.index) text = ui.config( "undo", "interactivehelptext", "legend: red - to hide; green - to revive\n", ) repo.ui.status(text) repo.ui.status( _("<-: newer " "->: older " "q: abort " "enter: confirm\n")) return ui.popbuffer() def rightarrow(self): self.index += 1 def leftarrow(self): self.index -= 1 def enter(self): del opts["preview"] del opts["interactive"] opts["absolute"] = "absolute" opts["step"] = self.index undo(ui, repo, *args, **opts) return viewobj = undopreview(ui, repo, reverseindex) interactiveui.view(viewobj) return elif preview: _preview(ui, repo, reverseindex) return with repo.wlock(), repo.lock(), repo.transaction("undo"): cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) if not (opts.get("force") or _gapcheck(ui, repo, reverseindex)): raise error.Abort( _("attempted risky undo across" " missing history")) _undoto(ui, repo, reverseindex, keep=keep, branch=branch) # store undo data # for absolute undos, think of this as a reset # for relative undos, think of this as an update _logundoredoindex(repo, reverseindex, branch)
def _moverelative(ui, repo, args, opts, reverse=False): """Update to a changeset relative to the current changeset. Implements both `hg previous` and `hg next`. Takes in a list of positional arguments and a dict of command line options. (See help for `hg previous` and `hg next` to see which arguments and flags are supported.) Moves forward through history by default -- the behavior of `hg next`. Setting reverse=True will change the behavior to that of `hg previous`. """ # Parse positional argument. try: n = int(args[0]) if args else 1 except ValueError: raise error.Abort(_("argument must be an integer")) if n <= 0: return if ui.configbool("amend", "alwaysnewest"): opts["newest"] = True # Check that the given combination of arguments is valid. if args: if opts.get("bookmark", False): raise error.Abort(_("cannot use both number and --bookmark")) if opts.get("top", False): raise error.Abort(_("cannot use both number and --top")) if opts.get("bottom", False): raise error.Abort(_("cannot use both number and --bottom")) if opts.get("bookmark", False): if opts.get("top", False): raise error.Abort(_("cannot use both --top and --bookmark")) if opts.get("bottom", False): raise error.Abort(_("cannot use both --bottom and --bookmark")) if opts.get("towards", False) and opts.get("top", False): raise error.Abort(_("cannot use both --top and --towards")) if opts.get("merge", False) and opts.get("rebase", False): raise error.Abort(_("cannot use both --merge and --rebase")) # Check if there is an outstanding operation or uncommited changes. cmdutil.checkunfinished(repo) if not opts.get("clean", False) and not opts.get("merge", False): try: cmdutil.bailifchanged(repo) except error.Abort as e: e.hint = _("use --clean to discard uncommitted changes " "or --merge to bring them along") raise # If we have both --clean and --rebase, we need to discard any outstanding # changes now before we attempt to perform any rebases. if opts.get("clean") and opts.get("rebase"): commands.update(ui, repo, rev=repo["."].hex(), clean=True) with repo.wlock(), repo.lock(): # Record the active bookmark, if any. bookmark = repo._activebookmark noactivate = opts.get("no_activate_bookmark", False) movebookmark = opts.get("move_bookmark", False) with repo.transaction("moverelative") as tr: # Find the desired changeset. May potentially perform rebase. try: target = _findtarget(ui, repo, n, opts, reverse) except error.InterventionRequired: # Rebase failed. Need to manually close transaction to allow # `hg rebase --continue` to work correctly. tr.close() raise # Move the active bookmark if necessary. Needs to happen before # we update to avoid getting a 'leaving bookmark X' message. if movebookmark and bookmark is not None: _setbookmark(repo, tr, bookmark, target) # Update to the target changeset. commands.update( ui, repo, rev=hex(target), clean=opts.get("clean", False), merge=opts.get("merge", False), ) # Print out the changeset we landed on. _showchangesets(ui, repo, nodes=[target]) # Activate the bookmark on the new changeset. if not noactivate and not movebookmark: _activate(ui, repo, target)
def chistedit(ui, repo, *freeargs, **opts): """Provides a ncurses interface to histedit. Press ? in chistedit mode to see an extensive help. Requires python-curses to be installed.""" if curses is None: raise error.Abort(_("Python curses library required")) # disable color ui._colormode = None try: keep = opts.get("keep") revs = opts.get("rev", [])[:] cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) if os.path.exists(os.path.join(repo.path, "histedit-state")): raise error.Abort( _("history edit already in progress, try " "--continue or --abort")) revs.extend(freeargs) if not revs: defaultrev = destutil.desthistedit(ui, repo) if defaultrev is not None: revs.append(defaultrev) if len(revs) != 1: raise error.Abort( _("histedit requires exactly one ancestor revision")) rr = list(repo.set("roots(%ld)", scmutil.revrange(repo, revs))) if len(rr) != 1: raise error.Abort( _("The specified revisions must have " "exactly one common root")) root = rr[0].node() topmost, empty = repo.dirstate.parents() revs = histedit.between(repo, root, topmost, keep) if not revs: raise error.Abort( _("%s is not an ancestor of working directory") % node.short(root)) ctxs = [] for i, r in enumerate(revs): ctxs.append(histeditrule(repo[r], i)) rc = curses.wrapper(functools.partial(main, repo, ctxs)) curses.echo() curses.endwin() if rc is False: ui.write(_("chistedit aborted\n")) return 0 if type(rc) is list: ui.status(_("running histedit\n")) rules = makecommands(rc) filename = repo.localvfs.join("chistedit") with open(filename, "w+") as fp: for r in rules: fp.write(r) opts["commands"] = filename return histedit.histedit(ui, repo, *freeargs, **opts) except KeyboardInterrupt: pass return -1