Skip to main content

gix/
init.rs

1#![allow(clippy::result_large_err)]
2use std::{borrow::Cow, path::Path};
3
4use gix_ref::{
5    store::WriteReflog,
6    transaction::{PreviousValue, RefEdit},
7    Category, FullName, Target,
8};
9
10use crate::{
11    bstr::{BString, ByteSlice},
12    config::tree::Init,
13    ThreadSafeRepository,
14};
15
16/// The name of the branch to use if non is configured via git configuration.
17///
18/// # Deviation
19///
20/// We use `main` instead of `master`.
21pub const DEFAULT_BRANCH_NAME: &str = "main";
22
23/// The error returned by [`crate::init()`].
24#[derive(Debug, thiserror::Error)]
25#[allow(missing_docs)]
26pub enum Error {
27    #[error("Could not obtain the current directory")]
28    CurrentDir(#[from] std::io::Error),
29    #[error(transparent)]
30    Init(#[from] crate::create::Error),
31    #[error(transparent)]
32    Open(#[from] crate::open::Error),
33    #[error("Invalid default branch name: {name:?}")]
34    InvalidBranchName {
35        name: BString,
36        source: gix_validate::reference::name::Error,
37    },
38    #[error("Could not edit HEAD reference with new default name")]
39    EditHeadForDefaultBranch(#[from] crate::reference::edit::Error),
40}
41
42impl ThreadSafeRepository {
43    /// Create a repository with work-tree within `directory`, creating intermediate directories as needed.
44    ///
45    /// Fails without action if there is already a `.git` repository inside of `directory`, but
46    /// won't mind if the `directory` otherwise is non-empty.
47    pub fn init(
48        directory: impl AsRef<Path>,
49        kind: crate::create::Kind,
50        options: crate::create::Options,
51    ) -> Result<Self, Error> {
52        use gix_sec::trust::DefaultForLevel;
53        let open_options = crate::open::Options::default_for_level(gix_sec::Trust::Full);
54        Self::init_opts(directory, kind, options, open_options)
55    }
56
57    /// Similar to [`init`][Self::init()], but allows to determine how exactly to open the newly created repository.
58    ///
59    /// # Deviation
60    ///
61    /// Instead of naming the default branch `master`, we name it `main` unless configured explicitly using the `init.defaultBranch`
62    /// configuration key.
63    pub fn init_opts(
64        directory: impl AsRef<Path>,
65        kind: crate::create::Kind,
66        create_options: crate::create::Options,
67        mut open_options: crate::open::Options,
68    ) -> Result<Self, Error> {
69        let path = crate::create::into(directory.as_ref(), kind, create_options)?;
70        let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories();
71        open_options.git_dir_trust = Some(gix_sec::Trust::Full);
72        // The repo will use `core.precomposeUnicode` to adjust the value as needed.
73        open_options.current_dir = gix_fs::current_dir(false)?.into();
74        let repo = ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, open_options)?;
75
76        let branch_name = repo
77            .config
78            .resolved
79            .string(Init::DEFAULT_BRANCH)
80            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_BRANCH_NAME.into()));
81        if branch_name.as_ref() != DEFAULT_BRANCH_NAME {
82            let configured_branch_name = branch_name.into_owned();
83            let sym_ref: FullName = Category::LocalBranch
84                .to_full_name(configured_branch_name.as_bstr())
85                .map_err(|err| Error::InvalidBranchName {
86                    name: configured_branch_name.clone(),
87                    source: err,
88                })?;
89            gix_validate::reference::branch_name(sym_ref.as_bstr()).map_err(|err| Error::InvalidBranchName {
90                name: configured_branch_name,
91                source: err,
92            })?;
93            let mut repo = repo.to_thread_local();
94            let prev_write_reflog = repo.refs.write_reflog;
95            repo.refs.write_reflog = WriteReflog::Disable;
96            repo.edit_reference(RefEdit {
97                change: gix_ref::transaction::Change::Update {
98                    log: Default::default(),
99                    expected: PreviousValue::Any,
100                    new: Target::Symbolic(sym_ref),
101                },
102                name: "HEAD".try_into().expect("valid"),
103                deref: false,
104            })?;
105            repo.refs.write_reflog = prev_write_reflog;
106        }
107
108        Ok(repo)
109    }
110}