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