2 store_impl
::file
::{transaction::PackedRefs, Transaction}
,
3 transaction
::{Change, LogChange, RefEdit, RefLog}
,
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.
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.
18 /// In this stage, we perform the following operations:
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
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())
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
!(
36 PackedRefs
::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(_
)
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),
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.
56 PreviousValue
::ExistingMustMatch(Target
::Peeled(oid
)) => {
57 Some((Some(gix_hash
::ObjectId
::null(oid
.kind())), oid
))
62 Target
::Peeled(new_oid
) => {
63 let previous
= match expected
{
64 PreviousValue
::MustExistAndMatch(Target
::Peeled(oid
)) => Some(oid
.to_owned()),
67 .or(change
.leaf_referent_previous_oid
);
68 Some((previous
, new_oid
))
71 if let Some((previous
, new_oid
)) = log_update
{
72 let do_update
= previous
.as_ref().map_or(true, |previous
| previous
!= new_oid
);
74 self.store
.reflog_create_or_append(
75 change
.update
.name
.as_ref(),
80 log
.force_create_reflog
,
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
88 if delete_loose_refs
&& matches
!(new
, Target
::Peeled(_
)) {
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
))
104 if let Some(err
) = err
{
105 return Err(Error
::LockCommit
{
107 full_name
: change
.name(),
113 Change
::Delete { .. }
=> {}
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
{
129 full_name
: change
.name(),
133 gix_tempfile
::remove_dir
::empty_upward_until_boundary(
134 reflog_path
.parent().expect("never without parent"),
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();
150 for change
in &mut updates
{
151 let take_lock_and_delete
= match &change
.update
.change
{
153 log
: LogChange { mode, .. }
,
156 } => delete_loose_refs
&& *mode
== RefLog
::AndReference
&& matches
!(new
, Target
::Peeled(_
)),
157 Change
::Delete { log: mode, .. }
=> *mode
== RefLog
::AndReference
,
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
{
166 full_name
: change
.name(),
173 Ok(updates
.into_iter().map(|edit
| edit
.update
).collect())
177 use gix_object
::bstr
::BString
;
179 use crate::store_impl
::{file, packed}
;
181 /// The error returned by various [`Transaction`][super::Transaction] methods.
182 #[derive(Debug, thiserror::Error)]
183 #[allow(missing_docs)]
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),
199 pub use error
::Error
;
201 use crate::transaction
::PreviousValue
;