git_ref/transaction/
ext.rs

1use git_object::bstr::BString;
2
3use crate::{
4    transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog, Target},
5    PartialNameRef,
6};
7
8/// An extension trait to perform commonly used operations on edits across different ref stores.
9pub trait RefEditsExt<T>
10where
11    T: std::borrow::Borrow<RefEdit> + std::borrow::BorrowMut<RefEdit>,
12{
13    /// Return true if each ref `name` has exactly one `edit` across multiple ref edits
14    fn assure_one_name_has_one_edit(&self) -> Result<(), BString>;
15
16    /// Split all symbolic refs into updates for the symbolic ref as well as all their referents if the `deref` flag is enabled.
17    ///
18    /// Note no action is performed if deref isn't specified.
19    fn extend_with_splits_of_symbolic_refs(
20        &mut self,
21        find: impl FnMut(&PartialNameRef) -> Option<Target>,
22        make_entry: impl FnMut(usize, RefEdit) -> T,
23    ) -> Result<(), std::io::Error>;
24
25    /// All processing steps in one and in the correct order.
26    ///
27    /// Users call this to assure derefs are honored and duplicate checks are done.
28    fn pre_process(
29        &mut self,
30        find: impl FnMut(&PartialNameRef) -> Option<Target>,
31        make_entry: impl FnMut(usize, RefEdit) -> T,
32    ) -> Result<(), std::io::Error> {
33        self.extend_with_splits_of_symbolic_refs(find, make_entry)?;
34        self.assure_one_name_has_one_edit().map_err(|name| {
35            std::io::Error::new(
36                std::io::ErrorKind::AlreadyExists,
37                format!("A reference named '{name}' has multiple edits"),
38            )
39        })
40    }
41}
42
43impl<E> RefEditsExt<E> for Vec<E>
44where
45    E: std::borrow::Borrow<RefEdit> + std::borrow::BorrowMut<RefEdit>,
46{
47    fn assure_one_name_has_one_edit(&self) -> Result<(), BString> {
48        let mut names: Vec<_> = self.iter().map(|e| &e.borrow().name).collect();
49        names.sort();
50        match names.windows(2).find(|v| v[0] == v[1]) {
51            Some(name) => Err(name[0].as_bstr().to_owned()),
52            None => Ok(()),
53        }
54    }
55
56    fn extend_with_splits_of_symbolic_refs(
57        &mut self,
58        mut find: impl FnMut(&PartialNameRef) -> Option<Target>,
59        mut make_entry: impl FnMut(usize, RefEdit) -> E,
60    ) -> Result<(), std::io::Error> {
61        let mut new_edits = Vec::new();
62        let mut first = 0;
63        let mut round = 1;
64        loop {
65            for (eid, edit) in self[first..].iter_mut().enumerate().map(|(eid, v)| (eid + first, v)) {
66                let edit = edit.borrow_mut();
67                if !edit.deref {
68                    continue;
69                };
70
71                // we can't tell what happened and we are here because it's a non-existing ref or an invalid one.
72                // In any case, we don't want the following algorithms to try dereffing it and assume they deal with
73                // broken refs gracefully.
74                edit.deref = false;
75                if let Some(Target::Symbolic(referent)) = find(edit.name.as_ref().as_partial_name()) {
76                    new_edits.push(make_entry(
77                        eid,
78                        match &mut edit.change {
79                            Change::Delete {
80                                expected: previous,
81                                log: mode,
82                            } => {
83                                let current_mode = *mode;
84                                *mode = RefLog::Only;
85                                RefEdit {
86                                    change: Change::Delete {
87                                        expected: previous.clone(),
88                                        log: current_mode,
89                                    },
90                                    name: referent,
91                                    deref: true,
92                                }
93                            }
94                            Change::Update { log, expected, new } => {
95                                let current = std::mem::replace(
96                                    log,
97                                    LogChange {
98                                        message: log.message.clone(),
99                                        mode: RefLog::Only,
100                                        force_create_reflog: log.force_create_reflog,
101                                    },
102                                );
103                                let next = std::mem::replace(expected, PreviousValue::Any);
104                                RefEdit {
105                                    change: Change::Update {
106                                        expected: next,
107                                        new: new.clone(),
108                                        log: current,
109                                    },
110                                    name: referent,
111                                    deref: true,
112                                }
113                            }
114                        },
115                    ));
116                }
117            }
118            if new_edits.is_empty() {
119                break Ok(());
120            }
121            if round == 5 {
122                break Err(std::io::Error::new(
123                    std::io::ErrorKind::WouldBlock,
124                    format!("Could not follow all splits after {round} rounds, assuming reference cycle"),
125                ));
126            }
127            round += 1;
128            first = self.len();
129
130            self.append(&mut new_edits);
131        }
132    }
133}