def restackonce( ui, repo, rev, rebaseopts=None, childrenonly=False, noconflict=None, noconflictmsg=None, ): """Rebase all descendants of precursors of rev onto rev, thereby stabilzing any non-obsolete descendants of those precursors. Takes in an optional dict of options for the rebase command. If childrenonly is True, only rebases direct children of precursors of rev rather than all descendants of those precursors. NOTE(phillco): This function shouldn't be used; prefer restack.restack or a custom rebase using `-d _destrestack(SRC)`. """ # Get visible, non-obsolete descendants of precusors of rev. allpredecessors = repo.revs("predecessors(%d) - (%d)", rev, rev) fmt = "%s(%%ld) - %%ld - obsolete()" % ("children" if childrenonly else "descendants") descendants = repo.revs(fmt, allpredecessors, allpredecessors) # Nothing to do if there are no descendants. if not descendants: return # Overwrite source and destination, leave all other options. if rebaseopts is None: rebaseopts = {} rebaseopts["rev"] = descendants rebaseopts["dest"] = rev rebaseopts["noconflict"] = noconflict overrides = { # Explicitly disable revnum deprecation warnings. This is an internal # use of "rebase" that does not contain user-provided revsets. ("devel", "legacy.revnum"): "" } try: tweakdefaults = extensions.find("tweakdefaults") except KeyError: # No tweakdefaults extension -- skip this since there is no wrapper # to set the metadata. pass else: # We need to ensure that the 'operation' field in the obsmarker metadata # is always set to 'rebase', regardless of the current command so that # the restacked commits will appear as 'rebased' in smartlog. overrides[(tweakdefaults.globaldata, tweakdefaults.createmarkersoperation)] = "rebase" if noconflictmsg: overrides[("rebase", "noconflictmsg")] = noconflictmsg # Perform rebase. with repo.ui.configoverride(overrides, "restack"): rebase.rebase(ui, repo, **rebaseopts)
def debugbruterebase(ui, repo, source, dest): """for every non-empty subset of source, run rebase -r subset -d dest Print one line summary for each subset. Assume obsstore is enabled. """ srevs = list(repo.revs(source)) with repo.wlock(), repo.lock(): repolen = len(repo) cl = repo.changelog newrevs = [] def getdesc(rev): result = cl.changelogrevision(rev).description if rev in newrevs: result += "'" return result for i in xrange(1, 2**len(srevs)): subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0] spec = revsetlang.formatspec("%ld", subset) tr = repo.transaction("rebase") tr.report = lambda x: 0 # hide "transaction abort" oldnodes = set(repo.nodes("all()")) ui.pushbuffer() try: rebase.rebase(ui, repo, dest=dest, rev=[spec]) except error.Abort as ex: summary = "ABORT: %s" % ex except Exception as ex: summary = "CRASH: %s" % ex else: # short summary about new nodes cl = repo.changelog descs = [] newnodes = set(repo.nodes("all()")) newrevs = list(map(cl.rev, newnodes - oldnodes)) for rev in sorted(newrevs): desc = "%s:" % getdesc(rev) for prev in cl.parentrevs(rev): if prev > -1: desc += getdesc(prev) descs.append(desc) descs.sort() summary = " ".join(descs) ui.popbuffer() repo.localvfs.tryunlink("rebasestate") subsetdesc = "".join(getdesc(rev) for rev in subset) ui.write(("%s: %s\n") % (subsetdesc.rjust(len(srevs)), summary)) tr.abort()
def restack(ui, repo, **rebaseopts): """Repair a situation in which one or more commits in a stack have been obsoleted (thereby leaving their descendants in the stack orphaned) by finding any such commits and rebasing their descendants onto the latest version of each respective commit. """ rebaseopts = rebaseopts.copy() with repo.wlock(), repo.lock(): # Find drafts connected to the current stack via either changelog or # obsolete graph. Note: "draft() & ::." is optimized by D441. if not rebaseopts["rev"]: # 1. Connect drafts via changelog revs = list(repo.revs("(draft() & ::.)::")) if not revs: # "." is probably public. Check its direct children. revs = repo.revs("draft() & children(.)") if not revs: ui.status(_("nothing to restack\n")) return 1 # 2. Connect revs via obsolete graph revs = list( repo.revs("successors(%ld)+predecessors(%ld)", revs, revs)) # 3. Connect revs via changelog again to cover missing revs revs = list(repo.revs("draft() & ((draft() & %ld)::)", revs)) rebaseopts["rev"] = [ctx.hex() for ctx in repo.set("%ld", revs)] rebaseopts["dest"] = "_destrestack(SRC)" rebase.rebase(ui, repo, **rebaseopts) # Ensure that we always end up on the latest version of the # current changeset. Usually, this will be taken care of # by the rebase operation. However, in some cases (such as # if we are on the precursor of the base changeset) the # rebase will not update to the latest version, so we need # to do this manually. successor = repo.revs("successors(.) - .").last() if successor is not None: commands.update(ui, repo, rev=repo[successor].hex())
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()])