/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::collections::HashMap;
use std::sync::Arc;

use anyhow::Result;
use async_runtime::try_block_unless_interrupted;
use checkout::Action;
use checkout::ActionMap;
use checkout::Checkout;
use checkout::CheckoutPlan;
use checkout::Conflict;
use checkout::Merge;
use checkout::MergeResult;
use cpython::*;
use cpython_ext::convert::ImplInto;
use cpython_ext::ExtractInnerRef;
use cpython_ext::PyNone;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use manifest_tree::Diff;
use manifest_tree::TreeManifest;
use pathmatcher::Matcher;
use progress_model::ProgressBar;
use pyconfigloader::config;
use pymanifest::treemanifest;
use pypathmatcher::extract_matcher;
use pypathmatcher::extract_option_matcher;
use pystatus::status as PyStatus;
use pytreestate::treestate as PyTreeState;
use storemodel::ReadFileContents;
use vfs::VFS;

type ArcReadFileContents = Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync>;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "checkout"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<checkoutplan>(py)?;
    m.add_class::<mergeresult>(py)?;
    m.add_class::<manifestbuilder>(py)?;
    Ok(m)
}

py_class!(class checkoutplan |py| {
    data plan: CheckoutPlan;

    def __new__(
        _cls,
        config: &config,
        root: PyPathBuf,
        current_manifest: &treemanifest,
        target_manifest: &treemanifest,
        matcher: Option<PyObject> = None,
        // If sparse profile changes, contains Some((old_sparse_matcher, new_sparse_matcher))
        sparse_change: Option<(PyObject, PyObject)> = None,
        progress_path: Option<PyPathBuf> = None,
    ) -> PyResult<checkoutplan> {
        let config = config.get_cfg(py);
        let matcher: Arc<dyn Matcher + Send + Sync> = extract_option_matcher(py, matcher)?;

        let current = current_manifest.get_underlying(py);
        let target = target_manifest.get_underlying(py);
        let mut actions = py.allow_threads(move || {
            let target = target.read();
            let current = current.read();
            let diff = Diff::new(&current, &target, &matcher)?;
            ActionMap::from_diff(diff)
        }).map_pyerr(py)?;

        let current_lock = current_manifest.get_underlying(py);
        let target_lock = target_manifest.get_underlying(py);
        if let Some((old_sparse_matcher, new_sparse_matcher)) = sparse_change {
            let old_matcher = extract_matcher(py, old_sparse_matcher)?;
            let new_matcher = extract_matcher(py, new_sparse_matcher)?;
            actions = py.allow_threads(move || {
                let current = current_lock.read();
                let target = target_lock.read();
                actions.with_sparse_profile_change(old_matcher, new_matcher, &*current, &*target)
            }).map_pyerr(py)?;
        }
        let vfs = VFS::new(root.to_path_buf()).map_pyerr(py)?;
        let checkout = Checkout::from_config(vfs, &config).map_pyerr(py)?;
        let mut plan = checkout.plan_action_map(actions);
        if let Some(progress_path) = progress_path {
            plan.add_progress(progress_path.as_path()).map_pyerr(py)?;
        }
        checkoutplan::create_instance(py, plan)
    }

    def check_unknown_files(
        &self,
        manifest: &treemanifest,
        store: ImplInto<ArcReadFileContents>,
        state: &PyTreeState,
        status: &PyStatus,
    ) -> PyResult<Vec<String>> {
        let plan = self.plan(py);
        let state = state.get_state(py);
        let manifest = manifest.get_underlying(py);
        let store = store.into();
        let status = status.extract_inner_ref(py);
        let unknown = py.allow_threads(move || -> Result<_> {
            let mut state = state.lock();
            let manifest = manifest.read();
            try_block_unless_interrupted(
            plan.check_unknown_files(&*manifest, store.as_ref(), &mut state, status))
        }).map_pyerr(py)?;
        Ok(unknown.into_iter().map(|p|p.to_string()).collect())
    }

    def check_conflicts(&self, status: &PyStatus) -> PyResult<Vec<String>> {
        let status = status.extract_inner_ref(py);
        let plan = self.plan(py);
        let conflicts = plan.check_conflicts(status);
        let conflicts = conflicts.into_iter().map(ToString::to_string).collect();
        Ok(conflicts)
    }

    def apply(&self, store: ImplInto<ArcReadFileContents>) -> PyResult<PyNone> {
        let plan = self.plan(py);
        let store = store.into();
        py.allow_threads(|| try_block_unless_interrupted(
            plan.apply_store(store.as_ref())
        )).map_pyerr(py)?;
        Ok(PyNone)
    }

    def apply_dry_run(&self, store: ImplInto<ArcReadFileContents>) -> PyResult<(usize, u64)> {
        let plan = self.plan(py);
        let store = store.into();
        py.allow_threads(|| try_block_unless_interrupted(
            plan.apply_store_dry_run(store.as_ref())
        )).map_pyerr(py)
    }

    def stats(&self) -> PyResult<(usize, usize, usize, usize)> {
        let plan = self.plan(py);
        let (updated, removed) = plan.stats();
        let (merged, unresolved) = (0, 0);

        Ok((updated, merged, removed, unresolved))
    }

    def record_updates(&self, state: &PyTreeState) -> PyResult<PyNone> {
        let plan = self.plan(py);
        let vfs = plan.vfs();
        let state = state.get_state(py);
        py.allow_threads(move || -> Result<()> {
            let bar = ProgressBar::register_new("recording", plan.all_files().count() as u64, "files");

            let mut state = state.lock();

            for removed in plan.removed_files() {
                state.remove(removed)?;
                bar.increase_position(1);
            }

            for updated in plan.updated_content_files().chain(plan.updated_meta_files()) {
                let fstate = checkout::file_state(vfs, updated)?;
                state.insert(updated, &fstate)?;
                bar.increase_position(1);
            }

            Ok(())
        }).map_pyerr(py)?;

        Ok(PyNone)
    }

    def __str__(&self) -> PyResult<PyString> {
        Ok(PyString::new(py, &self.plan(py).to_string()))
    }

    // This function is not efficient, only good for debug commands
    def __len__(&self) -> PyResult<usize> {
        Ok(self.plan(py).all_files().count())
    }

});

py_class!(class mergeresult |py| {
    data merge_result: MergeResult<TreeManifest>;

    def __new__(
        _cls,
        src_manifest: &treemanifest,
        dst_manifest: &treemanifest,
        ancestor_manifest: &treemanifest,
        // matcher: Option<PyObject> = None,
        // If sparse profile changes, contains Some((old_sparse_matcher, new_sparse_matcher))
        // sparse_change: Option<(PyObject, PyObject)> = None,
    ) -> PyResult<mergeresult> {
        let src_lock = src_manifest.get_underlying(py);
        let dst_lock = dst_manifest.get_underlying(py);
        let ancestor_lock = ancestor_manifest.get_underlying(py);
        let merge_result = py.allow_threads(move || -> Result<_> {
            let src = src_lock.read();
            let dst = dst_lock.read();
            let ancestor = ancestor_lock.read();
            Merge{}.merge(&*src, &*dst, &*ancestor)
        }).map_pyerr(py)?;
        mergeresult::create_instance(py, merge_result)
    }

    def __str__(&self) -> PyResult<PyString> {
        Ok(PyString::new(py, &self.merge_result(py).to_string()))
    }

    def pymerge_actions(&self) -> PyResult<Option<HashMap<String, (&'static str, (&'static str, bool), &'static str)>>> {
        let actions = self.merge_result(py).try_actions();
        if let Some(actions) = actions {
            Ok(Some(actions.iter().map(|(k,v)|(k.to_string(), v.pymerge_action())).collect()))
        } else {
            Ok(None)
        }
    }

    def manifestbuilder(&self) -> PyResult<Option<manifestbuilder>> {
        let mut modifiedconflicts = vec![];
        for (path, conflict) in self.merge_result(py).conflicts().iter() {
            match conflict {
                Conflict::BothChanged{ancestor, dest, src} => {
                    if ancestor.is_some() && src.file_type == dest.file_type {
                        modifiedconflicts.push(path.to_string()); // both modified
                    } else {
                        // This is either both created(ancestor.is_none), no way to do 3-way merge
                        // Or, file type differs between src and dst - needs special handling
                        return Ok(None);
                    }
                },
                _ => return Ok(None)
            }
        }
        let actions = self.merge_result(py).actions();
        let actions = actions.clone();
        Ok(Some(manifestbuilder::create_instance(py, actions, modifiedconflicts)?))
    }

    def conflict_paths(&self) -> PyResult<Vec<String>> {
        Ok(self.merge_result(py).conflicts().keys().map(|k|k.to_string()).collect())
    }
});

py_class!(class manifestbuilder |py| {
    data actions: ActionMap;
    data _modifiedconflicts: Vec<String>;

    def removed(&self) -> PyResult<Vec<String>> {
        let actions = self.actions(py);
        Ok(actions.iter().filter_map(|(f, a)|
            if matches!(a, Action::Remove) {
                Some(f.to_string())
            } else {
                None
            })
        .collect())
    }

    def modified(&self) -> PyResult<Vec<String>> {
        let actions = self.actions(py);
        Ok(actions.iter().filter_map(|(f, a)|
            if !matches!(a, Action::Remove) {
                Some(f.to_string())
            } else {
                None
            })
        .collect())
    }

    def modifiedconflicts(&self) -> PyResult<Vec<String>> {
        Ok(self._modifiedconflicts(py).clone())
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::cell::RefCell;

use ::clientinfo as client_info;
use cpython::*;
use cpython_ext::ResultPyErrExt;
use pyconfigloader::config;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "pyclientinfo"].join(".");
    let m = PyModule::new(py, &name)?;

    m.add_class::<clientinfo>(py)?;
    Ok(m)
}

/// Convert `io::Result<Vec<u8>>` to a `PyResult<PyBytes>`.
fn convert<T>(py: Python, result: Result<Vec<u8>, T>) -> PyResult<PyBytes>
where
    T: std::fmt::Display,
{
    result
        .map_err(|e| PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))
        .map(|buf| PyBytes::new(py, &buf))
}

