mirror of
https://github.com/facebook/sapling.git
synced 2024-10-12 09:48:05 +03:00
6525966fd1
Summary: After going over the code review for D14332967, I have decided to keep things simple for now and only allow making commits to same target parent as the original parent. This was already the intention with the existing code. Therefore, this commit just further enforces the requirement. Reviewed By: quark-zju Differential Revision: D14422351 fbshipit-source-id: 2f786fc3596b17c5020de9906adf8f22b50be4dd
273 lines
8.2 KiB
Python
273 lines
8.2 KiB
Python
# Copyright 2019 Facebook, Inc.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
"""make commits without a working copy
|
|
|
|
With this extension enabled, Mercurial provides a command i.e. `memcommit` to
|
|
make commits to a repository without requiring a working copy.
|
|
|
|
Config::
|
|
|
|
[memcommit]
|
|
# allow creating commits with no parents.
|
|
allowunrelatedroots = False
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import contextlib
|
|
import json
|
|
import sys
|
|
|
|
from edenscm.mercurial import bookmarks, error, registrar, scmutil
|
|
from edenscm.mercurial.i18n import _
|
|
from edenscm.mercurial.node import hex, nullid
|
|
|
|
from . import commitdata, serialization
|
|
from ..pushrebase.stackpush import pushrequest
|
|
|
|
|
|
configtable = {}
|
|
configitem = registrar.configitem(configtable)
|
|
configitem("memcommit", "allowunrelatedroots", default=False)
|
|
|
|
cmdtable = {}
|
|
command = registrar.command(cmdtable)
|
|
|
|
|
|
@command(
|
|
"^debugserializecommit",
|
|
[
|
|
("r", "rev", "", _("revision to serialize"), _("REV")),
|
|
("d", "dest", "", _("destination bookmark"), _("DEST")),
|
|
("", "to", "", _("destination parents"), _("TO")),
|
|
("", "pushrebase", False, _("pushrebase commit")),
|
|
],
|
|
_("hg debugserializecommit -r REV -d DEST"),
|
|
)
|
|
def debugserializecommit(ui, repo, *args, **opts):
|
|
"""serialize commit in format consumable by 'memcommit' command
|
|
|
|
If no revision for serialization is specified, the current commit is
|
|
serialized.
|
|
|
|
This command is mainly intended for the testing the 'memcommit' command.
|
|
"""
|
|
ctx = scmutil.revsingle(repo, opts.get("rev"))
|
|
changelistbuilder = commitdata.changelistbuilder(ctx.p1().hex())
|
|
|
|
for path in ctx.files():
|
|
if path in ctx:
|
|
fctx = ctx[path]
|
|
renamed = fctx.renamed()
|
|
copysource = renamed[0] if renamed else None
|
|
|
|
info = commitdata.fileinfo(
|
|
flags=fctx.flags(), content=fctx.data(), copysource=copysource
|
|
)
|
|
else:
|
|
info = commitdata.fileinfo(deleted=True)
|
|
|
|
changelistbuilder.addfile(path, info)
|
|
|
|
changelist = changelistbuilder.build()
|
|
|
|
bookmark = opts.get("dest")
|
|
to = opts.get("to")
|
|
pushrebase = opts.get("pushrebase")
|
|
|
|
destination = commitdata.destination(bookmark=bookmark, pushrebase=pushrebase)
|
|
|
|
parents = (
|
|
[hex(p) for p in repo.nodes(to)] if to else [p.hex() for p in ctx.parents()]
|
|
)
|
|
|
|
metadata = commitdata.metadata(
|
|
author=ctx.user(),
|
|
description=ctx.description(),
|
|
parents=parents,
|
|
extra=ctx.extra(),
|
|
)
|
|
|
|
params = commitdata.params(changelist, metadata, destination)
|
|
ui.write(serialization.serialize(params.todict()))
|
|
|
|
|
|
@command("^memcommit", [], _("hg memcommit"))
|
|
def memcommit(ui, repo, *args, **opts):
|
|
"""make commits without a working copy
|
|
|
|
This command supports creating commits in three different ways::
|
|
|
|
- Commit on a specified parent
|
|
|
|
In this case, we will create a commit on top of the specified parent.
|
|
|
|
- Commit on a specified bookmark
|
|
|
|
In this case, we will create a commit on top of the specified
|
|
bookmark. For now, we require that the specified bookmark refers to
|
|
the same commit as the specified parent.
|
|
|
|
After creating the commit, we move the bookmark to refer to the new
|
|
commit.
|
|
|
|
- Commit on a specified bookmark using pushrebase
|
|
|
|
In this case, we will create a commit only if the parent commit is an
|
|
ancestor or descendant of the specified bookmark.
|
|
|
|
- Case I: commit parent is an ancestor of bookmark
|
|
|
|
o bookmark
|
|
:
|
|
:
|
|
: o x
|
|
: /
|
|
: /
|
|
o
|
|
|
|
We should pushrebase x onto bookmark as long as there are no
|
|
merge conflicts.
|
|
|
|
- Case II: commit parent is a descendant of bookmark
|
|
|
|
o x
|
|
:
|
|
:
|
|
:
|
|
:
|
|
:
|
|
o bookmark
|
|
|
|
We will only commit x if the repository already has parent of x.
|
|
|
|
After creating the commit, we move the bookmark to refer to the new
|
|
commit.
|
|
|
|
The output of the command will be JSON based. There are two cases::
|
|
|
|
- Commit was created successfully
|
|
|
|
In this case, exit code will be zero and the output will be:
|
|
|
|
{ "hash": "<commithash>" }
|
|
|
|
where '<commithash>' is the commit hash for the newly created commit.
|
|
|
|
- Commit creation failed
|
|
|
|
In this case, exit code will be non-zero and the output will be:
|
|
|
|
{ "error": "<error>" }
|
|
|
|
where '<error>' will describe the error that occurred while attempting
|
|
to make the commit.
|
|
|
|
There will be no output if the `-q` i.e. quiet flag is specified.
|
|
"""
|
|
|
|
@contextlib.contextmanager
|
|
def nooutput(ui):
|
|
ui.pushbuffer(error=True, subproc=True)
|
|
try:
|
|
yield
|
|
finally:
|
|
ui.popbuffer()
|
|
|
|
out = {}
|
|
try:
|
|
with nooutput(ui):
|
|
params = commitdata.params.fromdict(serialization.deserialize(ui.fin))
|
|
out["hash"] = hex(_memcommit(repo, params))
|
|
except Exception as ex:
|
|
out["error"] = str(ex)
|
|
sys.exit(255)
|
|
finally:
|
|
if not ui.quiet:
|
|
ui.write(json.dumps(out))
|
|
|
|
|
|
def _memcommit(repo, params):
|
|
"""create a new commit in the repo based on the params
|
|
|
|
Isolating this method allows easy wrapping by other extensions like hgsql.
|
|
"""
|
|
|
|
with repo.wlock(), repo.lock(), repo.transaction("memcommit"):
|
|
|
|
def resolvetargetctx(repo, originalparentnode, targetparents):
|
|
numparents = len(targetparents)
|
|
|
|
if numparents > 1:
|
|
raise error.Abort(_("merge commits are not supported"))
|
|
|
|
if numparents == 0:
|
|
raise error.Abort(_("parent commit must be specified"))
|
|
|
|
targetctx = repo[targetparents[0]]
|
|
targetnode = targetctx.node()
|
|
|
|
if originalparentnode != targetnode:
|
|
raise error.Abort(_("commit with new parents not supported"))
|
|
|
|
if (
|
|
not repo.ui.configbool("memcommit", "allowunrelatedroots")
|
|
and targetnode == nullid
|
|
):
|
|
raise error.Abort(_("commit without parents are not allowed"))
|
|
|
|
return targetctx
|
|
|
|
request = pushrequest.frommemcommit(repo, params)
|
|
originalparentnode = request.stackparentnode
|
|
targetctx = resolvetargetctx(repo, originalparentnode, params.metadata.parents)
|
|
targetnode = targetctx.node()
|
|
|
|
destination = params.destination
|
|
pushrebase = destination.pushrebase
|
|
ontobookmark = destination.bookmark
|
|
|
|
if ontobookmark:
|
|
bookmarkctx = scmutil.revsingle(repo, ontobookmark)
|
|
if not pushrebase and bookmarkctx.node() != targetnode:
|
|
raise error.Abort(
|
|
_("destination parent does not match destination bookmark")
|
|
)
|
|
elif pushrebase:
|
|
raise error.Abort(_("must specify destination bookmark for pushrebase"))
|
|
|
|
if pushrebase:
|
|
ontonode = bookmarkctx.node()
|
|
cl = repo.changelog
|
|
if cl.isancestor(originalparentnode, ontonode):
|
|
targetctx = bookmarkctx
|
|
elif cl.isancestor(ontonode, originalparentnode):
|
|
targetctx = repo[originalparentnode]
|
|
else:
|
|
raise error.Abort(
|
|
_(
|
|
"destination bookmark is not ancestor or descendant of commit parent"
|
|
)
|
|
)
|
|
|
|
added, replacements = request.pushonto(targetctx)
|
|
|
|
if len(added) > 1:
|
|
# We always create a single commit.
|
|
error.Abort(_("more than one commit was created"))
|
|
|
|
if replacements:
|
|
# We always create a new commit and therefore, cannot have any
|
|
# replacements.
|
|
error.Abort(_("new commit cannot replace any commit"))
|
|
|
|
node = added[0]
|
|
|
|
if ontobookmark:
|
|
bookmarks.pushbookmark(repo, ontobookmark, bookmarkctx.hex(), node)
|
|
|
|
return node
|