Skip to content

Commit 7f67b23

Browse files
committed
feat: Use git-config to write config file on initialization, including logallrefupdates and precomposeunicode. (#331)
1 parent 0103501 commit 7f67b23

File tree

6 files changed

+83
-36
lines changed

6 files changed

+83
-36
lines changed

git-repository/src/assets/baseline-init/config

Lines changed: 0 additions & 3 deletions
This file was deleted.

git-repository/src/create.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
use git_config::parse::section;
2+
use git_discover::DOT_GIT_DIR;
3+
use std::convert::TryFrom;
14
use std::{
25
fs::{self, OpenOptions},
36
io::Write,
47
path::{Path, PathBuf},
58
};
69

7-
use crate::bstr::ByteSlice;
8-
910
/// The error used in [`into()`].
1011
#[derive(Debug, thiserror::Error)]
1112
#[allow(missing_docs)]
@@ -22,8 +23,6 @@ pub enum Error {
2223
CreateDirectory { source: std::io::Error, path: PathBuf },
2324
}
2425

25-
const GIT_DIR_NAME: &str = ".git";
26-
2726
const TPL_INFO_EXCLUDE: &[u8] = include_bytes!("assets/baseline-init/info/exclude");
2827
const TPL_HOOKS_APPLYPATCH_MSG: &[u8] = include_bytes!("assets/baseline-init/hooks/applypatch-msg.sample");
2928
const TPL_HOOKS_COMMIT_MSG: &[u8] = include_bytes!("assets/baseline-init/hooks/commit-msg.sample");
@@ -37,7 +36,6 @@ const TPL_HOOKS_PRE_REBASE: &[u8] = include_bytes!("assets/baseline-init/hooks/p
3736
const TPL_HOOKS_PRE_RECEIVE: &[u8] = include_bytes!("assets/baseline-init/hooks/pre-receive.sample");
3837
const TPL_HOOKS_PREPARE_COMMIT_MSG: &[u8] = include_bytes!("assets/baseline-init/hooks/prepare-commit-msg.sample");
3938
const TPL_HOOKS_UPDATE: &[u8] = include_bytes!("assets/baseline-init/hooks/update.sample");
40-
const TPL_CONFIG: &[u8] = include_bytes!("assets/baseline-init/config");
4139
const TPL_DESCRIPTION: &[u8] = include_bytes!("assets/baseline-init/description");
4240
const TPL_HEAD: &[u8] = include_bytes!("assets/baseline-init/HEAD");
4341

@@ -99,14 +97,22 @@ fn create_dir(p: &Path) -> Result<(), Error> {
9997
}
10098

10199
/// Options for use in [`into()`];
100+
#[derive(Copy, Clone)]
102101
pub struct Options {
103102
/// If true, the repository will be a bare repository without a worktree.
104103
pub bare: bool,
104+
105+
/// If set, use these filesytem capabilities to populate the respective git-config fields.
106+
/// If `None`, the directory will be probed.
107+
pub fs_capabilities: Option<git_worktree::fs::Capabilities>,
105108
}
106109

107110
/// Create a new `.git` repository of `kind` within the possibly non-existing `directory`
108111
/// and return its path.
109-
pub fn into(directory: impl Into<PathBuf>, Options { bare }: Options) -> Result<git_discover::repository::Path, Error> {
112+
pub fn into(
113+
directory: impl Into<PathBuf>,
114+
Options { bare, fs_capabilities }: Options,
115+
) -> Result<git_discover::repository::Path, Error> {
110116
let mut dot_git = directory.into();
111117

112118
if bare {
@@ -121,7 +127,7 @@ pub fn into(directory: impl Into<PathBuf>, Options { bare }: Options) -> Result<
121127
return Err(Error::DirectoryNotEmpty { path: dot_git });
122128
}
123129
} else {
124-
dot_git.push(GIT_DIR_NAME);
130+
dot_git.push(DOT_GIT_DIR);
125131

126132
if dot_git.is_dir() {
127133
return Err(Error::DirectoryExists { path: dot_git });
@@ -166,19 +172,29 @@ pub fn into(directory: impl Into<PathBuf>, Options { bare }: Options) -> Result<
166172
create_dir(PathCursor(cursor.as_mut()).at("tags"))?;
167173
}
168174

169-
for (tpl, filename) in &[
170-
(TPL_HEAD, "HEAD"),
171-
(TPL_DESCRIPTION, "description"),
172-
(TPL_CONFIG, "config"),
173-
] {
174-
if *filename == "config" {
175-
write_file(
176-
&tpl.replace("{bare-value}", if bare { "true" } else { "false" }),
177-
PathCursor(&mut dot_git).at(filename),
178-
)?;
179-
} else {
180-
write_file(tpl, PathCursor(&mut dot_git).at(filename))?;
175+
for (tpl, filename) in &[(TPL_HEAD, "HEAD"), (TPL_DESCRIPTION, "description")] {
176+
write_file(tpl, PathCursor(&mut dot_git).at(filename))?;
177+
}
178+
179+
{
180+
let mut config = git_config::File::default();
181+
{
182+
let caps = fs_capabilities.unwrap_or_else(|| git_worktree::fs::Capabilities::probe(&dot_git));
183+
let mut core = config.new_section("core", None).expect("valid section name");
184+
185+
core.push(key("repositoryformatversion"), "0");
186+
core.push(key("filemode"), bool(caps.executable_bit));
187+
core.push(key("bare"), bool(bare));
188+
core.push(key("logallrefupdates"), bool(!bare));
189+
core.push(key("symlinks"), bool(caps.symlink));
190+
core.push(key("ignorecase"), bool(caps.ignore_case));
191+
core.push(key("precomposeunicode"), bool(caps.precompose_unicode));
181192
}
193+
let config_path = dot_git.join("config");
194+
std::fs::write(&config_path, &config.to_bstring()).map_err(|err| Error::IoWrite {
195+
source: err,
196+
path: config_path,
197+
})?;
182198
}
183199

184200
Ok(git_discover::repository::Path::from_dot_git_dir(
@@ -187,3 +203,14 @@ pub fn into(directory: impl Into<PathBuf>, Options { bare }: Options) -> Result<
187203
.unwrap_or(git_discover::repository::Kind::WorkTree { linked_git_dir: None }),
188204
))
189205
}
206+
207+
fn key(name: &'static str) -> section::Key<'static> {
208+
section::Key::try_from(name).expect("valid key name")
209+
}
210+
211+
fn bool(v: bool) -> &'static str {
212+
match v {
213+
true => "true",
214+
false => "false",
215+
}
216+
}

git-repository/src/lib.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,12 +234,26 @@ pub fn discover(directory: impl AsRef<std::path::Path>) -> Result<Repository, di
234234

235235
/// See [ThreadSafeRepository::init()], but returns a [`Repository`] instead.
236236
pub fn init(directory: impl AsRef<std::path::Path>) -> Result<Repository, init::Error> {
237-
ThreadSafeRepository::init(directory, crate::create::Options { bare: false }).map(Into::into)
237+
ThreadSafeRepository::init(
238+
directory,
239+
create::Options {
240+
bare: false,
241+
fs_capabilities: None,
242+
},
243+
)
244+
.map(Into::into)
238245
}
239246

240247
/// See [ThreadSafeRepository::init()], but returns a [`Repository`] instead.
241248
pub fn init_bare(directory: impl AsRef<std::path::Path>) -> Result<Repository, init::Error> {
242-
ThreadSafeRepository::init(directory, crate::create::Options { bare: true }).map(Into::into)
249+
ThreadSafeRepository::init(
250+
directory,
251+
create::Options {
252+
bare: true,
253+
fs_capabilities: None,
254+
},
255+
)
256+
.map(Into::into)
243257
}
244258

245259
/// See [ThreadSafeRepository::open()], but returns a [`Repository`] instead.
@@ -395,7 +409,8 @@ pub mod discover {
395409
}
396410

397411
impl ThreadSafeRepository {
398-
/// Try to open a git repository in `directory` and search upwards through its parents until one is found.
412+
/// Try to open a git repository in `directory` and search upwards through its parents until one is found,
413+
/// using default trust options which matters in case the found repository isn't owned by the current user.
399414
pub fn discover(directory: impl AsRef<Path>) -> Result<Self, Error> {
400415
Self::discover_opts(directory, Default::default(), Default::default())
401416
}

git-repository/src/open.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ impl ThreadSafeRepository {
172172
match git_discover::is_git(&path) {
173173
Ok(kind) => (path, kind),
174174
Err(_err) => {
175-
let git_dir = path.join(".git");
175+
let git_dir = path.join(git_discover::DOT_GIT_DIR);
176176
git_discover::is_git(&git_dir).map(|kind| (git_dir, kind))?
177177
}
178178
}

git-repository/tests/init/mod.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
mod bare {
22
#[test]
3-
fn init_into_empty_directory_creates_a_dot_git_dir() {
4-
let tmp = tempfile::tempdir().unwrap();
5-
let repo = git_repository::init_bare(tmp.path()).unwrap();
3+
fn init_into_empty_directory_creates_a_dot_git_dir() -> crate::Result {
4+
let tmp = tempfile::tempdir()?;
5+
let repo = git_repository::init_bare(tmp.path())?;
66
assert_eq!(repo.kind(), git_repository::Kind::Bare);
77
assert!(
88
repo.work_dir().is_none(),
@@ -13,18 +13,20 @@ mod bare {
1313
tmp.path(),
1414
"the repository is placed into the directory itself"
1515
);
16-
assert_eq!(git_repository::open(repo.git_dir()).unwrap(), repo);
16+
assert_eq!(git_repository::open(repo.git_dir())?, repo);
17+
Ok(())
1718
}
1819

1920
#[test]
20-
fn init_into_non_empty_directory_is_not_allowed() {
21-
let tmp = tempfile::tempdir().unwrap();
22-
std::fs::write(tmp.path().join("existing.txt"), b"I was here before you").unwrap();
21+
fn init_into_non_empty_directory_is_not_allowed() -> crate::Result {
22+
let tmp = tempfile::tempdir()?;
23+
std::fs::write(tmp.path().join("existing.txt"), b"I was here before you")?;
2324

2425
assert!(git_repository::init_bare(tmp.path())
2526
.unwrap_err()
2627
.to_string()
27-
.starts_with("Refusing to initialize the non-empty directory as"),);
28+
.starts_with("Refusing to initialize the non-empty directory as"));
29+
Ok(())
2830
}
2931
}
3032

gitoxide-core/src/repository/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ use anyhow::{Context as AnyhowContext, Result};
44
use git_repository as git;
55

66
pub fn init(directory: Option<PathBuf>) -> Result<git::discover::repository::Path> {
7-
git_repository::create::into(directory.unwrap_or_default(), git::create::Options { bare: false })
8-
.with_context(|| "Repository initialization failed")
7+
git_repository::create::into(
8+
directory.unwrap_or_default(),
9+
git::create::Options {
10+
bare: false,
11+
fs_capabilities: None,
12+
},
13+
)
14+
.with_context(|| "Repository initialization failed")
915
}
1016

1117
pub mod tree;

0 commit comments

Comments
 (0)