py_class!(pub class clientinfo |py| {
    data clientinfo: RefCell<client_info::ClientInfo>;

    def __new__(_cls, config: config) -> PyResult<clientinfo> {
        let config = config.get_cfg(py);
        let clientinfo = client_info::ClientInfo::new(&config).map_pyerr(py)?;

        clientinfo::create_instance(py, RefCell::new(clientinfo))
    }

    def into_json(&self) -> PyResult<PyBytes> {
        convert(py, self.clientinfo(py).borrow().into_json().map(|s| s.into_bytes()))
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::HashMap;

use clidispatch::global_flags::HgGlobalOpts;
use cliparser::alias::expand_aliases;
use cliparser::parser::*;
use configmodel::Config;
use cpython::*;
use cpython_ext::Str;
use pyconfigloader::config;

mod exceptions {
    use super::*;

    py_exception!(cliparser, AmbiguousCommand);
    py_exception!(cliparser, CircularReference);
    py_exception!(cliparser, MalformedAlias);
    py_exception!(cliparser, OptionNotRecognized);
    py_exception!(cliparser, OptionRequiresArgument);
    py_exception!(cliparser, OptionArgumentInvalid);
    py_exception!(cliparser, OptionAmbiguous);
}

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "cliparser"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add(py, "earlyparse", py_fn!(py, early_parse(args: Vec<String>)))?;
    m.add(py, "parseargs", py_fn!(py, parse_args(args: Vec<String>)))?;
    m.add(
        py,
        "parse",
        py_fn!(py, parse(args: Vec<String>, keep_sep: bool)),
    )?;
    m.add(
        py,
        "expandargs",
        py_fn!(py, expand_args(config: config, args: Vec<String>,)),
    )?;
    m.add(
        py,
        "parsecommand",
        py_fn!(
            py,
            parse_command(args: Vec<String>, definitions: Vec<FlagDef>)
        ),
    )?;
    {
        use exceptions::*;
        m.add(py, "AmbiguousCommand", AmbiguousCommand::type_object(py))?;
        m.add(py, "CircularReference", CircularReference::type_object(py))?;
        m.add(py, "MalformedAlias", MalformedAlias::type_object(py))?;
        m.add(
            py,
            "OptionNotRecognized",
            OptionNotRecognized::type_object(py),
        )?;
        m.add(
            py,
            "OptionRequiresArgument",
            OptionRequiresArgument::type_object(py),
        )?;
        m.add(
            py,
            "OptionArgumentInvalid",
            OptionArgumentInvalid::type_object(py),
        )?;
        m.add(py, "OptionAmbiguous", OptionAmbiguous::type_object(py))?;
    }
    Ok(m)
}

struct FlagDef {
    short: Option<char>,
    long: String,
    default: Value,
    flag_type: String,
}

impl<'s> FromPyObject<'s> for FlagDef {
    fn extract(py: Python, obj: &'s PyObject) -> PyResult<Self> {
        let tuple: PyTuple = obj.extract(py)?;
        if tuple.len(py) < 3 {
            let msg = format!("flag defintion requires at least 3 items");
            return Err(PyErr::new::<exc::ValueError, _>(py, msg));
        }
        let short: String = tuple.get_item(py, 0).extract(py)?;
        let long: String = tuple.get_item(py, 1).extract(py)?;
        let default: Value = tuple.get_item(py, 2).extract(py)?;
        let flag_type: String = if tuple.len(py) >= 4 {
            tuple.get_item(py, 3).extract(py)?
        } else {
            "".into()
        };
        Ok(FlagDef {
            short: short.chars().next(),
            long,
            default,
            flag_type,
        })
    }
}

impl Into<Flag> for FlagDef {
    fn into(self) -> Flag {
        let description = "";
        (
            self.short,
            self.long,
            description,
            self.default,
            self.flag_type,
        )
            .into()
    }
}

fn parse_command(
    py: Python,
    args: Vec<String>,
    definitions: Vec<FlagDef>,
) -> PyResult<(Vec<Str>, HashMap<Str, Value>)> {
    let flags: Vec<Flag> = definitions.into_iter().map(Into::into).collect();

    let result = ParseOptions::new()
        .flag_alias("repo", "repository")
        .flags(flags)
        .error_on_unknown_opts(true)
        .parse_args(&args)
        .map_err(|e| map_to_python_err(py, e))?;

    let arguments: Vec<Str> = result.args().clone().into_iter().map(Str::from).collect();

    let opts = result.opts().clone();

    let options: HashMap<Str, Value> = opts
        .into_iter()
        .map(|(k, v)| (k.replace('-', "_").into(), v))
        .collect();

    Ok((arguments, options))
}

fn expand_args(py: Python, config: config, args: Vec<String>) -> PyResult<(Vec<Str>, Vec<Str>)> {
    let cfg = &config.get_cfg(py);

    let lookup = move |name: &str| {
        if name.contains(":") {
            return None;
        }
        match (cfg.get("alias", name), cfg.get("defaults", name)) {
            (None, None) => None,
            (Some(v), None) => Some(v.to_string()),
            (None, Some(v)) => Some(format!("{} {}", name, v.as_ref())),
            (Some(a), Some(d)) => {
                // XXX: This makes defaults override alias if there are conflicted
                // flags. The desired behavior is to make alias override defaults.
                // However, [defaults] is deprecated and is likely only used
                // by tests. So this might be fine.
                Some(format!("{} {}", a.as_ref(), d.as_ref()))
            }
        }
    };

    let (expanded_args, replaced_aliases) =
        expand_aliases(lookup, &args).map_err(|e| map_to_python_err(py, e))?;

    let expanded_args: Vec<Str> = expanded_args.into_iter().map(Str::from).collect();
    let replaced_aliases: Vec<Str> = replaced_aliases.into_iter().map(Str::from).collect();

    Ok((expanded_args, replaced_aliases))
}

fn early_parse(py: Python, args: Vec<String>) -> PyResult<HashMap<String, PyObject>> {
    let result = ParseOptions::new()
        .ignore_prefix(true)
        .early_parse(true)
        .flag_alias("repo", "repository")
        .flags(HgGlobalOpts::flags())
        .parse_args(&args)
        .map_err(|e| map_to_python_err(py, e))?;
    let rust_opts = result.opts().clone();
    let mut opts = HashMap::new();

    for (key, value) in rust_opts {
        let val: PyObject = value.into_py_object(py).into_object();
        opts.insert(key, val);
    }
    Ok(opts)
}

fn parse_args(py: Python, args: Vec<String>) -> PyResult<Vec<String>> {
    let result = ParseOptions::new()
        .flag_alias("repo", "repository")
        .flags(HgGlobalOpts::flags())
        .parse_args(&args)
        .map_err(|e| map_to_python_err(py, e))?;
    let arguments = result.args().clone();

    Ok(arguments)
}

fn parse(
    py: Python,
    args: Vec<String>,
    keep_sep: bool,
) -> PyResult<(Vec<Str>, HashMap<Str, PyObject>, usize)> {
    let result = ParseOptions::new()
        .flag_alias("repo", "repository")
        .flags(HgGlobalOpts::flags())
        .keep_sep(keep_sep)
        .parse_args(&args)
        .map_err(|e| map_to_python_err(py, e))?;

    let arguments = result.args().iter().cloned().map(Str::from).collect();
    let opts = result
        .opts()
        .iter()
        .map(|(k, v)| (Str::from(k.clone()), v.to_py_object(py).into_object()))
        .collect();

    Ok((arguments, opts, result.first_arg_index()))
}

fn map_to_python_err(py: Python, err: ParseError) -> PyErr {
    let msg = format!("{}", err);
    match err {
        ParseError::OptionNotRecognized { option_name } => {
            return PyErr::new::<exceptions::OptionNotRecognized, _>(py, (msg, option_name));
        }
        ParseError::OptionRequiresArgument { option_name } => {
            return PyErr::new::<exceptions::OptionRequiresArgument, _>(py, (msg, option_name));
        }
        ParseError::OptionArgumentInvalid {
            option_name,
            given,
            expected,
        } => {
            return PyErr::new::<exceptions::OptionArgumentInvalid, _>(
                py,
                (msg, option_name, given, expected),
            );
        }
        ParseError::OptionAmbiguous {
            option_name,
            possibilities,
        } => {
            return PyErr::new::<exceptions::OptionAmbiguous, _>(
                py,
                (msg, option_name, possibilities),
            );
        }
        ParseError::AmbiguousCommand {
            command_name,
            possibilities,
        } => {
            return PyErr::new::<exceptions::AmbiguousCommand, _>(
                py,
                (msg, command_name, possibilities),
            );
        }
        ParseError::CircularReference { command_name } => {
            return PyErr::new::<exceptions::CircularReference, _>(py, (msg, command_name));
        }
        ParseError::MalformedAlias { name, value } => {
            return PyErr::new::<exceptions::MalformedAlias, _>(py, (msg, name, value));
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

// conch_parser is separate from edenscmnative/bindings intentionally
// so it can be used standalone without coupling with the rest of
// edenscm logic.

use conch_parser::lexer::Lexer;
use conch_parser::parse::DefaultParser;
use cpython::*;
use cpython_ext::ser::to_object;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "conchparser"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add(py, "parse", py_fn!(py, parse(code: &str)))?;
    Ok(m)
}

fn parse(py: Python, code: &str) -> PyResult<PyObject> {
    let lex = Lexer::new(code.chars());
    let mut parser = DefaultParser::new(lex);
    let mut commands = Vec::new();
    while let Some(command) = parser
        .complete_command()
        .map_err(|e| PyErr::new::<exc::ValueError, _>(py, e.to_string()))?
    {
        commands.push(command)
    }
    to_object(py, &commands)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Support `ImplInto` from cpython-ext.

use cpython::*;
use cpython_ext::convert::register_into;

use crate::config;

pub(crate) fn register(py: Python) {
    register_into(py, |py, c: config| c.get_config_trait(py));
    register_into(py, |py, c: config| c.get_thread_safe_config_trait(py));
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::cell::RefCell;
use std::sync::Arc;

use configloader::config::ConfigSet;
use configloader::config::Options;
use configloader::convert::parse_list;
use configloader::hg::ConfigSetHgExt;
use configloader::hg::OptionsHgExt;
use configloader::Config;
use cpython::*;
use cpython_ext::error::Result;
use cpython_ext::error::ResultPyErrExt;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;
use cpython_ext::Str;

mod impl_into;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "configloader"].join(".");
    let m = PyModule::new(py, &name)?;

    m.add_class::<config>(py)?;

    m.add(py, "parselist", py_fn!(py, parselist(value: String)))?;

    impl_into::register(py);

    Ok(m)
}

py_class!(pub class config |py| {
    data cfg: RefCell<ConfigSet>;

    def __new__(_cls) -> PyResult<config> {
        config::create_instance(py, RefCell::new(ConfigSet::new()))
    }

    def clone(&self) -> PyResult<config> {
        let cfg = self.cfg(py).borrow();
        config::create_instance(py, RefCell::new(cfg.clone()))
    }

    def readpath(
        &self,
        path: &PyPath,
        source: String,
        sections: Option<Vec<String>>,
        remap: Option<Vec<(String, String)>>,
        readonly_items: Option<Vec<(String, String)>>
    ) -> PyResult<Vec<Str>> {
        let mut cfg = self.cfg(py).borrow_mut();

        let mut opts = Options::new().source(source).process_hgplain();
        if let Some(sections) = sections {
            opts = opts.filter_sections(sections);
        }
        if let Some(remap) = remap {
            let map = remap.into_iter().collect();
            opts = opts.remap_sections(map);
        }
        if let Some(readonly_items) = readonly_items {
            opts = opts.readonly_items(readonly_items);
        }

        let errors = cfg.load_path(path, &opts);
        Ok(errors_to_str_vec(errors))
    }

    def parse(&self, content: String, source: String) -> PyResult<Vec<Str>> {
        let mut cfg = self.cfg(py).borrow_mut();
        let opts = source.into();
        let errors = cfg.parse(content, &opts);
        Ok(errors_to_str_vec(errors))
    }

    def get(&self, section: &str, name: &str) -> PyResult<Option<PyUnicode>> {
        let cfg = self.cfg(py).borrow();

        Ok(cfg.get(section, name).map(|v| PyUnicode::new(py, &v)))
    }

    def sources(
        &self, section: &str, name: &str
    ) -> PyResult<Vec<(Option<PyUnicode>, Option<(PyPathBuf, usize, usize, usize)>, PyUnicode)>> {
        // Return [(value, file_source, source)]
        // file_source is a tuple of (file_path, byte_start, byte_end, line)
        let cfg = self.cfg(py).borrow();
        let sources = cfg.get_sources(section, name);
        let mut result = Vec::with_capacity(sources.len());
        for source in sources.as_ref().iter() {
            let value = source.value().as_ref().map(|v| PyUnicode::new(py, &v));
            let file = source.location().map(|(path, range)| {
                // Calculate the line number - count "\n" till range.start
                let file = source.file_content().unwrap();
                let line = 1 + file.slice(0..range.start).chars().filter(|ch| *ch == '\n').count();

                let pypath = if path.as_os_str().is_empty() {
                    PyPathBuf::from(String::from("<builtin>"))
                } else {
                    let path = util::path::strip_unc_prefix(&path);
                    path.try_into().unwrap()
                };
                (pypath, range.start, range.end, line)
            });
            let source = PyUnicode::new(py, &source.source());
            result.push((value, file, source));
        }
        Ok(result)
    }

    def set(
        &self, section: String, name: String, value: Option<String>, source: String
    ) -> PyResult<PyNone> {
        let mut cfg = self.cfg(py).borrow_mut();
        let opts = source.into();
        cfg.set(section, name, value, &opts);
        Ok(PyNone)
    }

    def sections(&self) -> PyResult<Vec<PyUnicode>> {
        let cfg = self.cfg(py).borrow();
        Ok(cfg.sections().iter().map(|s| PyUnicode::new(py, &s)).collect())
    }

    def names(&self, section: &str) -> PyResult<Vec<PyUnicode>> {
        let cfg = self.cfg(py).borrow();
        Ok(cfg.keys(section).iter().map(|s| PyUnicode::new(py, &s)).collect())
    }

    def tostring(&self) -> PyResult<Str> {
        let cfg = self.cfg(py).borrow();
        Ok(cfg.to_string().into())
    }

    @staticmethod
    def load(repopath: Option<PyPathBuf>) -> PyResult<Self> {
        let repopath = repopath.as_ref().map(|p| p.as_path());
        let mut cfg = ConfigSet::new();
        cfg.load::<String, String>(repopath, None).map_pyerr(py)?;
        Self::create_instance(py, RefCell::new(cfg))
    }

    def reload(
        &self,
        repopath: Option<PyPathBuf>,
        readonly_items: Option<Vec<(String, String)>>
    ) -> PyResult<PyNone> {
        let repopath = repopath.as_ref().map(|p| p.as_path());
        let mut cfg = self.cfg(py).borrow_mut();
        cfg.load(repopath, readonly_items).map_pyerr(py)?;
        Ok(PyNone)
    }

    def files(&self) -> PyResult<Vec<PyPathBuf>> {
        self.cfg(py).borrow().files().iter().map(|p| p.as_path().try_into()).collect::<Result<Vec<PyPathBuf>>>().map_pyerr(py)
    }
});

impl config {
    pub fn get_cfg(&self, py: Python) -> ConfigSet {
        self.cfg(py).clone().into_inner()
    }

    pub(crate) fn get_config_trait(&self, py: Python) -> Arc<dyn Config> {
        Arc::new(self.get_cfg(py))
    }

    pub(crate) fn get_thread_safe_config_trait(&self, py: Python) -> Arc<dyn Config + Send + Sync> {
        Arc::new(self.get_cfg(py))
    }
}

fn parselist(py: Python, value: String) -> PyResult<Vec<PyUnicode>> {
    Ok(parse_list(value)
        .iter()
        .map(|v| PyUnicode::new(py, &v))
        .collect())
}

fn errors_to_str_vec(errors: Vec<configloader::error::Error>) -> Vec<Str> {
    errors
        .into_iter()
        .map(|err| Str::from(format!("{}", err)))
        .collect()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::collections::HashMap;
use std::sync::Arc;

use ::copytrace::GitCopyTrace;
use ::types::HgId;
use async_runtime::try_block_unless_interrupted as block_on;
use configmodel::Config;
use copytrace::ContentSimilarityRenameFinder;
use copytrace::CopyTrace;
use copytrace::DagCopyTrace;
use copytrace::MetadataRenameFinder;
use copytrace::RenameFinder;
use cpython::*;
use cpython_ext::convert::ImplInto;
use cpython_ext::convert::Serde;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use dag::DagAlgorithm;
use dag::Vertex;
use parking_lot::Mutex;
use storemodel::ReadFileContents;
use storemodel::ReadRootTreeIds;
use storemodel::TreeFormat;
use storemodel::TreeStore;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "copytrace"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<gitcopytrace>(py)?;
    m.add_class::<dagcopytrace>(py)?;
    Ok(m)
}

py_class!(pub class gitcopytrace |py| {
    data inner: Arc<Mutex<GitCopyTrace>>;

    def __new__(_cls, gitdir: &PyPath) -> PyResult<Self> {
        let copytrace = GitCopyTrace::open(gitdir.as_path()).map_pyerr(py)?;
        Self::create_instance(py, Arc::new(Mutex::new(copytrace)))
    }

    /// Find copies between old and new commits, the result is a {newpath: oldpath} map.
    def findcopies(
        &self, oldnode: Serde<HgId>, newnode: Serde<HgId>
    ) -> PyResult<HashMap<String, String>> {
        let copytrace = self.inner(py).lock();
        let copies = copytrace.find_copies(oldnode.0, newnode.0).map_pyerr(py)?;
        Ok(copies)
    }
});

py_class!(pub class dagcopytrace |py| {
    data inner: Arc<DagCopyTrace>;

    def __new__(
        _cls,
        root_tree_reader: ImplInto<Arc<dyn ReadRootTreeIds + Send + Sync>>,
        tree_store: ImplInto<Arc<dyn TreeStore + Send + Sync>>,
        file_reader: ImplInto<Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync>>,
        dag: ImplInto<Arc<dyn DagAlgorithm + Send + Sync>>,
        config: ImplInto<Arc<dyn Config + Send + Sync>>,
    ) -> PyResult<Self> {
        let root_tree_reader = root_tree_reader.into();
        let tree_store = tree_store.into();
        let config = config.into();
        let rename_finder: Arc<dyn RenameFinder + Send + Sync> = match tree_store.format() {
            TreeFormat::Hg => Arc::new(MetadataRenameFinder::new(file_reader.into(), config).map_pyerr(py)?),
            TreeFormat::Git => Arc::new(ContentSimilarityRenameFinder::new(file_reader.into(), config).map_pyerr(py)?),
        };
        let dag = dag.into();

        let copytrace = DagCopyTrace::new(
            root_tree_reader,
            tree_store,
            rename_finder,
            dag,
        ).map_pyerr(py)?;
        Self::create_instance(py, Arc::new(copytrace))
    }

    /// trace_rename(src: node, dst: node, src_path: str) -> Optional[str].
    /// Find the renamed path in `dst` that is from the `src_path` in `src` commit.
    def trace_rename(
        &self,
        src: PyBytes,
        dst: PyBytes,
        src_path: PyPathBuf,
    ) -> PyResult<Option<String>> {
        let src = Vertex::copy_from(src.data(py));
        let dst = Vertex::copy_from(dst.data(py));
        let src_path = src_path.to_repo_path_buf().map_pyerr(py)?;
        let path = block_on(self.inner(py).trace_rename(src, dst, src_path)).map_pyerr(py)?;
        Ok(path.map(|v| v.to_string()))
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::sync::Arc;

use anyhow::format_err;
use async_runtime::try_block_unless_interrupted as block_on;
use cpython::*;
use cpython_ext::convert::BytesLike;
use cpython_ext::convert::Serde;
use cpython_ext::ExtractInner;
use cpython_ext::PyCell;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::ResultPyErrExt;
use cpython_ext::Str;
use dag::ops::DagExportCloneData;
use dag::ops::DagImportCloneData;
use dag::ops::DagPersistent;
use dag::ops::Parents;
use dag::ops::ToIdSet;
use dag::CloneData;
use dag::Dag;
use dag::DagAlgorithm;
use dag::Vertex;
use dag::VertexListWithOptions;
use hgcommits::DagCommits;
use hgcommits::DoubleWriteCommits;
use hgcommits::GitSegmentedCommits;
use hgcommits::GraphNode;
use hgcommits::HgCommit;
use hgcommits::HgCommits;
use hgcommits::HybridCommits;
use hgcommits::MemHgCommits;
use hgcommits::RevlogCommits;
use minibytes::Bytes;
use parking_lot::RwLock;
use pyedenapi::PyClient;
use pymetalog::metalog as PyMetaLog;
use storemodel::ReadRootTreeIds;

use crate::dagalgo::dagalgo;
use crate::idmap;
use crate::Names;
use crate::Spans;

py_class!(pub class commits |py| {
    data inner: Arc<RwLock<Box<dyn DagCommits + Send + 'static>>>;

    /// Add a list of commits (node, [parent], text) in-memory.
    def addcommits(&self, commits: Vec<(PyBytes, Vec<PyBytes>, PyBytes)>) -> PyResult<PyNone> {
        let commits: Vec<HgCommit> = commits.into_iter().map(|(node, parents, raw_text)| {
            let vertex = node.data(py).to_vec().into();
            let parents = parents.into_iter().map(|p| p.data(py).to_vec().into()).collect();
            let raw_text = raw_text.data(py).to_vec().into();
            HgCommit { vertex, parents, raw_text }
        }).collect();
        let mut inner = self.inner(py).write();
        block_on(inner.add_commits(&commits)).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Add a list of graph nodes (node, [parent]) in-memory.
    /// This is only supported by backends with lazy commit message support.
    def addgraphnodes(&self, commits: Vec<(PyBytes, Vec<PyBytes>)>) -> PyResult<PyNone> {
        let graph_nodes: Vec<GraphNode> = commits.into_iter().map(|(node, parents)| {
            let vertex = node.data(py).to_vec().into();
            let parents = parents.into_iter().map(|p| p.data(py).to_vec().into()).collect();
            GraphNode { vertex, parents }
        }).collect();
        let mut inner = self.inner(py).write();
        block_on(inner.add_graph_nodes(&graph_nodes)).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Flush in-memory commit data and graph to disk.
    /// `masterheads` is a hint about what parts belong to the "master" group.
    def flush(&self, masterheads: Vec<PyBytes>) -> PyResult<PyNone> {
        let heads = masterheads.into_iter().map(|h| h.data(py).to_vec().into()).collect::<Vec<_>>();
        let mut inner = self.inner(py).write();
        block_on(inner.flush(&heads)).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Flush in-memory commit data to disk.
    /// For the revlog backend, this also write the commit graph to disk.
    /// For the lazy commit hash backend, this also writes the commit hashes.
    def flushcommitdata(&self) -> PyResult<PyNone> {
        let mut inner = self.inner(py).write();
        block_on(inner.flush_commit_data()).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Import clone data (inside PyCell) and flush.
    def importclonedata(&self, data: PyCell) -> PyResult<PyNone> {
        let data: Box<CloneData<Vertex>> = data.take(py).ok_or_else(|| format_err!("Data is not CloneData")).map_pyerr(py)?;
        let mut inner = self.inner(py).write();
        block_on(inner.import_clone_data(*data)).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Import pull data (inside PyCell) and flush.
    /// Returns (commit_count, segment_count) on success.
    def importpulldata(&self, data: PyCell, heads: Serde<VertexListWithOptions>) -> PyResult<(u64, usize)> {
        let data: Box<CloneData<Vertex>> = data.take(py).ok_or_else(|| format_err!("Data is not CloneData")).map_pyerr(py)?;
        let commits = data.flat_segments.vertex_count();
        let segments = data.flat_segments.segment_count();
        let mut inner = self.inner(py).write();
        block_on(inner.import_pull_data(*data, &heads.0)).map_pyerr(py)?;
        Ok((commits, segments))
    }

    /// Strip commits. ONLY used to make LEGACY TESTS running.
    /// Fails if called in a non-test environment.
    /// New tests should avoid depending on `strip`.
    def strip(&self, set: Names) -> PyResult<PyNone> {
        let mut inner = self.inner(py).write();
        block_on(inner.strip_commits(set.0)).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Lookup the raw text of a commit by binary commit hash.
    def getcommitrawtext(&self, node: PyBytes) -> PyResult<Option<PyBytes>> {
        let vertex = node.data(py).to_vec().into();
        let inner = self.inner(py).read();
        let optional_bytes = block_on(inner.get_commit_raw_text(&vertex)).map_pyerr(py)?;
        Ok(optional_bytes.map(|bytes| PyBytes::new(py, bytes.as_ref())))
    }

    /// Lookup the raw texts by a list of binary commit hashes.
    def getcommitrawtextlist(&self, nodes: Vec<BytesLike<Vertex>>) -> PyResult<Vec<BytesLike<Bytes>>>
    {
        let vertexes: Vec<Vertex> = nodes.into_iter().map(|b| b.0).collect();
        let inner = self.inner(py).read();
        let texts = block_on(inner.get_commit_raw_text_list(&vertexes)).map_pyerr(py)?;
        Ok(texts.into_iter().map(BytesLike).collect())
    }

    /// Convert Set to IdSet. For compatibility with legacy code only.
    def torevs(&self, set: Names) -> PyResult<Spans> {
        // Attempt to use IdMap bound to `set` if possible for performance.
        let id_map = match set.0.hints().id_map() {
            Some(map) => map,
            None => self.inner(py).read().id_map_snapshot().map_pyerr(py)?,
        };
        Ok(Spans(block_on(id_map.to_id_set(&set.0)).map_pyerr(py)?))
    }

    /// Convert IdSet to Set. For compatibility with legacy code only.
    def tonodes(&self, set: Spans) -> PyResult<Names> {
        let inner = self.inner(py).read();
        Ok(Names(inner.to_set(&set.0).map_pyerr(py)?))
    }

    /// Obtain the read-only dagalgo object that supports various DAG algorithms.
    def dagalgo(&self) -> PyResult<dagalgo> {
        dagalgo::from_arc_dag(py, self.inner(py).read().dag_snapshot().map_pyerr(py)?)
    }

    /// Obtain the read-only object that can do hex prefix lookup and convert
    /// between binary commit hashes and integer Ids.
    def idmap(&self) -> PyResult<idmap::idmap> {
        idmap::idmap::from_arc_idmap(py, self.inner(py).read().id_map_snapshot().map_pyerr(py)?)
    }

    /// Name of the backend used for DAG algorithms.
    def algorithmbackend(&self) -> PyResult<Str> {
        let inner = self.inner(py).read();
        Ok(inner.algorithm_backend().to_string().into())
    }

    /// Describe the backend.
    def describebackend(&self) -> PyResult<Str> {
        let inner = self.inner(py).read();
        Ok(inner.describe_backend().into())
    }

    /// Explain internal data.
    def explaininternals(&self, out: PyObject) -> PyResult<PyNone> {
        // This function takes a 'out' parameter so it can work with pager
        // and output progressively.
        let inner = self.inner(py).read();
        let mut out = cpython_ext::wrap_pyio(out);
        inner.explain_internals(&mut out).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// checkuniversalids() -> [id]
    ///
    /// Check for missing universal ids.
    /// Returns missing ids. A valid lazy graph should return an empty list.
    /// See document in the dag crate for details.
    def checkuniversalids(&self) -> PyResult<Vec<u64>> {
        let inner = self.inner(py).read();
        let ids = block_on(inner.check_universal_ids()).map_pyerr(py)?;
        Ok(ids.into_iter().map(|i| i.0).collect())
    }

    /// checksegments() -> [str]
    ///
    /// Check for problems of segments such as cycles or wrong flags.
    /// Returns a list of human-readable messages indicating problems.
    /// A valid graph should return an empty list.
    def checksegments(&self) -> PyResult<Vec<String>> {
        let inner = self.inner(py).read();
        let problems = block_on(inner.check_segments()).map_pyerr(py)?;
        Ok(problems)
    }

    /// checkisomorphicgraph(inner, heads) -> [str]
    ///
    /// Check for problems of segments such as cycles or wrong flags.
    /// Returns a list of human-readable messages indicating problems.
    /// A valid graph should return an empty list.
    def checkisomorphicgraph(&self, other: commits, heads: Names) -> PyResult<Vec<String>> {
        let inner = self.inner(py).read();
        let other = other.inner(py).read().dag_snapshot().map_pyerr(py)?;
        let heads = heads.0;
        let problems = block_on(inner.check_isomorphic_graph(&other, heads)).map_pyerr(py)?;
        Ok(problems)
    }

    /// updatereferences(metalog)
    ///
    /// Update commit references to match metalog. Useful when metalog is not the
    /// source of truth of commit references (ex. using git references as source
    /// of truth).
    def updatereferences(&self, metalog: PyMetaLog) -> PyResult<PyNone> {
        let meta = metalog.metalog_rwlock(py);
        let mut inner = self.inner(py).write();
        inner.update_references_to_match_metalog(&meta.read()).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// migratesparsesegments(src, dst, heads=[]).
    ///
    /// Load full Dag from src directory, migrate a subset of dag to dst directory.
    ///
    /// If heads is empty, then only the master group and IdMap that are essential
    /// are migrated. If heads is not empty, specified vertexes and their ancestors
    /// are also migrated.
    ///
    /// This can be used to create a commit backend with lazy commit hashes
    /// from an existing repo.
    @staticmethod
    def migratesparsesegments(src: &PyPath, dst: &PyPath, heads: Vec<PyBytes> = Vec::new()) -> PyResult<PyNone> {
        let src = Dag::open(src.as_path()).map_pyerr(py)?;
        let mut dst = Dag::open(dst.as_path()).map_pyerr(py)?;
        let clone_data = py.allow_threads(|| block_on(src.export_clone_data())).map_pyerr(py)?;
        py.allow_threads(|| block_on(dst.import_clone_data(clone_data))).map_pyerr(py)?;

        // Also migrate specified heads and their ancestors.
        let heads: Vec<Vertex> = heads.into_iter().map(|h| h.data(py).to_vec().into()).collect::<Vec<_>>();
        let src_snapshot = src.dag_snapshot().map_pyerr(py)?;
        dst.set_remote_protocol(Arc::new(src));
        let src_dag: &dyn Parents = &src_snapshot;
        let heads = VertexListWithOptions::from(heads);
        py.allow_threads(|| block_on(dst.add_heads_and_flush(src_dag, &heads))).map_pyerr(py)?;

        Ok(PyNone)
    }

    /// Construct `commits` from a revlog (`00changelog.i` and `00changelog.d`).
    @staticmethod
    def openrevlog(dir: &PyPath) -> PyResult<Self> {
        let inner = RevlogCommits::new(dir.as_path()).map_pyerr(py)?;
        Self::from_commits(py, inner)
    }

    /// Construct `commits` from a segmented changelog + hgcommits directory.
    @staticmethod
    def opensegments(segmentsdir: &PyPath, commitsdir: &PyPath) -> PyResult<Self> {
        let inner = HgCommits::new(segmentsdir.as_path(), commitsdir.as_path()).map_pyerr(py)?;
        Self::from_commits(py, inner)
    }

    /// Migrate from revlog to segmented changelog (full IdMap).
    ///
    /// This does not migrate commit texts and therefore only useful for
    /// doublewrite backend.
    @staticmethod
    def migraterevlogtosegments(revlogdir: &PyPath, segmentsdir: &PyPath, commitsdir: &PyPath, master: Names) -> PyResult<PyNone> {
        let revlog = RevlogCommits::new(revlogdir.as_path()).map_pyerr(py)?;
        let mut segments = HgCommits::new(segmentsdir.as_path(), commitsdir.as_path()).map_pyerr(py)?;
        py.allow_threads(|| block_on(segments.import_dag(revlog, master.0))).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Construct "double write" `commits` from both revlog and segmented
    /// changelog.
    @staticmethod
    def opendoublewrite(revlogdir: &PyPath, segmentsdir: &PyPath, commitsdir: &PyPath) -> PyResult<Self> {
        let inner = DoubleWriteCommits::new(revlogdir.as_path(), segmentsdir.as_path(), commitsdir.as_path()).map_pyerr(py)?;
        Self::from_commits(py, inner)
    }

    /// Construct `commits` from a revlog + segmented changelog + hgcommits + edenapi hybrid.
    ///
    /// This is similar to doublewrite backend, except that commit text fallback is edenapi,
    /// not revlog, despite the revlog might have the data.
    ///
    /// If lazyhash is True, enable lazy commit hashes or EdenAPI.
    ///
    /// If lazyhashdir is set, enable lazy commit hashes backed by the given segments dir
    /// (for testing).
    @staticmethod
    def openhybrid(
        revlogdir: Option<&PyPath>, segmentsdir: &PyPath, commitsdir: &PyPath, edenapi: PyClient,
        lazyhash: bool = false, lazyhashdir: Option<&PyPath> = None
    ) -> PyResult<Self> {
        let client = edenapi.extract_inner(py);
        let mut inner = HybridCommits::new(
            revlogdir.map(|d| d.as_path()),
            segmentsdir.as_path(),
            commitsdir.as_path(),
            client,
        ).map_pyerr(py)?;
        if let Some(dir) = lazyhashdir {
            inner.enable_lazy_commit_hashes_from_local_segments( dir.as_path()).map_pyerr(py)?;
        } else if lazyhash {
            inner.enable_lazy_commit_hashes();
        }
        Self::from_commits(py, inner)
    }

    /// Construct "git segmented" `commits` from a git repo and segmented
    /// changelog.
    @staticmethod
    def opengitsegments(gitdir: &PyPath, segmentsdir: &PyPath, metalog: PyMetaLog) -> PyResult<Self> {
        let inner = py.allow_threads(|| GitSegmentedCommits::new(gitdir.as_path(), segmentsdir.as_path())).map_pyerr(py)?;
        let meta = metalog.metalog_rwlock(py);
        inner.git_references_to_metalog(&mut meta.write()).map_pyerr(py)?;
        Self::from_commits(py, inner)
    }

    /// Construct a private, empty `commits` object backed by the memory.
    /// `flush` does nothing for this type of object.
    @staticmethod
    def openmemory() -> PyResult<Self> {
        let inner = MemHgCommits::new().map_pyerr(py)?;
        Self::from_commits(py, inner)
    }
});

impl commits {
    /// Create a `commits` Python object from a Rust struct.
    pub fn from_commits(py: Python, commits: impl DagCommits + Send + 'static) -> PyResult<Self> {
        Self::create_instance(py, Arc::new(RwLock::new(Box::new(commits))))
    }

    pub(crate) fn to_read_root_tree_nodes(
        &self,
        py: Python,
    ) -> Arc<dyn ReadRootTreeIds + Send + Sync> {
        let inner = self.inner(py).read();
        inner.to_dyn_read_root_tree_ids()
    }

    pub fn get_inner(&self, py: Python) -> Arc<RwLock<Box<dyn DagCommits + Send + 'static>>> {
        self.inner(py).clone()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::VecDeque;
use std::sync::Arc;

use async_runtime::try_block_unless_interrupted as block_on;
use cpython::*;
use cpython_ext::convert::ImplInto;
use cpython_ext::convert::Serde;
use cpython_ext::ResultPyErrExt;
use cpython_ext::Str;
use dag::DagAlgorithm;
use dag::IdSegment;
use dag::Set;
use dag::Vertex;

use crate::Names;

py_class!(pub class dagalgo |py| {
    data dag: Arc<dyn DagAlgorithm + Send + Sync + 'static>;

    /// Sort a set.
    def sort(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).sort(&set.0)).map_pyerr(py)?))
    }

    def all(&self) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).all()).map_pyerr(py)?))
    }

    /// Return a set including vertexes in the master group.
    def mastergroup(&self) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).master_group()).map_pyerr(py)?))
    }

    /// Return a set including vertexes pending in memory.
    def dirty(&self) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).dirty()).map_pyerr(py)?))
    }

    /// Calculate all ancestors reachable from the set.
    def ancestors(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).ancestors(set.0)).map_pyerr(py)?))
    }

    /// Calculate all ancestors reachable from the set, following only first parents.
    def firstancestors(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).first_ancestors(set.0)).map_pyerr(py)?))
    }

    /// Calculate parents of the given set.
    def parents(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).parents(set.0)).map_pyerr(py)?))
    }

    /// Get parents of a single `name`. Preserve the order.
    def parentnames(&self, name: PyBytes) -> PyResult<Vec<PyBytes>> {
        let parents = block_on(self.dag(py).parent_names(Vertex::copy_from(name.data(py)))).map_pyerr(py)?;
        Ok(parents.into_iter().map(|name| PyBytes::new(py, name.as_ref())).collect())
    }

    /// The `n`-th first ancestor of `x`
    def firstancestornth(&self, x: PyBytes, n: u64) -> PyResult<Option<PyBytes>> {
        let result = block_on(self.dag(py).first_ancestor_nth(Vertex::copy_from(x.data(py)), n)).map_pyerr(py)?;
        Ok(result.map(|v| PyBytes::new(py, v.as_ref())))
    }

    /// Calculate parents of the given set.
    def heads(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).heads(set.0)).map_pyerr(py)?))
    }

    /// Calculate children of the given set.
    def children(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).children(set.0)).map_pyerr(py)?))
    }

    /// Calculate roots of the given set.
    def roots(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).roots(set.0)).map_pyerr(py)?))
    }

    /// Calculate merges of the given set.
    def merges(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).merges(set.0)).map_pyerr(py)?))
    }

    /// Calculate one greatest common ancestor of a set.
    /// If there are multiple greatest common ancestors, pick an arbitrary one.
    def gcaone(&self, set: Names) -> PyResult<Option<PyBytes>> {
        Ok(block_on(self.dag(py).gca_one(set.0)).map_pyerr(py)?.map(|name| PyBytes::new(py, name.as_ref())))
    }

    /// Calculate all greatest common ancestors of a set.
    def gcaall(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).gca_all(set.0)).map_pyerr(py)?))
    }

    /// Calculate all common ancestors of a set.
    def commonancestors(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).common_ancestors(set.0)).map_pyerr(py)?))
    }

    /// Check if `ancestor` is an ancestor of `descendant`.
    def isancestor(&self, ancestor: PyBytes, descendant: PyBytes) -> PyResult<bool> {
        let ancestor = Vertex::copy_from(ancestor.data(py));
        let descendant = Vertex::copy_from(descendant.data(py));
        block_on(self.dag(py).is_ancestor(ancestor, descendant)).map_pyerr(py)
    }

    /// Calculate `heads(ancestors(set))`.
    /// This is faster than calling `heads` and `ancestors` individually.
    def headsancestors(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).heads_ancestors(set.0)).map_pyerr(py)?))
    }

    /// Calculate `roots::heads`.
    def range(&self, roots: Names, heads: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).range(roots.0, heads.0)).map_pyerr(py)?))
    }

    /// Calculate `reachable % unreachable`.
    def only(&self, reachable: Names, unreachable: Names) -> PyResult<Names> {
        let result = block_on(self.dag(py).only(reachable.0, unreachable.0)).map_pyerr(py)?;
        Ok(Names(result))
    }

    /// Calculate `reachable % unreachable`, and `::unreachable`.
    def onlyboth(&self, reachable: Names, unreachable: Names) -> PyResult<(Names, Names)> {
        let (reachable_ancestors, unreachable_ancestors) =
            block_on(self.dag(py).only_both(reachable.0, unreachable.0)).map_pyerr(py)?;
        Ok((Names(reachable_ancestors), Names(unreachable_ancestors)))
    }

    /// Calculate descendants of the given set.
    def descendants(&self, set: Names) -> PyResult<Names> {
        Ok(Names(block_on(self.dag(py).descendants(set.0)).map_pyerr(py)?))
    }

    /// Calculate `roots & (heads | parents(only(heads, roots & ancestors(heads))))`.
    def reachableroots(&self, roots: Names, heads: Names) -> PyResult<Names> {
        let result = block_on(self.dag(py).reachable_roots(roots.0, heads.0)).map_pyerr(py)?;
        Ok(Names(result))
    }

    /// Return true if the vertexes are lazily fetched from remote.
    def isvertexlazy(&self) -> PyResult<bool> {
        Ok(self.dag(py).is_vertex_lazy())
    }

    /// Beautify the graph so `render` might look better.
    def beautify(&self, mainbranch: Option<Names> = None) -> PyResult<Self> {
        let dag = block_on(self.dag(py).beautify(mainbranch.map(|h| h.0))).map_pyerr(py)?;
        Self::from_dag(py, dag)
    }

    /// Extract a subdag from the graph.
    def subdag(&self, set: Names) -> PyResult<Self> {
        let dag = block_on(self.dag(py).subdag(set.0)).map_pyerr(py)?;
        Self::from_dag(py, dag)
    }

    /// Render the graph into an ASCII string.
    def render(&self, getmessage: Option<PyObject> = None) -> PyResult<Str> {
        let get_message = move |vertex: &Vertex| -> Option<String> {
            if let Some(getmessage) = &getmessage {
                if getmessage.is_callable(py) {
                    if let Ok(message) = getmessage.call(py, (PyBytes::new(py, vertex.as_ref()),), None) {
                        if let Ok(message) = message.extract::<String>(py) {
                            return Some(message)
                        }
                    }
                }
            }
            None
        };
        let dag = self.dag(py);
        Ok(dag::render::render_namedag(dag.as_ref(), get_message).map_pyerr(py)?.into())
    }

    /// segments(nameset, maxlevel=255) -> [segment]
    /// Get the segments covering the set with specified maximum level.
    def segments(&self, set: ImplInto<Set>, maxlevel: u8 = 255) -> PyResult<Serde<VecDeque<IdSegment>>> {
        let set = set.into();
        let (id_set, _id_map) = match set.to_id_set_and_id_map_in_o1() {
            Some(v) => v,
            None => {
                let msg = format!("{:?} cannot be converted to IdSet", &set);
                return Err(PyErr::new::<exc::ValueError, _>(py, msg));
            }
        };
        let id_dag = self.dag(py).id_dag_snapshot().map_pyerr(py)?;
        let segments = id_dag.id_set_to_id_segments_with_max_level(&id_set, maxlevel).map_pyerr(py)?;
        Ok(Serde(segments))
    }
});

