gix_ref/store/file/loose/
reflog.rs1use std::{io::Read, path::PathBuf};
2
3use crate::{
4 FullNameRef,
5 store_impl::{file, file::log},
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::{ObjectId, oid};
95 use gix_object::bstr::BStr;
96
97 use crate::store_impl::{file, file::WriteReflog};
98
99 impl file::Store {
100 pub(crate) fn reflog_create_or_append(
101 &self,
102 name: &FullNameRef,
103 previous_oid: Option<ObjectId>,
104 new: &oid,
105 committer: Option<gix_actor::SignatureRef<'_>>,
106 message: &BStr,
107 mut force_create_reflog: bool,
108 ) -> Result<(), Error> {
109 let (reflog_base, full_name) = self.reflog_base_and_relative_path(name);
110 match self.write_reflog {
111 WriteReflog::Normal | WriteReflog::Always => {
112 if self.write_reflog == WriteReflog::Always {
113 force_create_reflog = true;
114 }
115 let mut options = std::fs::OpenOptions::new();
116 options.append(true).read(false);
117 let log_path = reflog_base.join(&full_name);
118
119 if force_create_reflog || self.should_autocreate_reflog(&full_name) {
120 let parent_dir = log_path.parent().expect("always with parent directory");
121 gix_tempfile::create_dir::all(parent_dir, Default::default()).map_err(|err| {
122 Error::CreateLeadingDirectories {
123 source: err,
124 reflog_directory: parent_dir.to_owned(),
125 }
126 })?;
127 options.create(true);
128 }
129
130 let file_for_appending = match options.open(&log_path) {
131 Ok(f) => Some(f),
132 Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
133 Err(err) => {
134 if log_path.is_dir() {
136 gix_tempfile::remove_dir::empty_depth_first(log_path.clone())
137 .and_then(|_| options.open(&log_path))
138 .map(Some)
139 .map_err(|_| Error::Append {
140 source: err,
141 reflog_path: self.reflog_path(name),
142 })?
143 } else {
144 return Err(Error::Append {
145 source: err,
146 reflog_path: log_path,
147 });
148 }
149 }
150 };
151
152 if let Some(mut file) = file_for_appending {
153 let committer = committer.ok_or(Error::MissingCommitter)?;
154 write!(file, "{} {} ", previous_oid.unwrap_or_else(|| new.kind().null()), new)
155 .and_then(|_| committer.trim().write_to(&mut file))
156 .and_then(|_| {
157 if !message.is_empty() {
158 writeln!(file, "\t{message}")
159 } else {
160 writeln!(file)
161 }
162 })
163 .map_err(|err| Error::Append {
164 source: err,
165 reflog_path: self.reflog_path(name),
166 })?;
167 }
168 Ok(())
169 }
170 WriteReflog::Disable => Ok(()),
171 }
172 }
173
174 fn should_autocreate_reflog(&self, full_name: &Path) -> bool {
175 full_name.starts_with("refs/heads/")
176 || full_name.starts_with("refs/remotes/")
177 || full_name.starts_with("refs/notes/")
178 || full_name.starts_with("refs/worktree/") || full_name == Path::new("HEAD")
180 }
181
182 pub(in crate::store_impl::file) fn reflog_base_and_relative_path<'a>(
184 &self,
185 name: &'a FullNameRef,
186 ) -> (PathBuf, Cow<'a, Path>) {
187 let is_reflog = true;
188 let (base, name) = self.to_base_dir_and_relative_name(name, is_reflog);
189 (
190 base.join("logs"),
191 match &self.namespace {
192 None => gix_path::to_native_path_on_windows(name.as_bstr()),
193 Some(namespace) => gix_path::to_native_path_on_windows(
194 namespace.to_owned().into_namespaced_name(name).into_inner(),
195 ),
196 },
197 )
198 }
199 }
200
201 #[cfg(test)]
202 mod tests;
203
204 mod error {
205 use std::path::PathBuf;
206
207 #[derive(Debug, thiserror::Error)]
209 #[expect(missing_docs)]
210 pub enum Error {
211 #[error("Could create one or more directories in {reflog_directory:?} to contain reflog file")]
212 CreateLeadingDirectories {
213 source: std::io::Error,
214 reflog_directory: PathBuf,
215 },
216 #[error("Could not open reflog file at {reflog_path:?} for appending")]
217 Append {
218 source: std::io::Error,
219 reflog_path: PathBuf,
220 },
221 #[error("reflog message must not contain newlines")]
222 MessageWithNewlines,
223 #[error("reflog messages need a committer which isn't set")]
224 MissingCommitter,
225 }
226 }
227 pub use error::Error;
228
229 use crate::FullNameRef;
230}
231
232mod error {
233 #[derive(Debug, thiserror::Error)]
235 #[expect(missing_docs)]
236 pub enum Error {
237 #[error("The reflog name or path is not a valid ref name")]
238 RefnameValidation(#[from] crate::name::Error),
239 #[error("The reflog file could not read")]
240 Io(#[from] std::io::Error),
241 }
242}
243pub use error::Error;