Info command that prints snapshot changes

Summary:
This diff makes `hg snapshot info` an equivalent to `hg show`, which shows info about the snapshot and its changes.

To do that, it creates a `memctx` for the snapshot, which is kind of a commit in memory, which is useful because we can then reuse base hg stuff to show the diff :)

Reviewed By: mitrandir77

Differential Revision: D31802201

fbshipit-source-id: 1bcea87e9a1b3ee665e04822fe78ff03e3ad393e
This commit is contained in:
Yan Soares Couto 2021-11-01 09:15:33 -07:00 committed by Facebook GitHub Bot
parent 396e345703
commit e63377ac51
8 changed files with 124 additions and 43 deletions

View File

@ -6,7 +6,7 @@
from edenscm.mercurial import error, registrar
from edenscm.mercurial.i18n import _
from . import createremote, restore, info
from . import createremote, update, show
cmdtable = {}
command = registrar.command(cmdtable)
@ -23,8 +23,8 @@ def snapshot(ui, repo, **opts):
subcmd = snapshot.subcommand(
categories=[
("Manage snapshots", ["createremote", "restore"]),
("Query snapshots", ["info"]),
("Manage snapshots", ["create", "update"]),
("Query snapshots", ["show"]),
]
)
@ -36,7 +36,7 @@ def createremotecmd(*args, **kwargs):
@subcmd(
"restore",
"update|restore|checkout|co|up",
[
(
"C",
@ -47,12 +47,12 @@ def createremotecmd(*args, **kwargs):
],
_("ID"),
)
def restorecmd(*args, **kwargs):
def updatecmd(*args, **kwargs):
"""download a previously created snapshot and update working copy to its state"""
restore.restore(*args, **kwargs)
update.update(*args, **kwargs)
@subcmd("info", [], _("ID"))
def infocmd(*args, **kwargs):
@subcmd("show|info", [], _("ID"))
def showcmd(*args, **kwargs):
"""gather information about the snapshot"""
info.info(*args, **kwargs)
show.show(*args, **kwargs)

View File

@ -1,24 +0,0 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
from edenscm.mercurial import error
from edenscm.mercurial.edenapi_upload import (
getreponame,
)
from edenscm.mercurial.i18n import _
def info(ui, repo, csid, **opts):
try:
repo.edenapi.fetchsnapshot(
getreponame(repo),
{
"cs_id": bytes.fromhex(csid),
},
)
except Exception:
raise error.Abort(_("snapshot doesn't exist"))
else:
ui.status(_("snapshot exists\n"))

View File

@ -0,0 +1,88 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
from edenscm.mercurial import error, scmutil
from edenscm.mercurial.cmdutil import changeset_printer
from edenscm.mercurial.context import memctx, memfilectx
from edenscm.mercurial.edenapi_upload import (
getreponame,
)
from edenscm.mercurial.i18n import _
from edenscm.mercurial.node import nullid
from edenscm.mercurial.util import pickle
def _snapshot2ctx(repo, snapshot):
"""Build a memctx for this snapshot.
This is not precisely correct as it doesn't differentiate untracked/added
but it's good enough for diffing.
"""
parent = snapshot["hg_parents"]
# Once merges/conflicted states are supported, we'll need to support more
# than one parent
assert isinstance(parent, bytes)
parents = (parent, nullid)
path2filechange = {f[0]: f[1] for f in snapshot["file_changes"]}
def token2cacheable(token):
data = token["data"]
return pickle.dumps((data["id"], data["bubble_id"]))
cache = {}
def getfile(repo, memctx, path):
change = path2filechange.get(path)
if change is None:
return repo[parent][path]
if change == "Deletion" or change == "UntrackedDeletion":
return None
elif "Change" in change or "UntrackedChange" in change:
change = change.get("Change") or change["UntrackedChange"]
token = change["upload_token"]
key = token2cacheable(token)
if key not in cache:
# Possible future optimisation: Download files in parallel
cache[key] = repo.edenapi.downloadfiletomemory(getreponame(repo), token)
islink = change["file_type"] == "Symlink"
isexec = change["file_type"] == "Executable"
return memfilectx(
repo, None, path, data=cache[key], islink=islink, isexec=isexec
)
else:
raise error.Abort(_("Unknown file change {}").format(change))
ctx = memctx(
repo,
parents,
text="",
files=list(path2filechange.keys()),
filectxfn=getfile,
user=None,
date=None,
)
return ctx
def show(ui, repo, csid, **opts):
try:
snapshot = repo.edenapi.fetchsnapshot(
getreponame(repo),
{
"cs_id": bytes.fromhex(csid),
},
)
except Exception:
raise error.Abort(_("snapshot doesn't exist"))
else:
ui.status(_("snapshot: {}\n").format(csid))
ctx = _snapshot2ctx(repo, snapshot)
displayer = changeset_printer(
ui, repo, scmutil.matchall(repo), {"patch": True}, False
)
displayer.show(ctx)
displayer.close()

View File

@ -38,7 +38,7 @@ def _fullclean(ui, repo):
)
def restore(ui, repo, csid, clean=False):
def update(ui, repo, csid, clean=False):
ui.status(_("Will restore snapshot {}\n").format(csid), component="snapshot")
snapshot = repo.edenapi.fetchsnapshot(

View File

@ -1954,10 +1954,11 @@ class changeset_printer(object):
return
columns = self._columns
self.ui.write(
columns["changeset"] % scmutil.formatchangeid(ctx),
label=_changesetlabels(ctx),
)
if changenode:
self.ui.write(
columns["changeset"] % scmutil.formatchangeid(ctx),
label=_changesetlabels(ctx),
)
# branches are shown first before any other names due to backwards
# compatibility

View File

@ -392,6 +392,15 @@ py_class!(pub class client |py| {
self.inner(py).clone().downloadfiles_py(py, repo, root, files)
}
/// Download file from given upload token to memory
def downloadfiletomemory(
&self,
repo: String,
token: Serde<UploadToken>
) -> PyResult<PyBytes> {
self.inner(py).clone().downloadfiletomemory_py(py, repo, token)
}
def ephemeralprepare(&self, repo: String)
-> PyResult<TStream<anyhow::Result<Serde<EphemeralPrepareResponse>>>>
{

View File

@ -741,6 +741,18 @@ pub trait EdenApiPyExt: EdenApi {
.map_pyerr(py)
.map(|_| true)
}
fn downloadfiletomemory_py(
&self,
py: Python,
repo: String,
token: Serde<UploadToken>,
) -> PyResult<PyBytes> {
py.allow_threads(|| block_unless_interrupted(self.download_file(repo.clone(), token.0)))
.map_pyerr(py)?
.map_pyerr(py)
.map(|data| PyBytes::new(py, &data))
}
}
impl<T: EdenApi + ?Sized> EdenApiPyExt for T {}

View File

@ -1627,13 +1627,11 @@ sh % "hg log -r 'null:null'" == r"""
# clean:
sh % "hg log -r 'wdir()' --debug" == r"""
commit: ffffffffffffffffffffffffffffffffffffffff
phase: draft
user: test
date: Thu Jan 01 00:00:00 1970 +0000
extra: branch=default"""
sh % "hg log -r 'wdir()' -p --stat" == r"""
commit: ffffffffffff
user: test
date: Thu Jan 01 00:00:00 1970 +0000"""
@ -1649,13 +1647,11 @@ sh % "hg status" == r"""
R .d6/f1"""
sh % "hg log -r 'wdir()'" == r"""
commit: ffffffffffff
user: test
date: Thu Jan 01 00:00:00 1970 +0000"""
sh % "hg log -r 'wdir()' -q" == "ffffffffffff"
sh % "hg log -r 'wdir()' --debug" == r"""
commit: ffffffffffffffffffffffffffffffffffffffff
phase: draft
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@ -1664,7 +1660,6 @@ sh % "hg log -r 'wdir()' --debug" == r"""
files-: .d6/f1
extra: branch=default"""
sh % "hg log -r 'wdir()' -p --stat --git" == r"""
commit: ffffffffffff
user: test
date: Thu Jan 01 00:00:00 1970 +0000