impl dagalgo {
    pub fn from_dag(py: Python, dag: impl DagAlgorithm + Send + Sync + 'static) -> PyResult<Self> {
        Self::create_instance(py, Arc::new(dag))
    }

    pub fn from_arc_dag(py: Python, dag: Arc<dyn DagAlgorithm + Send + Sync>) -> PyResult<Self> {
        Self::create_instance(py, dag)
    }

    pub fn to_dag_algorithm(&self, py: Python) -> Arc<dyn DagAlgorithm + Send + Sync> {
        self.dag(py).clone()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::sync::Arc;

use async_runtime::try_block_unless_interrupted as block_on;
use cpython::*;
use cpython_ext::ResultPyErrExt;
use dag::ops::IdConvert;
use dag::Id;
use dag::Vertex;

// Mercurial's special case. -1 maps to (b"\0" * 20)
pub(crate) const NULL_NODE: [u8; 20] = [0u8; 20];

py_class!(pub class idmap |py| {
    data map: Arc<dyn IdConvert + Send + Sync + 'static>;

    /// Translate id to node.
    def id2node(&self, id: i64) -> PyResult<PyBytes> {
        if id == -1 {
            Ok(PyBytes::new(py, &NULL_NODE))
        } else {
            let v = block_on(self.map(py).vertex_name(Id(id as u64))).map_pyerr(py)?;
            Ok(PyBytes::new(py, v.as_ref()))
        }
    }

    /// Translate node to id.
    def node2id(&self, node: PyBytes) -> PyResult<i64> {
        let node = node.data(py);
        if node == &NULL_NODE {
            Ok(-1)
        } else {
            let id = block_on(self.map(py).vertex_id(Vertex::copy_from(node))).map_pyerr(py)?;
            Ok(id.0 as i64)
        }
    }

    /// Translate id to node in batch.
    def id2nodebatch(&self, ids: Vec<i64>) -> PyResult<Vec<PyBytes>> {
        let non_negative_ids: Vec<Id> = ids.iter().filter_map(|&i| if i >= 0 {
            Some(Id(i as _))
        } else {
            None
        }).collect();
        let nodes = block_on(self.map(py).vertex_name_batch(&non_negative_ids)).map_pyerr(py)?;
        let mut result = Vec::with_capacity(ids.len());
        let mut iter = nodes.into_iter();
        for id in ids {
            if id == -1 {
                result.push(PyBytes::new(py, &NULL_NODE));
            } else if id >= 0 {
                if let Some(node) = iter.next() {
                    let node = node.map_pyerr(py)?;
                    result.push(PyBytes::new(py, node.as_ref()));
                } else {
                    let msg = "vertex_name_batch does not return enough number of results".to_string();
                    return Err(PyErr::new::<exc::ValueError, _>(py, msg));
                }
            } else {
                return Err(PyErr::new::<exc::ValueError, _>(py, format!("invalid id: {}", id)));
            }
        }
        if iter.next().is_some() {
            let msg = "vertex_name_batch returned more results than expected".to_string();
            return Err(PyErr::new::<exc::ValueError, _>(py, msg));
        }
        Ok(result)
    }

    /// Filter out nodes not in the IdMap.
    /// (nodes, inverse=False, local=False) -> nodes.
    ///
    /// Use batching internally. Faster than checking `__contains__`
    /// one by one.
    ///
    /// If inverse is set to True, return missing nodes instead of
    /// present nodes.
    ///
    /// If local is set to True, avoid contacting the remote server.
    def filternodes(&self, nodes: Vec<PyBytes>, inverse: bool = false, local: bool = false) -> PyResult<Vec<PyBytes>> {
        let map = self.map(py);
        let mut vertexes: Vec<_> = nodes.iter().map(|n| Vertex::copy_from(n.data(py))).collect();
        if local {
            if inverse {
                return Err(PyErr::new::<exc::ValueError, _>(py, "inverse and local cannot be both True"));
            }
            let contains = block_on(map.contains_vertex_name_locally(&vertexes)).map_pyerr(py)?;
            assert_eq!(contains.len(), contains.len());
            vertexes = vertexes
                .into_iter()
                .zip(contains)
                .filter_map(|(v, c)| if c { Some(v) } else { None }).collect();
            let result = vertexes.into_iter().map(|v| PyBytes::new(py, v.as_ref())).collect();
            return Ok(result);
        };

        let ids = block_on(map.vertex_id_batch(&vertexes)).map_pyerr(py)?;
        let mut result = Vec::with_capacity(nodes.len());
        for (node, id) in nodes.into_iter().zip(ids) {
            let present = match id {
                Err(dag::Error::VertexNotFound(_)) => false,
                Ok(_) => true,
                Err(e) => return Err(e).map_pyerr(py),
            };
            match (present, inverse) {
                (true, false) | (false, true) => result.push(node),
                _ => {}
            }
        }
        Ok(result)
    }

    /// Lookup nodes by hex prefix.
    def hexprefixmatch(&self, prefix: PyObject, limit: usize = 5) -> PyResult<Vec<PyBytes>> {
        let prefix: Vec<u8> = if let Ok(bytes) = prefix.extract::<PyBytes>(py) {
            bytes.data(py).to_vec()
        } else {
            prefix.extract::<String>(py)?.as_bytes().to_vec()
        };
        if !prefix.iter().all(|&b| (b >= b'0' && b <= b'9') || (b >= b'a' && b <= b'f')) {
            // Invalid hex prefix. Pretend nothing matches.
            return Ok(Vec::new())
        }
        let map = self.map(py).clone();
        let vertexes = async_runtime::block_on(async move {
            map.vertexes_by_hex_prefix(&prefix, limit).await
        });
        let nodes = vertexes
            .map_pyerr(py)?
            .into_iter()
            .map(|s| PyBytes::new(py, s.as_ref()))
            .collect();
        Ok(nodes)
    }

    def __contains__(&self, node: PyBytes) -> PyResult<bool> {
        let node = node.data(py);
        if node == &NULL_NODE {
            Ok(true)
        } else {
            let name = Vertex::copy_from(node);
            Ok(block_on(self.map(py).contains_vertex_name(&name)).map_pyerr(py)?)
        }
    }
});

impl idmap {
    pub(crate) fn from_arc_idmap(
        py: Python,
        map: Arc<dyn IdConvert + Send + Sync>,
    ) -> PyResult<Self> {
        Self::create_instance(py, map)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Support `ImplInto` from cpython-ext.

use cpython::*;
use cpython_ext::convert::register_into;

use crate::commits::commits;
use crate::dagalgo::dagalgo;
use crate::nameset::nameset;

pub(crate) fn register(py: Python) {
    register_into(py, |py, c: commits| c.to_read_root_tree_nodes(py));
    register_into(py, |py, s: nameset| s.to_native_set(py));
    register_into(py, |py, d: dagalgo| d.to_dag_algorithm(py));
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use cpython::*;
use cpython_ext::PyPath;
use cpython_ext::ResultPyErrExt;
use dag::Repair;

pub mod commits;
pub mod dagalgo;
pub mod idmap;
mod impl_into;
pub mod nameset;
mod parents;
pub mod spanset;

pub use nameset::Names;
pub use spanset::spans;
pub use spanset::Spans;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "dag"].join(".");
    let m = PyModule::new(py, &name)?;

    // main entry type
    m.add_class::<commits::commits>(py)?;

    // "traits"
    m.add_class::<dagalgo::dagalgo>(py)?;
    m.add_class::<idmap::idmap>(py)?;
    m.add_class::<parents::parents>(py)?;

    // smartset-like types
    m.add_class::<nameset::nameset>(py)?;
    m.add_class::<spanset::spans>(py)?;

    // maximum Id
    m.add(py, "MAX_ID", dag::Id::MAX.0)?;

    // Explain bytes in indexedlog IdDag. For troubleshotting purpose.
    m.add(
        py,
        "describebytes",
        py_fn!(py, describe_bytes(bytes: PyBytes)),
    )?;

    // Repair NameDag storage.
    m.add(py, "repair", py_fn!(py, repair(path: &PyPath)))?;

    impl_into::register(py);

    Ok(m)
}

fn describe_bytes(py: Python, bytes: PyBytes) -> PyResult<String> {
    let data = bytes.data(py);
    Ok(dag::describe_indexedlog_entry(data))
}

fn repair(py: Python, path: &PyPath) -> PyResult<String> {
    dag::NameDag::repair(path).map_pyerr(py)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::cell::RefCell;
use std::collections::HashMap;

use async_runtime::try_block_unless_interrupted as block_on;
use cpython::*;
use cpython_ext::AnyhowResultExt;
use cpython_ext::ResultPyErrExt;
use dag::nameset::hints::Flags;
use dag::nameset::hints::Hints;
use dag::nameset::BoxVertexStream;
use dag::Set;
use dag::Vertex;
use futures::stream::StreamExt;

use crate::dagalgo::dagalgo;
use crate::idmap::NULL_NODE;
use crate::parents::parents;

/// A wrapper around [`Set`] with Python integration added.
///
/// Differences from the `py_class` version:
/// - Auto converts from a wider range of Python types - not just nameset, but
///   also List[bytes], and Generator[bytes].
/// - Pure Rust. No need to take the Python GIL to create `Names`.
pub struct Names(pub Set);

// A wrapper around [`Set`].
py_class!(pub class nameset |py| {
    data inner: Set;

    def __new__(_cls, obj: PyObject) -> PyResult<Self> {
        Ok(Names::extract(py, &obj)?.to_py_object(py))
    }

    def __contains__(&self, name: PyBytes) -> PyResult<bool> {
        let name = Vertex::copy_from(name.data(py));
        Ok(block_on(self.inner(py).contains(&name)).map_pyerr(py)?)
    }

    def __len__(&self) -> PyResult<usize> {
        Ok(block_on(self.inner(py).count()).map_pyerr(py)?)
    }

    def __repr__(&self) -> PyResult<String> {
        Ok(format!("{:?}", self.inner(py)))
    }

    def __add__(lhs, rhs) -> PyResult<Names> {
        let lhs = Names::extract(py, lhs)?;
        let rhs = Names::extract(py, rhs)?;
        Ok(Names(lhs.0.union(&rhs.0)))
    }

    def __or__(lhs, rhs) -> PyResult<Names> {
        let lhs = Names::extract(py, lhs)?;
        let rhs = Names::extract(py, rhs)?;
        Ok(Names(lhs.0.union(&rhs.0)))
    }

    def __and__(lhs, rhs) -> PyResult<Names> {
        let lhs = Names::extract(py, lhs)?;
        let rhs = Names::extract(py, rhs)?;
        Ok(Names(lhs.0.intersection(&rhs.0)))
    }

    def __sub__(lhs, rhs) -> PyResult<Names> {
        let lhs = Names::extract(py, lhs)?;
        let rhs = Names::extract(py, rhs)?;
        Ok(Names(lhs.0.difference(&rhs.0)))
    }

    def __iter__(&self) -> PyResult<nameiter> {
        self.iter(py)
    }

    def __getitem__(&self, n: i64) -> PyResult<Option<PyBytes>> {
        let n = if n < 0 {
            ((self.__len__(py)? as i64) + n) as u64
        } else {
            n as u64
        };
        let inner = self.inner(py);
        let set = inner.skip(n);
        Ok(block_on(set.first()).map_pyerr(py)?.map(|name| PyBytes::new(py, name.as_ref())))
    }

    def iterrev(&self) -> PyResult<nameiter> {
        let iter = block_on(self.inner(py).clone().iter_rev()).map_pyerr(py)?;
        let iter: RefCell<BoxVertexStream> = RefCell::new(iter);
        nameiter::create_instance(py, iter)
    }

    def iter(&self) -> PyResult<nameiter> {
        let iter = block_on(self.inner(py).clone().iter()).map_pyerr(py)?;
        let iter: RefCell<BoxVertexStream> = RefCell::new(iter);
        nameiter::create_instance(py, iter)
    }

    def first(&self) -> PyResult<Option<PyBytes>> {
        Ok(block_on(self.inner(py).first()).map_pyerr(py)?.map(|name| PyBytes::new(py, name.as_ref())))
    }

    def last(&self) -> PyResult<Option<PyBytes>> {
        Ok(block_on(self.inner(py).last()).map_pyerr(py)?.map(|name| PyBytes::new(py, name.as_ref())))
    }

    /// Obtain an optional dag bound to this set.
    def dag(&self) -> PyResult<Option<dagalgo>> {
        match self.inner(py).dag() {
            Some(dag) => dagalgo::from_arc_dag(py, dag).map(Some),
            None => Ok(None),
        }
    }

    /// Obtain "parents" information on this set.
    /// "parents" is Callable[[bytes], List[bytes]].
    def toparents(&self) -> PyResult<Option<parents>> {
        let dag_parents = block_on(self.inner(py).to_parents()).map_pyerr(py)?;
        match dag_parents {
            None => Ok(None),
            Some(dag_parents) => Ok(Some(parents::create_instance(py, Box::new(dag_parents))?))
        }
    }

    /// Convert the set to a plain static set.
    def flatten(&self) -> PyResult<Names> {
        let inner = self.inner(py);
        let set = block_on(inner.flatten()).map_pyerr(py)?;
        Ok(Names(set))
    }

    /// Skip the first `n` items.
    def skip(&self, n: u64) -> PyResult<Names> {
        let inner = self.inner(py);
        let set = inner.skip(n);
        Ok(Names(set))
    }

    /// Take the first `n` items.
    def take(&self, n: u64) -> PyResult<Names> {
        let inner = self.inner(py);
        let set = inner.take(n);
        Ok(Names(set))
    }

    def hints(&self) -> PyResult<HashMap<&'static str, PyObject>> {
        let mut result = HashMap::new();
        let hints = self.inner(py).hints();
        if let Some(id) = hints.min_id() {
            result.insert("min", id.0.to_py_object(py).into_object());
        }
        if let Some(id) = hints.max_id() {
            result.insert("max", id.0.to_py_object(py).into_object());
        }
        let flags = hints.flags();
        if flags.contains(Flags::ID_DESC) {
            result.insert("desc", py.True().into_object());
        }
        if flags.contains(Flags::ID_ASC) {
            result.insert("asc", py.True().into_object());
        }
        if flags.contains(Flags::TOPO_DESC) {
            result.insert("topo", py.True().into_object());
        }
        if flags.contains(Flags::EMPTY) {
            result.insert("empty", py.True().into_object());
        }
        if flags.contains(Flags::FULL) {
            result.insert("full", py.True().into_object());
        }
        if flags.contains(Flags::ANCESTORS) {
            result.insert("ancestors", py.True().into_object());
        }
        if let Some(map) = hints.id_map() {
            result.insert("mapid", map.map_id().to_py_object(py).into_object());
        }
        if let Some(dag) = hints.dag() {
            result.insert("dagid", dag.dag_id().to_py_object(py).into_object());
        }
        Ok(result)
    }
});

// A wrapper to [`NameIter`].
py_class!(pub class nameiter |py| {
    data iter: RefCell<BoxVertexStream>;

    def __next__(&self) -> PyResult<Option<PyBytes>> {
        let mut iter = self.iter(py).borrow_mut();
        let next: Option<Vertex> = block_on(async { iter.next().await.transpose() }).map_pyerr(py)?;
        Ok(next.map(|name| PyBytes::new(py, name.as_ref())))
    }

    def __iter__(&self) -> PyResult<nameiter> {
        Ok(self.clone_ref(py))
    }
});

impl<'a> FromPyObject<'a> for Names {
    fn extract(py: Python, obj: &'a PyObject) -> PyResult<Self> {
        // type(obj) is nameset - convert to Names directly.
        if let Ok(pyset) = obj.extract::<nameset>(py) {
            return Ok(Names(pyset.inner(py).clone()));
        }

        // type(obj) is list - convert to StaticSet
        if let Ok(pylist) = obj.extract::<Vec<PyBytes>>(py) {
            let set = Set::from_static_names(pylist.into_iter().filter_map(|name| {
                let data = name.data(py);
                // Skip "nullid" automatically.
                if data == &NULL_NODE[..] {
                    None
                } else {
                    Some(Vertex::copy_from(data))
                }
            }));
            return Ok(Names(set));
        }

        // Others - convert to LazySet.
        // XXX: This makes it possible that async Rust calls into Python!!!
        // Should this be an error?
        let obj = obj.clone_ref(py);
        let iter = PyNameIter::new(py, obj.iter(py)?.into_object())?;
        let set = Set::from_iter(iter, Hints::default());
        Ok(Names(set))
    }
}

/// Similar to `PyIterator`, but without lifetime and has `Vertex` as
/// output type.
struct PyNameIter {
    obj: PyObject,
    errored: bool,
}

impl PyNameIter {
    fn new(py: Python, obj: PyObject) -> PyResult<Self> {
        let _obj = obj.iter(py)?;
        Ok(Self {
            obj,
            errored: false,
        })
    }
}

impl Iterator for PyNameIter {
    type Item = dag::Result<Vertex>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.errored {
            return None;
        }
        (|| -> PyResult<Option<Vertex>> {
            let gil = Python::acquire_gil();
            let py = gil.python();
            let mut iter = self.obj.iter(py)?;
            match iter.next() {
                None => Ok(None),
                Some(Ok(value)) => {
                    let value = value.extract::<PyBytes>(py)?;
                    let data = value.data(py);
                    if data == &NULL_NODE[..] {
                        // Skip "nullid" automatically.
                        self.next().transpose().map_pyerr(py)
                    } else {
                        Ok(Some(Vertex::copy_from(data)))
                    }
                }
                Some(Err(err)) => {
                    self.errored = true;
                    Err(err)
                }
            }
        })()
        .into_anyhow_result()
        .map_err(|e: anyhow::Error| dag::errors::BackendError::Other(e).into())
        .transpose()
    }
}

impl ToPyObject for Names {
    type ObjectType = nameset;

    fn to_py_object(&self, py: Python) -> Self::ObjectType {
        nameset::create_instance(py, self.0.clone()).unwrap()
    }
}

impl nameset {
    /// Convets to native Rust `Set`.
    pub(crate) fn to_native_set(&self, py: Python) -> Set {
        self.inner(py).clone()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use async_runtime::try_block_unless_interrupted as block_on;
use cpython::*;
use cpython_ext::ResultPyErrExt;
use dag::ops::Parents as DagParents;
use dag::Vertex;

// A wrapper around [`DagParents`].
py_class!(pub class parents |py| {
    data inner: Box<dyn DagParents>;

    def __call__(&self, vertex: PyBytes) -> PyResult<Vec<PyBytes>> {
        let vertex = Vertex::copy_from(vertex.data(py));
        let parents: Vec<Vertex> = block_on(self.inner(py).parent_names(vertex)).map_pyerr(py)?;
        let parents: Vec<PyBytes> = parents.into_iter().map(|v| PyBytes::new(py, v.as_ref())).collect();
        Ok(parents)
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::cell::RefCell;

use cpython::*;
use dag::Id;
use dag::IdSet;
use dag::IdSetIter;

/// A wrapper around [`IdSet`] with Python integration.
///
/// Differences from the `py_class` version:
/// - Auto converts from a wider range of Python types - smartset, any iterator.
/// - No need to take the Python GIL to create a new instance of `Set`.
pub struct Spans(pub IdSet);

impl Into<IdSet> for Spans {
    fn into(self) -> IdSet {
        self.0
    }
}

// A wrapper around [`IdSet`].
// This is different from `smartset.spanset`.
// Used in the Python world. The Rust world should use the `Spans` and `IdSet` types.
py_class!(pub class spans |py| {
    data inner: IdSet;

    def __new__(_cls, obj: PyObject) -> PyResult<spans> {
        Ok(Spans::extract(py, &obj)?.to_py_object(py))
    }

    /// Construct a 'spans' from an arbitrary integer range.
    ///
    /// This is unsafe because there are no validation that Ids in this
    /// range are valid.
    ///
    /// Use `range & torevs(dag.all())` to get a valid Set.
    ///
    /// This should only be used to be compatible with legacy revsets like
    /// "x:", ":y", "x:y", ":", or for fast paths of lazy sets (ex. ancestors
    /// with a cutoff minrev). Avoid using this function in new code.
    @staticmethod
    def unsaferange(start: Option<i64> = None, end: Option<i64> = None) -> PyResult<Spans> {
        let _ = py;
        if end.unwrap_or(0) < 0 {
            return Ok(Spans(IdSet::empty()))
        }
        let start = match start {
            Some(start) => Id(start.max(0) as u64),
            None => Id::MIN,
        };
        let end = match end {
            Some(end) => Id(end.max(0) as u64),
            None => Id::MAX,
        };
        let id_set: IdSet = if start <= end {
            IdSet::from_spans(vec![start..=end])
        } else {
            IdSet::empty()
        };
        Ok(Spans(id_set))
    }

    def __contains__(&self, id: i64) -> PyResult<bool> {
        if id < 0 {
            Ok(false)
        } else {
            Ok(self.inner(py).contains(Id(id as u64)))
        }
    }

    def __len__(&self) -> PyResult<usize> {
        Ok(self.inner(py).count() as usize)
    }

    def __iter__(&self) -> PyResult<spansiter> {
        self.iterdesc(py)
    }

    def iterasc(&self) -> PyResult<spansiter> {
        let iter = RefCell::new( self.inner(py).clone().into_iter());
        spansiter::create_instance(py, iter, true)
    }

    def iterdesc(&self) -> PyResult<spansiter> {
        let iter = RefCell::new(self.inner(py).clone().into_iter());
        spansiter::create_instance(py, iter, false)
    }

    def min(&self) -> PyResult<Option<u64>> {
        Ok(self.inner(py).min().map(|id| id.0))
    }

    def max(&self) -> PyResult<Option<u64>> {
        Ok(self.inner(py).max().map(|id| id.0))
    }

    def __repr__(&self) -> PyResult<String> {
        Ok(format!("[{:?}]", self.inner(py)))
    }

    def __add__(lhs, rhs) -> PyResult<Spans> {
        let lhs = Spans::extract(py, lhs)?;
        let rhs = Spans::extract(py, rhs)?;
        Ok(Spans(lhs.0.union(&rhs.0)))
    }

    def __and__(lhs, rhs) -> PyResult<Spans> {
        let lhs = Spans::extract(py, lhs)?;
        let rhs = Spans::extract(py, rhs)?;
        Ok(Spans(lhs.0.intersection(&rhs.0)))
    }

    def __sub__(lhs, rhs) -> PyResult<Spans> {
        let lhs = Spans::extract(py, lhs)?;
        let rhs = Spans::extract(py, rhs)?;
        Ok(Spans(lhs.0.difference(&rhs.0)))
    }
});

// A wrapper to [`IdSetIter`].
py_class!(pub class spansiter |py| {
    data iter: RefCell<IdSetIter<IdSet>>;
    data ascending: bool;

    def __next__(&self) -> PyResult<Option<u64>> {
        let mut iter = self.iter(py).borrow_mut();
        let next = if *self.ascending(py) {
            iter.next_back()
        } else {
            iter.next()
        };
        Ok(next.map(|id| id.0))
    }

    // Makes code like `list(spans.iterasc())` work.
    def __iter__(&self) -> PyResult<spansiter> {
        Ok(self.clone_ref(py))
    }
});

impl<'a> FromPyObject<'a> for Spans {
    fn extract(py: Python, obj: &'a PyObject) -> PyResult<Self> {
        // If obj already owns Set, then avoid iterating through it.
        if let Ok(pyset) = obj.extract::<spans>(py) {
            return Ok(Spans(pyset.inner(py).clone()));
        }

        // Then iterate through obj and collect all ids.
        // Collecting ids to a Vec first to preserve error handling.
        let ids: PyResult<Vec<Id>> = obj
            .iter(py)?
            .map(|o| Ok(o?.extract::<Option<i64>>(py)?))
            .filter_map(|o| match o {
                // Skip "None" (wdir?) automatically.
                Ok(None) => None,
                Ok(Some(i)) => {
                    // Skip "nullrev" automatically.
                    if i >= 0 { Some(Ok(Id(i as u64))) } else { None }
                }
                Err(e) => Some(Err(e)),
            })
            .collect();
        Ok(Spans(IdSet::from_spans(ids?)))
    }
}

impl ToPyObject for Spans {
    type ObjectType = spans;

    fn to_py_object(&self, py: Python) -> Self::ObjectType {
        spans::create_instance(py, self.0.clone()).unwrap()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

// This module contains functions from the Mercurial's diffhelpers.

use cpython::*;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "diffhelpers"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add(
        py,
        "addlines",
        py_fn!(
            py,
            addlines(
                fp: PyObject,
                hunk: PyList,
                lena: usize,
                lenb: usize,
                a: PyList,
                b: PyList
            )
        ),
    )?;
    m.add(
        py,
        "fix_newline",
        py_fn!(py, fix_newline(hunk: &PyList, a: &PyList, b: &PyList)),
    )?;
    m.add(
        py,
        "testhunk",
        py_fn!(py, testhunk(a: PyList, b: PyList, bstart: usize)),
    )?;
    Ok(m)
}

// Read lines from fp into the hunk.  The hunk is parsed into two arrays
// a and b. a gets the old state of the text, b gets the new state
// The control char from the hunk is saved when inserting into a, but not b
// (for performance while deleting files)
fn addlines(
    py: Python,
    fp: PyObject,
    hunk: PyList,
    lena: usize,
    lenb: usize,
    a: PyList,
    b: PyList,
) -> PyResult<usize> {
    let mut fp_iter = fp.iter(py)?;
    loop {
        let todoa = lena - a.len(py);
        let todob = lenb - b.len(py);
        let num = todoa.max(todob);
        if num == 0 {
            break;
        }
        for _i in 0..num {
            let s: PyBytes = match fp_iter.next() {
                Some(s) => s?.extract(py)?,
                None => {
                    return Err(PyErr::new::<exc::IOError, _>(
                        py,
                        "hunk processing error - hunk too short",
                    ));
                }
            };
            let s = s.data(py);
            if s.starts_with(b"\\ No newline at end of file") {
                fix_newline(py, &hunk, &a, &b)?;
                continue;
            }
            // Some patches may be missing the control char
            // on empty lines. Supply a leading space.
            let s = if s == b"\n" { b" \n" } else { s };
            hunk.append(py, to_object(py, s));
            match s.get(0) {
                Some(b'+') => b.append(py, to_object(py, &s[1..])),
                Some(b'-') => a.append(py, to_object(py, s)),
                _ => {
                    a.append(py, to_object(py, s));
                    if s.is_empty() {
                        // Ignore empty lines at EOF
                    } else {
                        b.append(py, to_object(py, &s[1..]));
                    }
                }
            }
        }
    }
    Ok(0)
}

// Fixup the last lines of a and b when the patch has no newline at EOF.
fn fix_newline(py: Python, hunk: &PyList, a: &PyList, b: &PyList) -> PyResult<usize> {
    let hunk_len = hunk.len(py);
    if hunk_len > 0 {
        let last_line = hunk.get_item(py, hunk_len - 1).extract::<PyBytes>(py)?;
        let last_line = last_line.data(py);
        let last_line = if last_line.ends_with(b"\r\n") {
            &last_line[..last_line.len() - 2]
        } else if last_line.ends_with(b"\n") {
            &last_line[..last_line.len() - 1]
        } else {
            last_line
        };
        match last_line.get(0) {
            Some(b' ') => {
                b.set_item(py, b.len(py) - 1, to_object(py, &last_line[1..]));
                a.set_item(py, a.len(py) - 1, to_object(py, &last_line[..]));
            }
            Some(b'+') => {
                b.set_item(py, b.len(py) - 1, to_object(py, &last_line[1..]));
            }
            Some(b'-') => {
                a.set_item(py, a.len(py) - 1, to_object(py, &last_line[..]));
            }
            _ => {}
        }
        hunk.set_item(py, hunk_len - 1, to_object(py, &last_line));
    }
    Ok(0)
}

// Compare the lines in a with the lines in b.  a is assumed to have
// a control char at the start of each line, this char is ignored in the
// compare
fn testhunk(py: Python, a: PyList, b: PyList, bstart: usize) -> PyResult<isize> {
    let alen = a.len(py);
    let blen = b.len(py);
    if alen + bstart > blen {
        return Ok(-1);
    }
    for i in 0..alen {
        if &a.get_item(py, i).extract::<PyBytes>(py)?.data(py)[1..]
            != b.get_item(py, i + bstart).extract::<PyBytes>(py)?.data(py)
        {
            return Ok(-1);
        }
    }
    Ok(0)
}

fn to_object(py: Python, s: &[u8]) -> PyObject {
    PyBytes::new(py, s).into_object()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::cell::RefCell;
use std::collections::hash_map::Keys;
use std::collections::HashMap;
use std::str;

use cpython::*;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "dirs"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<dirs>(py)?;
    Ok(m)
}

fn add_path(map: &mut HashMap<PyPathBuf, u64>, path: &PyPath) {
    let path = path.as_str();
    for (i, _) in path.rmatch_indices("/").chain(Some((0, ""))) {
        let prefix = PyPath::from_str(&path[..i]);
        if let Some(e) = map.get_mut(prefix) {
            *e += 1;
            return;
        }
        map.insert(prefix.to_owned(), 1);
    }
}

fn del_path(py: Python, map: &mut HashMap<PyPathBuf, u64>, path: &PyPath) -> PyResult<()> {
    let path = path.as_str();
    for (i, _) in path.rmatch_indices("/").chain(Some((0, ""))) {
        let prefix = PyPath::from_str(&path[..i]);
        if let Some(e) = map.get_mut(prefix) {
            if *e > 1 {
                *e -= 1;
                return Ok(());
            }
        } else {
            return Err(PyErr::new::<exc::ValueError, _>(
                py,
                "path not in collection",
            ));
        }
        map.remove(prefix);
    }
    Ok(())
}

// A multi-set of the directories that contain paths.
py_class!(pub class dirs |py| {
    @shared data inner: HashMap<PyPathBuf, u64>;

    def __new__(_cls, init: Option<&PyObject>) -> PyResult<dirs> {
        let mut inner = HashMap::new();
        if let Some(init) = init {
            for path in init.iter(py)? {
                RefFromPyObject::with_extracted(py, &path?, |path: &PyPath| {
                    add_path(&mut inner, path);
                })?;
            }
        }
        Ok(dirs::create_instance(py, inner)?)
    }

    def addpath(&self, path: &PyPath) -> PyResult<PyNone> {
        let mut inner = self.inner(py).borrow_mut();
        add_path(&mut *inner, path);
        Ok(PyNone)
    }

    def delpath(&self, path: &PyPath) -> PyResult<PyNone> {
        let mut inner = self.inner(py).borrow_mut();
        del_path(py, &mut *inner, path)?;
        Ok(PyNone)
    }

    def __contains__(&self, path: PyPathBuf) -> PyResult<bool> {
        let inner = self.inner(py).borrow();
        Ok(inner.contains_key(&path))
    }

    def __len__(&self) -> PyResult<usize> {
        Ok(self.inner(py).borrow().len())
    }

    def __iter__(&self) -> PyResult<dirsiter> {
        let iter = self.inner(py).leak_immutable();
        dirsiter::create_instance(py, RefCell::new(unsafe { iter.map(py, |o| o.keys()) }))
    }
});

py_class!(pub class dirsiter |py| {
    data iter: RefCell<UnsafePyLeaked<Keys<'static, PyPathBuf, u64>>>;

    def __next__(&self) -> PyResult<Option<PyPathBuf>> {
        let mut iter = self.iter(py).borrow_mut();
        let mut iter = unsafe { iter.try_borrow_mut(py)? };
        Ok(iter.next().cloned())
    }

    def __iter__(&self) -> PyResult<dirsiter> {
        Ok(self.clone_ref(py))
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use cpython::*;
use pyconfigloader::config;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "doctor"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add(
        py,
        "diagnose_network",
        py_fn!(py, diagnose_network(config: &config)),
    )?;
    Ok(m)
}

fn diagnose_network(py: Python, config: &config) -> PyResult<Option<(String, String)>> {
    let config = &config.get_cfg(py);
    match network_doctor::Doctor::new().diagnose(config) {
        Ok(()) => Ok(None),
        Err(d) => Ok(Some((d.treatment(config), format!("{}", d)))),
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and 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::*;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "drawdag"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add(py, "parse", py_fn!(py, parse(text: &str)))?;
    Ok(m)
}

fn parse(_py: Python, text: &str) -> PyResult<BTreeMap<String, Vec<String>>> {
    let parsed = drawdag::parse(text);
    let result = parsed
        .into_iter()
        .map(|(k, vs)| (k, vs.into_iter().collect()))
        .collect();
    Ok(result)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */
use std::sync::Arc;

use cpython::*;
use cpython_ext::convert::register_into;
use storemodel::ReadFileContents;
use storemodel::TreeStore;

use crate::EagerRepoStore;

pub(crate) fn register(py: Python) {
    register_into(py, |py, obj: EagerRepoStore| obj.to_dyn_treestore(py));
    register_into(py, |py, obj: EagerRepoStore| obj.to_read_file_contents(py));
}

impl EagerRepoStore {
    fn to_dyn_treestore(&self, py: Python) -> Arc<dyn TreeStore + Send + Sync> {
        let store = self.inner(py);
        Arc::new(store.clone())
    }

    fn to_read_file_contents(
        &self,
        py: Python,
    ) -> Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync> {
        let store = self.inner(py).clone();
        Arc::new(store)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::cell::RefCell;
use std::path::PathBuf;
use std::sync::Arc;

use async_runtime::block_on;
use cpython::*;
use cpython_ext::convert::Serde;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::ResultPyErrExt;
use dag::ops::DagAlgorithm;
use eagerepo::EagerRepo as RustEagerRepo;
use eagerepo::EagerRepoStore as RustEagerRepoStore;
use edenapi_types::HgId;
use pyconfigloader::config as PyConfig;
use pydag::dagalgo::dagalgo as PyDag;
use pyedenapi::PyClient;

mod impl_into;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "eagerepo"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<EagerRepo>(py)?;
    m.add_class::<EagerRepoStore>(py)?;
    impl_into::register(py);
    Ok(m)
}

py_class!(class EagerRepo |py| {
    data path: PathBuf;
    data inner: RefCell<RustEagerRepo>;

    /// Construct `EagerRepo` from a directory.
    @staticmethod
    def open(dir: &PyPath, config: Option<&PyConfig> = None) -> PyResult<Self> {
        let config = config.map(|config| config.get_cfg(py));
        let path = dir.as_path().to_path_buf();
        let inner = RustEagerRepo::open(&path, config.as_ref()).map_pyerr(py)?;
        Self::create_instance(py, path, RefCell::new(inner))
    }

    /// Construct `EagerRepo` from a URL.
    @staticmethod
    def openurl(url: &str) -> PyResult<Self> {
        let dir = match RustEagerRepo::url_to_dir(url) {
            Some(dir) => dir,
            None => return Err(PyErr::new::<exc::ValueError, _>(py, "invalid url")),
        };
        let inner = RustEagerRepo::open(&dir, None).map_pyerr(py)?;
        Self::create_instance(py, dir, RefCell::new(inner))
    }

    /// Write pending changes to disk.
    def flush(&self) -> PyResult<PyNone> {
        let mut inner = self.inner(py).borrow_mut();
        block_on(inner.flush()).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Insert SHA1 blob to zstore.
    /// In hg's case, the `data` is `min(p1, p2) + max(p1, p2) + text`.
    /// (rawtext: bytes) -> node
    def addsha1blob(&self, data: PyBytes) -> PyResult<PyBytes> {
        let mut inner = self.inner(py).borrow_mut();
        let id = inner.add_sha1_blob(data.data(py)).map_pyerr(py)?;
        Ok(PyBytes::new(py, id.as_ref()))
    }

    /// Insert SHA1 blob to zstore.
    /// In hg's case, the `data` is `min(p1, p2) + max(p1, p2) + text`.
    /// (node) -> rawtext
    def getsha1blob(&self, node: PyBytes) -> PyResult<Option<PyBytes>> {
        let inner = self.inner(py).borrow();
        let id = HgId::from_slice(node.data(py)).map_pyerr(py)?;
        let data = inner.get_sha1_blob(id).map_pyerr(py)?;
        Ok(data.map(|d| PyBytes::new(py, d.as_ref())))
    }

    /// Insert a commit. Return the commit hash.
    /// (parents: [node], rawtext: bytes) -> node
    def addcommit(&self, parents: Vec<PyBytes>, rawtext: PyBytes) -> PyResult<PyBytes> {
        let parents: Vec<HgId> = parents.into_iter()
            .map(|p| HgId::from_slice(p.data(py))).collect::<Result<_, _>>().map_pyerr(py)?;
        let id = block_on(self.inner(py).borrow_mut().add_commit(&parents, rawtext.data(py))).map_pyerr(py)?;
        Ok(PyBytes::new(py, id.as_ref()))
    }

    /// Add or remove a bookmark. (name: str, node | None)
    def setbookmark(&self, name: String, node: Option<PyBytes>) -> PyResult<PyNone> {
        let id = match node {
            Some(node) => Some(HgId::from_slice(node.data(py)).map_pyerr(py)?),
            None => None,
        };
        self.inner(py).borrow_mut().set_bookmark(&name, id).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Obtain an edenapi client.
    /// It re-opens the on-disk state so pending changes won't be exposed.
    def edenapiclient(&self) -> PyResult<PyClient> {
        let inner = RustEagerRepo::open(self.path(py), None).map_pyerr(py)?;
        PyClient::from_edenapi(py, Arc::new(inner))
    }

    /// Obtain a dag snapshot.
    def dag(&self) -> PyResult<PyDag> {
        let inner = self.inner(py).borrow();
        let dag = inner.dag().dag_snapshot().map_pyerr(py)?;
        PyDag::from_arc_dag(py, dag)
    }

    /// Obtain a store object.
    def store(&self) -> PyResult<EagerRepoStore> {
        let store = self.inner(py).borrow().store();
        EagerRepoStore::create_instance(py, store)
    }
});

py_class!(pub class EagerRepoStore |py| {
    data inner: RustEagerRepoStore;

    /// Construct `EagerRepoStore` from a directory.
    @staticmethod
    def open(dir: &PyPath) -> PyResult<Self> {
        let path = dir.as_path().to_path_buf();
        let inner = RustEagerRepoStore::open(&path).map_pyerr(py)?;
        Self::create_instance(py, inner)
    }

    def flush(&self) -> PyResult<PyNone> {
        self.inner(py).flush().map_pyerr(py)?;
        Ok(PyNone)
    }

    /// (data, bases=[]) -> bytes
    ///
    /// `data` should match hg's SHA1 format: min(p1, p2) + max(p1, p2) + raw_text.
    /// For file content with renames, `raw_text` should include the rename filelog header.
    /// Returns sha1(data).
    ///
    /// Changes are buffered in memory until flush().
    def add_sha1_blob(&self, data: PyBytes, bases: Option<Serde<Vec<HgId>>> = None) -> PyResult<PyBytes> {
        let inner = self.inner(py);
        let bases = match bases {
            Some(bases) => bases.0,
            None => Vec::new(),
        };
        let id = inner.add_sha1_blob(data.data(py), &bases).map_pyerr(py)?;
        Ok(PyBytes::new(py, id.as_ref()))
    }

    /// (node) -> Optional[bytes].
    ///
    /// Get the raw text with p1, p2 prefix.
    def get_sha1_blob(&self, node: Serde<HgId>) -> PyResult<Option<PyBytes>> {
        let inner = self.inner(py);
        inner.get_sha1_blob(node.0).map_pyerr(py).map(|d| d.map(|d| PyBytes::new(py, d.as_ref())))
    }

    /// (node) -> Optional[bytes].
    ///
    /// Get the raw text without the p1, p2 prefix.
    /// The raw text includes filelog header for file content.
    def get_content(&self, node: Serde<HgId>) -> PyResult<Option<PyBytes>> {
        let inner = self.inner(py);
        inner.get_content(node.0).map_pyerr(py).map(|d| d.map(|d| PyBytes::new(py, d.as_ref())))
    }
});