Skip to main content

git_policy_cli/
commit_facts.rs

1//! What:
2//!  The repository facts of a commit transaction:
3//!  every rule fact is read from the real repository, and the candidate files are read from the private index
4//!  and the shadow object store against the recorded base.
5//! Why:
6//!  The incumbent's rules (`commit-only`, `commit-index`, `commit-sequencer`) ask real Git about the real index and
7//!  the real sequencer state, while the content policies read the private commit index that the transaction corrects.
8//!  Pointing every fact at the private index would answer the rules about a state the user never staged.
9//!
10//! In TS you'd write (pseudocode):
11//! ```ts
12//! const facts = { ...realRepositoryFacts, ...createPrivateIndexFacts({ indexPath, paths, baseRevision, objectDirectory }) };
13//! ```
14
15/// Errors of the candidate layer.
16use super::candidate_error::CandidateError;
17/// The prediction entry point and the request kinds.
18use super::candidate_prediction::{CandidateRequest, PreparedCandidates, prepare_candidates};
19/// Rule facts.
20use super::repository_facts::RepositoryFacts;
21use super::repository_location::RepositoryLocation;
22use super::rule_commit_index::IndexVsHead;
23use super::rule_commit_sequencer::SequencerState;
24use super::rule_worktree_dirty::DirtyScope;
25use super::transaction_git::GitContext;
26use std::ffi::{OsStr, OsString};
27use std::path::Path;
28
29/// The variable that names the index a Git child reads.
30const INDEX_FILE_VARIABLE: &str = "GIT_INDEX_FILE";
31/// The variable that names the object store a Git child writes to.
32const OBJECT_DIRECTORY_VARIABLE: &str = "GIT_OBJECT_DIRECTORY";
33
34/// What:
35///  Rule facts from the wrapped real repository and candidate reads from a private index.
36/// Why:
37///   The private index changes from one replay to the next, so the facts keep it as a replaceable value.
38///
39/// In TS you'd write (pseudocode):
40/// ```ts
41/// type CommitFacts = { rules: RepositoryFacts; privateIndex: { indexPath: string; objectDirectory: string } };
42/// ```
43pub struct PrivateIndexFacts<F: RepositoryFacts> {
44    /// The facts every rule reads.
45    inner: F,
46    /// `GIT_INDEX_FILE` and `GIT_OBJECT_DIRECTORY` of the candidate reads.
47    private: Vec<(OsString, OsString)>,
48}
49
50impl<F: RepositoryFacts> PrivateIndexFacts<F> {
51    /// What:
52    ///  Wrap rule facts with the private index and the object store the candidates are read from.
53    /// Why:
54    ///   The candidate prediction copies the named index, so reading never changes the transaction's index.
55    ///
56    /// In TS you'd write (pseudocode):
57    /// ```ts
58    /// createPrivateIndexFacts({ indexPath, objectDirectory });
59    /// ```
60    pub fn new(inner: F, index: &Path, object_directory: &Path) -> Self {
61        return PrivateIndexFacts {
62            inner,
63            private: private_entries(index, object_directory),
64        };
65    }
66
67    /// What:
68    ///  Read the candidates from another private index from now on.
69    /// Why:
70    ///   A replay validates its own merged tree in `replay-<n>/commit.index`.
71    ///
72    /// In TS you'd write (pseudocode):
73    /// ```ts
74    /// facts = createPrivateIndexFacts({ indexPath: replayIndexPath, objectDirectory });
75    /// ```
76    pub fn use_index(&mut self, index: &Path, object_directory: &Path) {
77        self.private = private_entries(index, object_directory);
78    }
79}
80
81/// What:
82///  The two child variables that select a private index and its object store.
83/// Why:
84///   Both candidate reads and corrections must name the same pair.
85///
86/// In TS you'd write (pseudocode):
87/// ```ts
88/// ({ GIT_INDEX_FILE: indexPath, GIT_OBJECT_DIRECTORY: objectDirectory })
89/// ```
90fn private_entries(index: &Path, object_directory: &Path) -> Vec<(OsString, OsString)> {
91    return vec![
92        (OsString::from(INDEX_FILE_VARIABLE), index.as_os_str().to_os_string()),
93        (
94            OsString::from(OBJECT_DIRECTORY_VARIABLE),
95            object_directory.as_os_str().to_os_string(),
96        ),
97    ];
98}
99
100impl<F: RepositoryFacts> RepositoryFacts for PrivateIndexFacts<F> {
101    fn location(&mut self) -> Result<RepositoryLocation, String> {
102        return self.inner.location();
103    }
104
105    fn index_vs_head(&mut self) -> Result<IndexVsHead, String> {
106        return self.inner.index_vs_head();
107    }
108
109    fn worktree_dirty(&mut self, scope: DirtyScope) -> Result<bool, String> {
110        return self.inner.worktree_dirty(scope);
111    }
112
113    fn sequencer_state(&mut self) -> Result<SequencerState, String> {
114        return self.inner.sequencer_state();
115    }
116
117    fn remote_guess_creates_branch(&mut self, target: &OsStr) -> Result<bool, String> {
118        return self.inner.remote_guess_creates_branch(target);
119    }
120
121    fn candidates(&mut self, request: &CandidateRequest) -> Result<PreparedCandidates, CandidateError> {
122        let context: GitContext = self.inner.transaction_context();
123        let mut overlay: Vec<(OsString, OsString)> = context.overlay.clone();
124        overlay.extend(self.private.iter().cloned());
125        return prepare_candidates(
126            context.real_git.as_path(),
127            context.global_prefix.as_slice(),
128            overlay.as_slice(),
129            request,
130        );
131    }
132
133    fn asked_location(&self) -> Option<Result<RepositoryLocation, String>> {
134        return self.inner.asked_location();
135    }
136
137    fn transaction_context(&self) -> GitContext {
138        return self.inner.transaction_context();
139    }
140}
141
142/// A borrowed provider answers like the provider it borrows.
143impl<F: RepositoryFacts> RepositoryFacts for &mut F {
144    fn location(&mut self) -> Result<RepositoryLocation, String> {
145        return (**self).location();
146    }
147
148    fn index_vs_head(&mut self) -> Result<IndexVsHead, String> {
149        return (**self).index_vs_head();
150    }
151
152    fn worktree_dirty(&mut self, scope: DirtyScope) -> Result<bool, String> {
153        return (**self).worktree_dirty(scope);
154    }
155
156    fn sequencer_state(&mut self) -> Result<SequencerState, String> {
157        return (**self).sequencer_state();
158    }
159
160    fn remote_guess_creates_branch(&mut self, target: &OsStr) -> Result<bool, String> {
161        return (**self).remote_guess_creates_branch(target);
162    }
163
164    fn candidates(&mut self, request: &CandidateRequest) -> Result<PreparedCandidates, CandidateError> {
165        return (**self).candidates(request);
166    }
167
168    fn asked_location(&self) -> Option<Result<RepositoryLocation, String>> {
169        return (**self).asked_location();
170    }
171
172    fn transaction_context(&self) -> GitContext {
173        return (**self).transaction_context();
174    }
175}
176
177/// Controls for this module stay out of the release executable.
178#[cfg(test)]
179#[path = "commit_facts_tests.rs"]
180mod tests;