]> git.proxmox.com Git - rustc.git/blob - vendor/gix-ref/src/store/file/transaction/commit.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / vendor / gix-ref / src / store / file / transaction / commit.rs
1 use crate::{
2 store_impl::file::{transaction::PackedRefs, Transaction},
3 transaction::{Change, LogChange, RefEdit, RefLog},
4 Target,
5 };
6
7 impl<'s, 'p> Transaction<'s, 'p> {
8 /// Make all [prepared][Transaction::prepare()] permanent and return the performed edits which represent the current
9 /// state of the affected refs in the ref store in that instant. Please note that the obtained edits may have been
10 /// adjusted to contain more dependent edits or additional information.
11 /// `committer` is used in the reflog and only if the reflog is actually written, which is why it is optional. Please note
12 /// that if `None` is passed and the reflog needs to be written, the operation will be aborted late and a few refs may have been
13 /// successfully committed already, making clear the non-atomic nature of multi-file edits.
14 ///
15 /// On error the transaction may have been performed partially, depending on the nature of the error, and no attempt to roll back
16 /// partial changes is made.
17 ///
18 /// In this stage, we perform the following operations:
19 ///
20 /// * update the ref log
21 /// * move updated refs into place
22 /// * delete reflogs and empty parent directories
23 /// * delete packed refs
24 /// * delete their corresponding reference (if applicable)
25 /// along with empty parent directories
26 ///
27 /// Note that transactions will be prepared automatically as needed.
28 pub fn commit<'a>(self, committer: impl Into<Option<gix_actor::SignatureRef<'a>>>) -> Result<Vec<RefEdit>, Error> {
29 self.commit_inner(committer.into())
30 }
31
32 fn commit_inner(self, committer: Option<gix_actor::SignatureRef<'_>>) -> Result<Vec<RefEdit>, Error> {
33 let mut updates = self.updates.expect("BUG: must call prepare before commit");
34 let delete_loose_refs = matches!(
35 self.packed_refs,
36 PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(_)
37 );
38
39 // Perform updates first so live commits remain referenced
40 for change in &mut updates {
41 assert!(!change.update.deref, "Deref mode is turned into splits and turned off");
42 match &change.update.change {
43 // reflog first, then reference
44 Change::Update { log, new, expected } => {
45 let lock = change.lock.take();
46 let (update_ref, update_reflog) = match log.mode {
47 RefLog::Only => (false, true),
48 RefLog::AndReference => (true, true),
49 };
50 if update_reflog {
51 let log_update = match new {
52 Target::Symbolic(_) => {
53 // no reflog for symref changes, unless the ref is new and we can obtain a peeled id
54 // identified by the expectation of what could be there, as is the case when cloning.
55 match expected {
56 PreviousValue::ExistingMustMatch(Target::Peeled(oid)) => {
57 Some((Some(gix_hash::ObjectId::null(oid.kind())), oid))
58 }
59 _ => None,
60 }
61 }
62 Target::Peeled(new_oid) => {
63 let previous = match expected {
64 PreviousValue::MustExistAndMatch(Target::Peeled(oid)) => Some(oid.to_owned()),
65 _ => None,
66 }
67 .or(change.leaf_referent_previous_oid);
68 Some((previous, new_oid))
69 }
70 };
71 if let Some((previous, new_oid)) = log_update {
72 let do_update = previous.as_ref().map_or(true, |previous| previous != new_oid);
73 if do_update {
74 self.store.reflog_create_or_append(
75 change.update.name.as_ref(),
76 previous,
77 new_oid,
78 committer,
79 log.message.as_ref(),
80 log.force_create_reflog,
81 )?;
82 }
83 }
84 }
85 // Don't do anything else while keeping the lock after potentially updating the reflog.
86 // We delay deletion of the reference and dropping the lock to after the packed-refs were
87 // safely written.
88 if delete_loose_refs && matches!(new, Target::Peeled(_)) {
89 change.lock = lock;
90 continue;
91 }
92 if update_ref {
93 if let Some(Err(err)) = lock.map(|l| l.commit()) {
94 // TODO: when Kind::IsADirectory becomes stable, use that.
95 let err = if err.instance.resource_path().is_dir() {
96 gix_tempfile::remove_dir::empty_depth_first(err.instance.resource_path())
97 .map_err(|io_err| std::io::Error::new(std::io::ErrorKind::Other, io_err))
98 .and_then(|_| err.instance.commit().map_err(|err| err.error))
99 .err()
100 } else {
101 Some(err.error)
102 };
103
104 if let Some(err) = err {
105 return Err(Error::LockCommit {
106 source: err,
107 full_name: change.name(),
108 });
109 }
110 };
111 }
112 }
113 Change::Delete { .. } => {}
114 }
115 }
116
117 for change in &mut updates {
118 let (reflog_root, relative_name) = self.store.reflog_base_and_relative_path(change.update.name.as_ref());
119 match &change.update.change {
120 Change::Update { .. } => {}
121 Change::Delete { .. } => {
122 // Reflog deletion happens first in case it fails a ref without log is less terrible than
123 // a log without a reference.
124 let reflog_path = reflog_root.join(relative_name);
125 if let Err(err) = std::fs::remove_file(&reflog_path) {
126 if err.kind() != std::io::ErrorKind::NotFound {
127 return Err(Error::DeleteReflog {
128 source: err,
129 full_name: change.name(),
130 });
131 }
132 } else {
133 gix_tempfile::remove_dir::empty_upward_until_boundary(
134 reflog_path.parent().expect("never without parent"),
135 &reflog_root,
136 )
137 .ok();
138 }
139 }
140 }
141 }
142
143 if let Some(t) = self.packed_transaction {
144 t.commit().map_err(Error::PackedTransactionCommit)?;
145 // Always refresh ourselves right away to avoid races. We ignore errors as there may be many reasons this fails, and it's not
146 // critical to be done here. In other words, the pack may be refreshed at a later time and then it might work.
147 self.store.force_refresh_packed_buffer().ok();
148 }
149
150 for change in &mut updates {
151 let take_lock_and_delete = match &change.update.change {
152 Change::Update {
153 log: LogChange { mode, .. },
154 new,
155 ..
156 } => delete_loose_refs && *mode == RefLog::AndReference && matches!(new, Target::Peeled(_)),
157 Change::Delete { log: mode, .. } => *mode == RefLog::AndReference,
158 };
159 if take_lock_and_delete {
160 let lock = change.lock.take();
161 let reference_path = self.store.reference_path(change.update.name.as_ref());
162 if let Err(err) = std::fs::remove_file(reference_path) {
163 if err.kind() != std::io::ErrorKind::NotFound {
164 return Err(Error::DeleteReference {
165 err,
166 full_name: change.name(),
167 });
168 }
169 }
170 drop(lock)
171 }
172 }
173 Ok(updates.into_iter().map(|edit| edit.update).collect())
174 }
175 }
176 mod error {
177 use gix_object::bstr::BString;
178
179 use crate::store_impl::{file, packed};
180
181 /// The error returned by various [`Transaction`][super::Transaction] methods.
182 #[derive(Debug, thiserror::Error)]
183 #[allow(missing_docs)]
184 pub enum Error {
185 #[error("The packed-ref transaction could not be committed")]
186 PackedTransactionCommit(#[source] packed::transaction::commit::Error),
187 #[error("Edit preprocessing failed with error")]
188 PreprocessingFailed { source: std::io::Error },
189 #[error("The change for reference {full_name:?} could not be committed")]
190 LockCommit { source: std::io::Error, full_name: BString },
191 #[error("The reference {full_name} could not be deleted")]
192 DeleteReference { full_name: BString, err: std::io::Error },
193 #[error("The reflog of reference {full_name:?} could not be deleted")]
194 DeleteReflog { full_name: BString, source: std::io::Error },
195 #[error("The reflog could not be created or updated")]
196 CreateOrUpdateRefLog(#[from] file::log::create_or_update::Error),
197 }
198 }
199 pub use error::Error;
200
201 use crate::transaction::PreviousValue;