gix 0.88.0

Interact with git repositories just like git would
Documentation
#![allow(clippy::result_large_err)]
use std::path::Path;

use gix_error::ErrorExt;
use gix_ref::{
    Category, FullName,
    store::WriteReflog,
    transaction::{PreviousValue, RefEdit},
};

use crate::{Result, ThreadSafeRepository, bstr::ByteSlice, config::tree::Init};
use gix_error::ResultExt;

/// The name of the branch to use if non is configured via git configuration.
///
/// # Deviation
///
/// We use `main` instead of `master`.
pub const DEFAULT_BRANCH_NAME: &str = "main";

impl ThreadSafeRepository {
    /// Create a repository with work-tree within `directory`, creating intermediate directories as needed.
    ///
    /// Fails without action if the destination directory isn't empty unless
    /// [`create::Options::destination_must_be_empty`][crate::create::Options::destination_must_be_empty] is `None`
    /// or `Some(false)`. Note that initialization still fails if a `.git` directory already exists in
    /// the destination.
    pub fn init(
        directory: impl AsRef<Path>,
        kind: crate::create::Kind,
        options: crate::create::Options,
    ) -> Result<Self> {
        use gix_sec::trust::DefaultForLevel;
        let open_options = crate::open::Options::default_for_level(gix_sec::Trust::Full);
        Self::init_opts(directory, kind, options, open_options)
    }

    /// Similar to [`init`][Self::init()], but allows to determine how exactly to open the newly created repository.
    /// Invalid `init.defaultBranch` values include their bytes as `input` [metadata](gix_error::Error::metadata()).
    ///
    /// # Deviation
    ///
    /// Instead of naming the default branch `master`, we name it `main` unless configured explicitly using the `init.defaultBranch`
    /// configuration key.
    pub fn init_opts(
        directory: impl AsRef<Path>,
        kind: crate::create::Kind,
        create_options: crate::create::Options,
        mut open_options: crate::open::Options,
    ) -> Result<Self> {
        let (path, capabilities) = crate::create::into_with_capabilities(directory.as_ref(), kind, create_options)?;
        if !capabilities.symlink {
            open_options.api_config_overrides.push("core.symlinks=false".into());
        }
        let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories();
        open_options.git_dir_trust = Some(gix_sec::Trust::Full);
        // The repo will use `core.precomposeUnicode` to adjust the value as needed.
        open_options.current_dir = gix_fs::current_dir(false)
            .or_raise(|| gix_error::message("Could not obtain the current directory"))?
            .into();
        let repo = ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, open_options, None)?;

        let branch_name = repo
            .config
            .resolved
            .string(Init::DEFAULT_BRANCH)
            .unwrap_or_else(|| DEFAULT_BRANCH_NAME.into());
        if branch_name.as_bstr() != DEFAULT_BRANCH_NAME {
            let configured_branch_name = branch_name;
            let sym_ref: FullName = Category::LocalBranch
                .to_full_name(configured_branch_name.as_bstr())
                .map_err(|err| {
                    err.and_raise(
                        gix_error::validation("Invalid default branch name")
                            .with("input", configured_branch_name.clone()),
                    )
                })?;
            gix_validate::reference::branch_name(sym_ref.as_bstr()).map_err(|err| {
                err.and_raise(
                    gix_error::validation("Invalid default branch name").with("input", configured_branch_name),
                )
            })?;
            let mut repo = repo.to_thread_local();
            let prev_write_reflog = repo.refs.write_reflog;
            repo.refs.write_reflog = WriteReflog::Disable;
            repo.edit_reference(RefEdit::update(
                "HEAD".try_into().expect("valid"),
                sym_ref,
                PreviousValue::Any,
                "",
            ))
            .or_raise(|| gix_error::message("Could not edit HEAD reference with new default name"))?;
            repo.refs.write_reflog = prev_write_reflog;
        }

        Ok(repo)
    }
}