]> git.proxmox.com Git - rustc.git/blame - vendor/gix/src/create.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / vendor / gix / src / create.rs
CommitLineData
0a29b90c
FG
1use std::{
2 convert::TryFrom,
3 fs::{self, OpenOptions},
4 io::Write,
5 path::{Path, PathBuf},
6};
7
8use gix_config::parse::section;
9use gix_discover::DOT_GIT_DIR;
781aab86 10use gix_macros::momo;
0a29b90c
FG
11
12/// The error used in [`into()`].
13#[derive(Debug, thiserror::Error)]
14#[allow(missing_docs)]
15pub enum Error {
16 #[error("Could not obtain the current directory")]
17 CurrentDir(#[from] std::io::Error),
18 #[error("Could not open data at '{}'", .path.display())]
19 IoOpen { source: std::io::Error, path: PathBuf },
20 #[error("Could not write data at '{}'", .path.display())]
21 IoWrite { source: std::io::Error, path: PathBuf },
22 #[error("Refusing to initialize the existing '{}' directory", .path.display())]
23 DirectoryExists { path: PathBuf },
24 #[error("Refusing to initialize the non-empty directory as '{}'", .path.display())]
25 DirectoryNotEmpty { path: PathBuf },
26 #[error("Could not create directory at '{}'", .path.display())]
27 CreateDirectory { source: std::io::Error, path: PathBuf },
28}
29
30/// The kind of repository to create.
31#[derive(Debug, Copy, Clone)]
32pub enum Kind {
33 /// An empty repository with a `.git` folder, setup to contain files in its worktree.
34 WithWorktree,
35 /// A bare repository without a worktree.
36 Bare,
37}
38
781aab86
FG
39const TPL_INFO_EXCLUDE: &[u8] = include_bytes!("assets/init/info/exclude");
40const TPL_HOOKS_APPLYPATCH_MSG: &[u8] = include_bytes!("assets/init/hooks/applypatch-msg.sample");
41const TPL_HOOKS_COMMIT_MSG: &[u8] = include_bytes!("assets/init/hooks/commit-msg.sample");
42const TPL_HOOKS_FSMONITOR_WATCHMAN: &[u8] = include_bytes!("assets/init/hooks/fsmonitor-watchman.sample");
43const TPL_HOOKS_POST_UPDATE: &[u8] = include_bytes!("assets/init/hooks/post-update.sample");
44const TPL_HOOKS_PRE_APPLYPATCH: &[u8] = include_bytes!("assets/init/hooks/pre-applypatch.sample");
45const TPL_HOOKS_PRE_COMMIT: &[u8] = include_bytes!("assets/init/hooks/pre-commit.sample");
46const TPL_HOOKS_PRE_MERGE_COMMIT: &[u8] = include_bytes!("assets/init/hooks/pre-merge-commit.sample");
47const TPL_HOOKS_PRE_PUSH: &[u8] = include_bytes!("assets/init/hooks/pre-push.sample");
48const TPL_HOOKS_PRE_REBASE: &[u8] = include_bytes!("assets/init/hooks/pre-rebase.sample");
49const TPL_HOOKS_PREPARE_COMMIT_MSG: &[u8] = include_bytes!("assets/init/hooks/prepare-commit-msg.sample");
50const TPL_HOOKS_DOCS_URL: &[u8] = include_bytes!("assets/init/hooks/docs.url");
51const TPL_DESCRIPTION: &[u8] = include_bytes!("assets/init/description");
52const TPL_HEAD: &[u8] = include_bytes!("assets/init/HEAD");
0a29b90c
FG
53
54struct PathCursor<'a>(&'a mut PathBuf);
55
56struct NewDir<'a>(&'a mut PathBuf);
57
58impl<'a> PathCursor<'a> {
59 fn at(&mut self, component: &str) -> &Path {
60 self.0.push(component);
61 self.0.as_path()
62 }
63}
64
65impl<'a> NewDir<'a> {
66 fn at(self, component: &str) -> Result<Self, Error> {
67 self.0.push(component);
68 create_dir(self.0)?;
69 Ok(self)
70 }
71 fn as_mut(&mut self) -> &mut PathBuf {
72 self.0
73 }
74}
75
76impl<'a> Drop for NewDir<'a> {
77 fn drop(&mut self) {
78 self.0.pop();
79 }
80}
81
82impl<'a> Drop for PathCursor<'a> {
83 fn drop(&mut self) {
84 self.0.pop();
85 }
86}
87
88fn write_file(data: &[u8], path: &Path) -> Result<(), Error> {
89 let mut file = OpenOptions::new()
90 .write(true)
91 .create(true)
92 .append(false)
93 .open(path)
94 .map_err(|e| Error::IoOpen {
95 source: e,
96 path: path.to_owned(),
97 })?;
98 file.write_all(data).map_err(|e| Error::IoWrite {
99 source: e,
100 path: path.to_owned(),
101 })
102}
103
104fn create_dir(p: &Path) -> Result<(), Error> {
105 fs::create_dir_all(p).map_err(|e| Error::CreateDirectory {
106 source: e,
107 path: p.to_owned(),
108 })
109}
110
111/// Options for use in [`into()`];
112#[derive(Copy, Clone, Default)]
113pub struct Options {
114 /// If true, and the kind of repository to create has a worktree, then the destination directory must be empty.
115 ///
116 /// By default repos with worktree can be initialized into a non-empty repository as long as there is no `.git` directory.
117 pub destination_must_be_empty: bool,
781aab86 118 /// If set, use these filesystem capabilities to populate the respective git-config fields.
0a29b90c 119 /// If `None`, the directory will be probed.
49aad941 120 pub fs_capabilities: Option<gix_fs::Capabilities>,
0a29b90c
FG
121}
122
123/// Create a new `.git` repository of `kind` within the possibly non-existing `directory`
124/// and return its path.
125/// Note that this is a simple template-based initialization routine which should be accompanied with additional corrections
126/// to respect git configuration, which is accomplished by [its callers][crate::ThreadSafeRepository::init_opts()]
127/// that return a [Repository][crate::Repository].
781aab86 128#[momo]
0a29b90c
FG
129pub fn into(
130 directory: impl Into<PathBuf>,
131 kind: Kind,
132 Options {
133 fs_capabilities,
134 destination_must_be_empty,
135 }: Options,
136) -> Result<gix_discover::repository::Path, Error> {
137 let mut dot_git = directory.into();
138 let bare = matches!(kind, Kind::Bare);
139
140 if bare || destination_must_be_empty {
141 let num_entries_in_dot_git = fs::read_dir(&dot_git)
142 .or_else(|err| {
143 if err.kind() == std::io::ErrorKind::NotFound {
144 fs::create_dir(&dot_git).and_then(|_| fs::read_dir(&dot_git))
145 } else {
146 Err(err)
147 }
148 })
149 .map_err(|err| Error::IoOpen {
150 source: err,
151 path: dot_git.clone(),
152 })?
153 .count();
154 if num_entries_in_dot_git != 0 {
155 return Err(Error::DirectoryNotEmpty { path: dot_git });
156 }
157 }
158
159 if !bare {
160 dot_git.push(DOT_GIT_DIR);
161
162 if dot_git.is_dir() {
163 return Err(Error::DirectoryExists { path: dot_git });
164 }
165 };
166 create_dir(&dot_git)?;
167
168 {
169 let mut cursor = NewDir(&mut dot_git).at("info")?;
170 write_file(TPL_INFO_EXCLUDE, PathCursor(cursor.as_mut()).at("exclude"))?;
171 }
172
173 {
174 let mut cursor = NewDir(&mut dot_git).at("hooks")?;
175 for (tpl, filename) in &[
781aab86 176 (TPL_HOOKS_DOCS_URL, "docs.url"),
0a29b90c 177 (TPL_HOOKS_PREPARE_COMMIT_MSG, "prepare-commit-msg.sample"),
0a29b90c
FG
178 (TPL_HOOKS_PRE_REBASE, "pre-rebase.sample"),
179 (TPL_HOOKS_PRE_PUSH, "pre-push.sample"),
180 (TPL_HOOKS_PRE_COMMIT, "pre-commit.sample"),
181 (TPL_HOOKS_PRE_MERGE_COMMIT, "pre-merge-commit.sample"),
182 (TPL_HOOKS_PRE_APPLYPATCH, "pre-applypatch.sample"),
183 (TPL_HOOKS_POST_UPDATE, "post-update.sample"),
184 (TPL_HOOKS_FSMONITOR_WATCHMAN, "fsmonitor-watchman.sample"),
185 (TPL_HOOKS_COMMIT_MSG, "commit-msg.sample"),
186 (TPL_HOOKS_APPLYPATCH_MSG, "applypatch-msg.sample"),
187 ] {
188 write_file(tpl, PathCursor(cursor.as_mut()).at(filename))?;
189 }
190 }
191
192 {
193 let mut cursor = NewDir(&mut dot_git).at("objects")?;
194 create_dir(PathCursor(cursor.as_mut()).at("info"))?;
195 create_dir(PathCursor(cursor.as_mut()).at("pack"))?;
196 }
197
198 {
199 let mut cursor = NewDir(&mut dot_git).at("refs")?;
200 create_dir(PathCursor(cursor.as_mut()).at("heads"))?;
201 create_dir(PathCursor(cursor.as_mut()).at("tags"))?;
202 }
203
204 for (tpl, filename) in &[(TPL_HEAD, "HEAD"), (TPL_DESCRIPTION, "description")] {
205 write_file(tpl, PathCursor(&mut dot_git).at(filename))?;
206 }
207
208 {
209 let mut config = gix_config::File::default();
210 {
49aad941 211 let caps = fs_capabilities.unwrap_or_else(|| gix_fs::Capabilities::probe(&dot_git));
0a29b90c
FG
212 let mut core = config.new_section("core", None).expect("valid section name");
213
214 core.push(key("repositoryformatversion"), Some("0".into()));
215 core.push(key("filemode"), Some(bool(caps.executable_bit).into()));
216 core.push(key("bare"), Some(bool(bare).into()));
217 core.push(key("logallrefupdates"), Some(bool(!bare).into()));
218 core.push(key("symlinks"), Some(bool(caps.symlink).into()));
219 core.push(key("ignorecase"), Some(bool(caps.ignore_case).into()));
220 core.push(key("precomposeunicode"), Some(bool(caps.precompose_unicode).into()));
221 }
222 let mut cursor = PathCursor(&mut dot_git);
223 let config_path = cursor.at("config");
224 std::fs::write(config_path, config.to_bstring()).map_err(|err| Error::IoWrite {
225 source: err,
226 path: config_path.to_owned(),
227 })?;
228 }
229
230 Ok(gix_discover::repository::Path::from_dot_git_dir(
231 dot_git,
232 if bare {
4b012472 233 gix_discover::repository::Kind::PossiblyBare
0a29b90c
FG
234 } else {
235 gix_discover::repository::Kind::WorkTree { linked_git_dir: None }
236 },
781aab86 237 &std::env::current_dir()?,
0a29b90c
FG
238 )
239 .expect("by now the `dot_git` dir is valid as we have accessed it"))
240}
241
242fn key(name: &'static str) -> section::Key<'static> {
243 section::Key::try_from(name).expect("valid key name")
244}
245
246fn bool(v: bool) -> &'static str {
247 match v {
248 true => "true",
249 false => "false",
250 }
251}