refencode: replace Python reference parsing with Rust

Summary:
Use Rust refencode to encode and decode commit references in Python.
This removes duplicated implementation.

Reviewed By: DurhamG

Differential Revision: D33237327

fbshipit-source-id: 2e4baa5a422fa562f355da2a82633d5b02c78a7d
This commit is contained in:
Jun Wu 2021-12-21 12:35:00 -08:00 committed by Facebook GitHub Bot
parent 1c04d3a663
commit 743b81ef16
6 changed files with 142 additions and 93 deletions

View File

@ -16,6 +16,8 @@ import errno
import struct
import typing
import bindings
from . import (
encoding,
error,
@ -78,51 +80,36 @@ class bmstore(dict):
nm = repo.changelog.nodemap
tonode = bin # force local lookup
setitem = dict.__setitem__
with _getbkfile(repo) as bkfile:
data = bkfile.read()
decoded = bindings.refencode.decodebookmarks(data)
try:
with _getbkfile(repo) as bkfile:
for line in bkfile:
line = decodeutf8(line)
line = line.strip()
if not line:
continue
try:
sha, refspec = line.split(" ", 1)
node = tonode(sha)
except (TypeError, ValueError):
# TypeError:
# - bin(...)
# ValueError:
# - node in nm, for non-20-bytes entry
# - split(...), for string without ' '
repo.ui.warn(_("malformed line in .hg/bookmarks: %r\n") % line)
for refspec, node in sorted(decoded.items()):
if node in nm:
refspec = encoding.tolocal(refspec)
setitem(self, refspec, node)
else:
# This might happen if:
# - changelog was loaded, bookmarks are not loaded
# - bookmarks was changed to point to unknown nodes
# - bookmarks are loaded
#
# Try to mitigate by reloading changelog.
repo.invalidate()
nm = repo.changelog.nodemap
if node in nm:
refspec = encoding.tolocal(refspec)
setitem(self, refspec, node)
repo.ui.log("features", feature="fix-bookmark-changelog-order")
else:
if node in nm:
refspec = encoding.tolocal(refspec)
setitem(self, refspec, node)
else:
# This might happen if:
# - changelog was loaded, bookmarks are not loaded
# - bookmarks was changed to point to unknown nodes
# - bookmarks are loaded
#
# Try to mitigate by reloading changelog.
repo.invalidate()
nm = repo.changelog.nodemap
if node in nm:
refspec = encoding.tolocal(refspec)
setitem(self, refspec, node)
repo.ui.log(
"features", feature="fix-bookmark-changelog-order"
)
else:
repo.ui.log(
"features",
feature="fix-bookmark-changelog-order-failed",
)
repo.ui.warn(
_("unknown reference in .hg/bookmarks: %s %s\n")
% (refspec, sha)
)
repo.ui.log(
"features",
feature="fix-bookmark-changelog-order-failed",
)
repo.ui.warn(
_("unknown reference in .hg/bookmarks: %s %s\n")
% (refspec, hex(node))
)
except IOError as inst:
if inst.errno != errno.ENOENT:
@ -205,9 +192,8 @@ class bmstore(dict):
self._aclean = True
def _write(self, fp):
for name, node in sorted(pycompat.iteritems(self)):
name = encoding.fromlocal(name)
fp.write(encodeutf8("%s %s\n" % (hex(node), name)))
encoded = bindings.refencode.encodebookmarks(self)
fp.write(encoded)
self._clean = True
self._repo.invalidatevolatilesets()
@ -1152,35 +1138,8 @@ def saveremotenames(repo, remotebookmarks, override=True):
repo.invalidatevolatilesets()
def decoderemotenames(data):
# type: (bytes) -> typing.Dict[str, bytes]
"""Decode remotenames into {fullname: node}
The fullname can further be split by `splitremotename`.
"""
result = {}
for line in decodeutf8(data).splitlines():
try:
hexnode, nametype, fullname = line.split(" ", 2)
except ValueError:
raise error.CorruptedState(_("corrupt entry in remotenames: %s") % (line,))
# Ignore 'default-push' names. See https://fburl.com/1rft34i8.
if fullname.startswith("default-push/"):
continue
if nametype != "bookmarks":
continue
result[fullname] = bin(hexnode)
return result
def encoderemotenames(fullnamenodes):
# type: typing.List[typing.Tuple[str, bytes]] -> (bytes)
"""Encode {fullname: node} to remotenames format."""
content = "".join(
"%s bookmarks %s\n" % (hex(node), name)
for name, node in sorted(fullnamenodes.items())
)
return encodeutf8(content)
encoderemotenames = bindings.refencode.encoderemotenames
decoderemotenames = bindings.refencode.decoderemotenames
class lazyremotenamedict(pycompat.Mapping):

View File

@ -43,24 +43,8 @@ def stoptracking(repo):
repo.svfs.write("visibleheads", b"")
# Supported file format version.
# Version 1 is:
# * A single line containing "v1"
# * A list of node hashes for each visible head, one per line.
FORMAT_VERSION = "v1"
def encodeheads(heads):
return encodeutf8(
"%s\n%s" % (FORMAT_VERSION, "".join("%s\n" % node.hex(h) for h in heads))
)
def decodeheads(data):
lines = decodeutf8(data).splitlines()
if lines and lines[0].strip() != FORMAT_VERSION:
raise error.Abort("invalid visibleheads file format %r" % lines[0])
return [node.bin(head.strip()) for head in lines[1:]]
encodeheads = bindings.refencode.encodevisibleheads
decodeheads = bindings.refencode.decodevisibleheads
class visibleheads(object):

View File

@ -49,6 +49,7 @@ pypathmatcher = { path = "modules/pypathmatcher" }
pypprint = { path = "modules/pypprint" }
pyprocess = { path = "modules/pyprocess" }
pyprogress = { path = "modules/pyprogress" }
pyrefencode = { path = "modules/pyrefencode" }
pyregex = { path = "modules/pyregex" }
pyrenderdag = { path = "modules/pyrenderdag" }
pyrevisionstore = { path = "modules/pyrevisionstore" }

View File

@ -0,0 +1,13 @@
[package]
name = "pyrefencode"
version = "0.1.0"
edition = "2021"
[dependencies]
cpython_ext = { path = "../../../../lib/cpython-ext", default-features = false }
cpython = { version = "0.7", default-features = false }
refencode = { path = "../../../../lib/refencode" }
[features]
python2 = ["cpython/python27-sys", "cpython_ext/python2"]
python3 = ["cpython/python3-sys", "cpython_ext/python3"]

View File

@ -0,0 +1,91 @@
/*
* 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.
*/
use std::collections::BTreeMap;
use cpython::*;
use cpython_ext::convert::Serde;
use cpython_ext::ResultPyErrExt;
use refencode::HgId;
pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
let name = [package, "refencode"].join(".");
let m = PyModule::new(py, &name)?;
m.add(
py,
"decodebookmarks",
py_fn!(py, decodebookmarks(data: PyBytes)),
)?;
m.add(
py,
"decoderemotenames",
py_fn!(py, decoderemotenames(data: PyBytes)),
)?;
m.add(
py,
"decodevisibleheads",
py_fn!(py, decodevisibleheads(data: PyBytes)),
)?;
m.add(
py,
"encodebookmarks",
py_fn!(
py,
encodebookmarks(namenodes: Serde<BTreeMap<String, HgId>>)
),
)?;
m.add(
py,
"encoderemotenames",
py_fn!(
py,
encoderemotenames(namenodes: Serde<BTreeMap<String, HgId>>)
),
)?;
m.add(
py,
"encodevisibleheads",
py_fn!(py, encodevisibleheads(namenodes: Serde<Vec<HgId>>)),
)?;
Ok(m)
}
fn decodebookmarks(py: Python, data: PyBytes) -> PyResult<Serde<BTreeMap<String, HgId>>> {
let data = data.data(py);
let decoded = refencode::decode_bookmarks(data).map_pyerr(py)?;
Ok(Serde(decoded))
}
fn decoderemotenames(py: Python, data: PyBytes) -> PyResult<Serde<BTreeMap<String, HgId>>> {
let data = data.data(py);
let decoded = refencode::decode_remotenames(data).map_pyerr(py)?;
Ok(Serde(decoded))
}
fn decodevisibleheads(py: Python, data: PyBytes) -> PyResult<Serde<Vec<HgId>>> {
let data = data.data(py);
let decoded = refencode::decode_visibleheads(data).map_pyerr(py)?;
Ok(Serde(decoded))
}
fn encodebookmarks(py: Python, namenodes: Serde<BTreeMap<String, HgId>>) -> PyResult<PyBytes> {
let encoded = refencode::encode_bookmarks(&namenodes.0);
Ok(PyBytes::new(py, encoded.as_ref()))
}
fn encoderemotenames(py: Python, namenodes: Serde<BTreeMap<String, HgId>>) -> PyResult<PyBytes> {
let encoded = refencode::encode_remotenames(&namenodes.0);
Ok(PyBytes::new(py, encoded.as_ref()))
}
fn encodevisibleheads(py: Python, nodes: Serde<Vec<HgId>>) -> PyResult<PyBytes> {
let encoded = refencode::encode_visibleheads(&nodes.0);
Ok(PyBytes::new(py, encoded.as_ref()))
}

View File

@ -49,6 +49,7 @@ pub(crate) fn populate_module(py: Python<'_>, module: &PyModule) -> PyResult<PyN
m.add(py, "pprint", pypprint::init_module(py, &name)?)?;
m.add(py, "process", pyprocess::init_module(py, &name)?)?;
m.add(py, "progress", pyprogress::init_module(py, &name)?)?;
m.add(py, "refencode", pyrefencode::init_module(py, &name)?)?;
m.add(py, "regex", pyregex::init_module(py, &name)?)?;
m.add(py, "renderdag", pyrenderdag::init_module(py, &name)?)?;
m.add(