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