gix_ref/store/file/loose/
reflog.rs1use std::{io::Read, path::PathBuf};
2
3use crate::{
4 store_impl::{file, file::log},
5 FullNameRef,
6};
7
8impl file::Store {
9 pub fn reflog_exists<'a, Name, E>(&self, name: Name) -> Result<bool, E>
15 where
16 Name: TryInto<&'a FullNameRef, Error = E>,
17 crate::name::Error: From<E>,
18 {
19 Ok(self.reflog_path(name.try_into()?).is_file())
20 }
21
22 pub fn reflog_iter_rev<'a, 'b, Name, E>(
27 &self,
28 name: Name,
29 buf: &'b mut [u8],
30 ) -> Result<Option<log::iter::Reverse<'b, std::fs::File>>, Error>
31 where
32 Name: TryInto<&'a FullNameRef, Error = E>,
33 crate::name::Error: From<E>,
34 {
35 let name: &FullNameRef = name.try_into().map_err(|err| Error::RefnameValidation(err.into()))?;
36 let path = self.reflog_path(name);
37 if path.is_dir() {
38 return Ok(None);
39 }
40 match std::fs::File::open(&path) {
41 Ok(file) => Ok(Some(log::iter::reverse(file, buf)?)),
42 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
43 Err(err) => Err(err.into()),
44 }
45 }
46
47 pub fn reflog_iter<'a, 'b, Name, E>(
52 &self,
53 name: Name,
54 buf: &'b mut Vec<u8>,
55 ) -> Result<Option<log::iter::Forward<'b>>, Error>
56 where
57 Name: TryInto<&'a FullNameRef, Error = E>,
58 crate::name::Error: From<E>,
59 {
60 let name: &FullNameRef = name.try_into().map_err(|err| Error::RefnameValidation(err.into()))?;
61 let path = self.reflog_path(name);
62 match std::fs::File::open(&path) {
63 Ok(mut file) => {
64 buf.clear();
65 if let Err(err) = file.read_to_end(buf) {
66 return if path.is_dir() { Ok(None) } else { Err(err.into()) };
67 }
68 Ok(Some(log::iter::forward(buf)))
69 }
70 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
71 #[cfg(windows)]
72 Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => Ok(None),
73 Err(err) => Err(err.into()),
74 }
75 }
76}
77
78impl file::Store {
79 pub(crate) fn reflog_path(&self, name: &FullNameRef) -> PathBuf {
81 let (base, rela_path) = self.reflog_base_and_relative_path(name);
82 base.join(rela_path)
83 }
84}
85
86pub mod create_or_update {
88 use std::{
89 borrow::Cow,
90 io::Write,
91 path::{Path, PathBuf},
92 };
93
94 use gix_hash::{oid, ObjectId};
95 use gix_object::bstr::BStr;
96
97 use crate::store_impl::{file, file::WriteReflog};
98
99 impl file::Store {
100 #[allow(clippy::too_many_arguments)]
101 pub(crate) fn reflog_create_or_append(
102 &self,
103 name: &FullNameRef,
104 previous_oid: Option<ObjectId>,
105 new: &oid,
106 committer: Option<gix_actor::SignatureRef<'_>>,
107 message: &BStr,
108 mut force_create_reflog: bool,
109 ) -> Result<(), Error> {
110 let (reflog_base, full_name) = self.reflog_base_and_relative_path(name);
111 match self.write_reflog {
112 WriteReflog::Normal | WriteReflog::Always => {
113 if self.write_reflog == WriteReflog::Always {
114 force_create_reflog = true;
115 }
116 let mut options = std::fs::OpenOptions::new();
117 options.append(true).read(false);
118 let log_path = reflog_base.join(&full_name);
119
120 if force_create_reflog || self.should_autocreate_reflog(&full_name) {
121 let parent_dir = log_path.parent().expect("always with parent directory");
122 gix_tempfile::create_dir::all(parent_dir, Default::default()).map_err(|err| {
123 Error::CreateLeadingDirectories {
124 source: err,
125 reflog_directory: parent_dir.to_owned(),
126 }
127 })?;
128 options.create(true);
129 }
130
131 let file_for_appending = match options.open(&log_path) {
132 Ok(f) => Some(f),
133 Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
134 Err(err) => {
135 if log_path.is_dir() {
137 gix_tempfile::remove_dir::empty_depth_first(log_path.clone())
138 .and_then(|_| options.open(&log_path))
139 .map(Some)
140 .map_err(|_| Error::Append {
141 source: err,
142 reflog_path: self.reflog_path(name),
143 })?
144 } else {
145 return Err(Error::Append {
146 source: err,
147 reflog_path: log_path,
148 });
149 }
150 }
151 };
152
153 if let Some(mut file) = file_for_appending {
154 let committer = committer.ok_or(Error::MissingCommitter)?;
155 write!(file, "{} {} ", previous_oid.unwrap_or_else(|| new.kind().null()), new)
156 .and_then(|_| committer.write_to(&mut file))
157 .and_then(|_| {
158 if !message.is_empty() {
159 writeln!(file, "\t{message}")
160 } else {
161 writeln!(file)
162 }
163 })
164 .map_err(|err| Error::Append {
165 source: err,
166 reflog_path: self.reflog_path(name),
167 })?;
168 }
169 Ok(())
170 }
171 WriteReflog::Disable => Ok(()),
172 }
173 }
174
175 fn should_autocreate_reflog(&self, full_name: &Path) -> bool {
176 full_name.starts_with("refs/heads/")
177 || full_name.starts_with("refs/remotes/")
178 || full_name.starts_with("refs/notes/")
179 || full_name.starts_with("refs/worktree/") || full_name == Path::new("HEAD")
181 }
182
183 pub(in crate::store_impl::file) fn reflog_base_and_relative_path<'a>(
185 &self,
186 name: &'a FullNameRef,
187 ) -> (PathBuf, Cow<'a, Path>) {
188 let is_reflog = true;
189 let (base, name) = self.to_base_dir_and_relative_name(name, is_reflog);
190 (
191 base.join("logs"),
192 match &self.namespace {
193 None => gix_path::to_native_path_on_windows(name.as_bstr()),
194 Some(namespace) => gix_path::to_native_path_on_windows(
195 namespace.to_owned().into_namespaced_name(name).into_inner(),
196 ),
197 },
198 )
199 }
200 }
201
202 #[cfg(test)]
203 mod tests;
204
205 mod error {
206 use std::path::PathBuf;
207
208 #[derive(Debug, thiserror::Error)]
210 #[allow(missing_docs)]
211 pub enum Error {
212 #[error("Could create one or more directories in {reflog_directory:?} to contain reflog file")]
213 CreateLeadingDirectories {
214 source: std::io::Error,
215 reflog_directory: PathBuf,
216 },
217 #[error("Could not open reflog file at {reflog_path:?} for appending")]
218 Append {
219 source: std::io::Error,
220 reflog_path: PathBuf,
221 },
222 #[error("reflog message must not contain newlines")]
223 MessageWithNewlines,
224 #[error("reflog messages need a committer which isn't set")]
225 MissingCommitter,
226 }
227 }
228 pub use error::Error;
229
230 use crate::FullNameRef;
231}
232
233mod error {
234 #[derive(Debug, thiserror::Error)]
236 #[allow(missing_docs)]
237 pub enum Error {
238 #[error("The reflog name or path is not a valid ref name")]
239 RefnameValidation(#[from] crate::name::Error),
240 #[error("The reflog file could not read")]
241 Io(#[from] std::io::Error),
242 }
243}
244pub use error::Error;