def snapshotcheckout(ui, repo, *args, **opts): """checks out the working copy to the snapshot state, given its revision id """ cctx = getsnapshotctx(ui, repo, args) clean = opts.get("clean") # This is a temporary safety check that WC is clean. if sum(map(len, repo.status(unknown=True))) != 0 and not clean: raise error.Abort( _("You must have a clean working copy to checkout on a snapshot. " "Use --clean to bypass that.\n")) ui.status(_("will checkout on %s\n") % cctx.hex()) with repo.wlock(): parents = [p.node() for p in cctx.parents()] # First we check out on the 1st parent of the snapshot state hg.update(repo.unfiltered(), parents[0], quietempty=True) # Then we update snapshot files in the working copy # Here the dirstate is not updated because of the matcher matcher = scmutil.matchfiles(repo, cctx.files(), opts) mergemod.update(repo.unfiltered(), cctx.hex(), False, False, matcher=matcher) # Finally, we mark the modified files in the dirstate scmutil.addremove(repo, matcher, "", opts) # Tie the state to the 2nd parent if needed if len(parents) == 2: with repo.dirstate.parentchange(): repo.setparents(*parents) snapshotmetadataid = cctx.extra().get("snapshotmetadataid") if snapshotmetadataid: snapmetadata = snapshotmetadata.getfromlocalstorage( repo, snapshotmetadataid) checkouttosnapshotmetadata(ui, repo, snapmetadata, clean) ui.status(_("checkout complete\n"))
def mergefiles(ui, repo, wctx, shelvectx): """updates to wctx and merges the changes from shelvectx into the dirstate.""" with ui.configoverride({("ui", "quiet"): True}): hg.update(repo, wctx.node()) files = [] files.extend(shelvectx.files()) files.extend(shelvectx.p1().files()) # revert will overwrite unknown files, so move them out of the way for file in repo.status(unknown=True).unknown: if file in files: util.rename(file, scmutil.origpath(ui, repo, file)) ui.pushbuffer(True) cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), *pathtofiles(repo, files), **{"no_backup": True}) ui.popbuffer()
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 rebaseorfastforward(orig, ui, repo, dest, **args): """Wrapper for rebasemodule.rebase that fast-forwards the working directory and any active bookmark to the rebase destination if there is actually nothing to rebase. """ prev = repo["."] destrev = scmutil.revsingle(repo, dest) common = destrev.ancestor(prev) if prev == common and destrev != prev: result = hg.update(repo, destrev.node()) if repo._activebookmark: with repo.wlock(): bookmarks.update(repo, [prev.node()], destrev.node()) ui.status(_("nothing to rebase - fast-forwarded to %s\n") % dest) return result return orig(ui, repo, dest=dest, **args)
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 fold(ui, repo, *revs, **opts): """combine multiple commits into a single commit With --from, folds all the revisions linearly between the current revision and the specified revision. With --exact, folds only the specified revisions while ignoring the revision currently checked out. The given revisions must form a linear unbroken chain. .. container:: verbose Some examples: - Fold from the current revision to its parent:: hg fold --from .^ - Fold all draft revisions into the current revision:: hg fold --from 'draft()' See :hg:`help phases` for more about draft revisions and :hg:`help revsets` for more about the `draft()` keyword - Fold revisions between 3 and 6 into the current revision:: hg fold --from 3::6 - Fold revisions 3 and 4: hg fold "3 + 4" --exact - Only fold revisions linearly between foo and @:: hg fold foo::@ --exact """ revs = list(revs) revs.extend(opts["rev"]) if not revs: raise error.Abort(_("no revisions specified")) revs = scmutil.revrange(repo, revs) if opts.get("no_rebase"): torebase = () else: torebase = repo.revs("descendants(%ld) - (%ld)", revs, revs) if opts["from"] and opts["exact"]: raise error.Abort(_("cannot use both --from and --exact")) elif opts["from"]: # Try to extend given revision starting from the working directory extrevs = repo.revs("(%ld::.) or (.::%ld)", revs, revs) discardedrevs = [r for r in revs if r not in extrevs] if discardedrevs: msg = _("cannot fold non-linear revisions") hint = _("given revisions are unrelated to parent of working" " directory") raise error.Abort(msg, hint=hint) revs = extrevs elif opts["exact"]: # Nothing to do; "revs" is already set correctly pass else: raise error.Abort(_("must specify either --from or --exact")) if not revs: raise error.Abort( _("specified revisions evaluate to an empty set"), hint=_("use different revision arguments"), ) elif len(revs) == 1: ui.write_err(_("single revision specified, nothing to fold\n")) return 1 with repo.wlock(), repo.lock(), ui.formatter("fold", opts) as fm: fm.startitem() root, head = _foldcheck(repo, revs) with repo.transaction("fold") as tr: commitopts = opts.copy() allctx = [repo[r] for r in revs] targetphase = max(c.phase() for c in allctx) if (commitopts.get("message") or commitopts.get("logfile") or commitopts.get("reuse_message")): commitopts["edit"] = False else: msgs = ["HG: This is a fold of %d changesets." % len(allctx)] msgs += [ "HG: Commit message of %s.\n\n%s\n" % (node.short(c.node()), c.description()) for c in allctx ] commitopts["message"] = "\n".join(msgs) commitopts["edit"] = True newid, unusedvariable = common.rewrite( repo, root, allctx, head, [root.p1().node(), root.p2().node()], commitopts=commitopts, mutop="fold", ) phases.retractboundary(repo, tr, targetphase, [newid]) replacements = {ctx.node(): (newid, ) for ctx in allctx} nodechanges = { fm.hexfunc(ctx.node()): [fm.hexfunc(newid)] for ctx in allctx } fm.data(nodechanges=fm.formatdict(nodechanges)) scmutil.cleanupnodes(repo, replacements, "fold") fm.condwrite(not ui.quiet, "count", "%i changesets folded\n", len(revs)) if repo["."].rev() in revs: hg.update(repo, newid) if torebase: common.restackonce(ui, repo, repo[newid].rev())
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 _docreatecmd(ui, repo, pats, opts): wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise error.Abort(_("cannot shelve while merging")) parent = parents[0] origbranch = wctx.branch() if parent.node() != nodemod.nullid: desc = "shelve changes to: %s" % parent.description().split("\n", 1)[0] else: desc = "(changes in empty repository)" if not opts.get("message"): opts["message"] = desc activebookmark = None try: with repo.lock(), repo.transaction("commit", report=None): interactive = opts.get("interactive", False) includeunknown = opts.get( "unknown", False) and not opts.get("addremove", False) name = getshelvename(repo, parent, opts) activebookmark = _backupactivebookmark(repo) extra = {} if includeunknown: _includeunknownfiles(repo, pats, opts, extra) if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): # In non-bare shelve we don't store newly created branch # at bundled commit repo.dirstate.setbranch(repo["."].branch()) commitfunc = getcommitfunc(extra, interactive, editor=True) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, commitfunc, None, False, cmdutil.recordfilter, *pats, **opts) if not node: _nothingtoshelvemessaging(ui, repo, pats, opts) return 1 _hidenodes(repo, [node]) except (KeyboardInterrupt, Exception): if activebookmark: bookmarks.activate(repo, activebookmark) raise _shelvecreatedcommit(ui, repo, node, name) if ui.formatted: desc = util.ellipsis(desc, ui.termwidth()) ui.status(_("shelved as %s\n") % name) # current wc parent may be already obsolete because # it might have been created previously and shelve just # reuses it try: hg.update(repo, parent.node()) except (KeyboardInterrupt, Exception): # failed to update to the original revision, which has left us on the # (hidden) shelve commit. Move directly to the original commit by # updating the dirstate parents. repo.setparents(parent.node()) raise finally: if origbranch != repo["."].branch() and not _isbareshelve(pats, opts): repo.dirstate.setbranch(origbranch) if activebookmark: bookmarks.activate(repo, activebookmark)
def metaedit(ui, repo, templ, *revs, **opts): """edit commit message and other metadata Edit commit message for the current commit. By default, opens your default editor so that you can edit the commit message interactively. Specify -m to specify the commit message on the command line. To edit the message for a different commit, specify -r. To edit the messages of multiple commits, specify --batch. You can edit other pieces of commit metadata, namely the user or date, by specifying -u or -d, respectively. The expected format for user is 'Full Name <*****@*****.**>'. There is also automation-friendly JSON input mode which allows the caller to provide the mapping between commit and new message and username in the following format: { "<commit_hash>": { "message": "<message>", "user": "******" // optional } } .. note:: You can specify --fold to fold multiple revisions into one when the given revisions form a linear unbroken chain. However, :hg:`fold` is the preferred command for this purpose. See :hg:`help fold` for more information. .. container:: verbose Some examples: - Edit the commit message for the current commit:: hg metaedit - Change the username for the current commit:: hg metaedit --user 'New User <*****@*****.**>' """ revs = list(revs) revs.extend(opts["rev"]) if not revs: if opts["fold"]: raise error.Abort(_("revisions must be specified with --fold")) revs = ["."] with repo.wlock(), repo.lock(): revs = scmutil.revrange(repo, revs) msgmap = { } # {node: message}, predefined messages, currently used by --batch usermap = { } # {node: author}, predefined authors, used by --jsoninputfile if opts["fold"]: root, head = fold._foldcheck(repo, revs) else: if repo.revs("%ld and public()", revs): raise error.Abort( _("cannot edit commit information for public " "revisions")) root = head = repo[revs.first()] wctx = repo[None] p1 = wctx.p1() tr = repo.transaction("metaedit") newp1 = None try: commitopts = opts.copy() allctx = [repo[r] for r in revs] jsoninputfile = None if any( commitopts.get(name) for name in ["message", "logfile", "reuse_message"]): commitopts["edit"] = False else: if opts["fold"]: msgs = [ _("HG: This is a fold of %d changesets.") % len(allctx) ] msgs += [ _("HG: Commit message of %s.\n\n%s\n") % (nodemod.short(c.node()), c.description()) for c in allctx ] else: if opts["batch"] and len(revs) > 1: msgmap = editmessages(repo, revs) msgs = [head.description()] jsoninputfile = opts.get("json_input_file") if jsoninputfile: try: if cmdutil.isstdiofilename(jsoninputfile): inputjson = pycompat.decodeutf8(ui.fin.read()) else: inputjson = pycompat.decodeutf8( util.readfile(jsoninputfile)) msgusermap = json.loads(inputjson) except IOError as inst: raise error.Abort( _("can't read JSON input file '%s': %s") % (jsoninputfile, encoding.strtolocal(inst.strerror))) except ValueError as inst: raise error.Abort( _("can't decode JSON input file '%s': %s") % (jsoninputfile, str(inst))) if not isinstance(msgusermap, dict): raise error.Abort( _("JSON input is not a dictionary (see --help for input format)" )) try: msgmap = { bin(node): msguser.get("message") for (node, msguser) in msgusermap.items() if "message" in msguser } usermap = { bin(node): msguser.get("user") for (node, msguser) in msgusermap.items() if "user" in msguser } except TypeError: raise error.Abort(_("invalid JSON input")) commitopts["message"] = "\n".join(msgs) commitopts["edit"] = True if root == head: # fast path: use metarewrite replacemap = {} # adding commitopts to the revisions to metaedit allctxopt = [{ "ctx": ctx, "commitopts": commitopts } for ctx in allctx] # all descendats that can be safely rewritten newunstable = common.newunstable(repo, revs) newunstableopt = [{ "ctx": ctx } for ctx in [repo[r] for r in newunstable]] # we need to edit descendants with the given revisions to not to # corrupt the stacks if _histediting(repo): ui.note( _("during histedit, the descendants of " "the edited commit weren't auto-rebased\n")) else: allctxopt += newunstableopt # we need topological order for all if mutation.enabled(repo): allctxopt = mutation.toposort( repo, allctxopt, nodefn=lambda copt: copt["ctx"].node()) else: allctxopt = sorted(allctxopt, key=lambda copt: copt["ctx"].rev()) def _rewritesingle(c, _commitopts): # Predefined message overrides other message editing choices. msg = msgmap.get(c.node()) if jsoninputfile: _commitopts["edit"] = False if msg is not None: _commitopts["message"] = msg _commitopts["edit"] = False user = usermap.get(c.node()) if user is not None: _commitopts["user"] = user if _commitopts.get("edit", False): msg = "HG: Commit message of changeset %s\n%s" % ( str(c), c.description(), ) _commitopts["message"] = msg bases = [ replacemap.get(c.p1().node(), c.p1().node()), replacemap.get(c.p2().node(), c.p2().node()), ] newid, created = common.metarewrite(repo, c, bases, commitopts=_commitopts) if created: replacemap[c.node()] = newid for copt in allctxopt: _rewritesingle( copt["ctx"], copt.get("commitopts", {"date": commitopts.get("date") or None}), ) if p1.node() in replacemap: repo.setparents(replacemap[p1.node()]) if len(replacemap) > 0: mapping = dict( map( lambda oldnew: (oldnew[0], [oldnew[1]]), pycompat.iteritems(replacemap), )) templ.setprop("nodereplacements", mapping) scmutil.cleanupnodes(repo, mapping, "metaedit") # TODO: set poroper phase boundaries (affects secret # phase only) else: ui.status(_("nothing changed\n")) return 1 else: # slow path: create a new commit targetphase = max(c.phase() for c in allctx) # TODO: if the author and message are the same, don't create a # new hash. Right now we create a new hash because the date can # be different. newid, created = common.rewrite( repo, root, allctx, head, [root.p1().node(), root.p2().node()], commitopts=commitopts, mutop="metaedit", ) if created: if p1.rev() in revs: newp1 = newid phases.retractboundary(repo, tr, targetphase, [newid]) mapping = dict([(repo[rev].node(), [newid]) for rev in revs]) templ.setprop("nodereplacements", mapping) scmutil.cleanupnodes(repo, mapping, "metaedit") else: ui.status(_("nothing changed\n")) return 1 tr.close() finally: tr.release() if opts["fold"]: ui.status(_("%i changesets folded\n") % len(revs)) if newp1 is not None: hg.update(repo, newp1)