use crate::candidate_error::CandidateError;
use crate::candidate_prediction::{CandidateRequest, PreparedCandidates, prepare_candidates};
use crate::git_metadata::{MetadataOutput, path_from_git_bytes, run_metadata_git};
use crate::repo_read_adapter::{location_of, sequencer_of};
use crate::git_system_config::with_real_git;
use crate::repo_read_layout::{RepoLayout, read_layout};
use crate::repository_location::{
RepositoryLocation, location_query_arguments, parse_location_output,
};
use crate::rule_branch_worktree::{remote_guess_creates_branch, remote_guess_query_arguments};
use crate::rule_commit_index::{IndexVsHead, index_query_arguments, index_state_from_exit};
use crate::rule_worktree_dirty::{DirtyScope, dirty_from_output, dirty_query_arguments};
use crate::rule_commit_sequencer::{
SequencerFacts, SequencerState, sequencer_head_paths, sequencer_query_arguments,
sequencer_state, sequencer_state_when_query_fails,
};
use crate::transaction_git::GitContext;
use crate::worktree_identity::{common_directory, git_directory};
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
pub trait RepositoryFacts {
fn location(&mut self) -> Result<RepositoryLocation, String>;
fn index_vs_head(&mut self) -> Result<IndexVsHead, String>;
fn worktree_dirty(&mut self, scope: DirtyScope) -> Result<bool, String>;
fn sequencer_state(&mut self) -> Result<SequencerState, String>;
fn remote_guess_creates_branch(&mut self, target: &OsStr) -> Result<bool, String>;
fn candidates(
&mut self,
request: &CandidateRequest,
) -> Result<PreparedCandidates, CandidateError>;
fn asked_location(&self) -> Option<Result<RepositoryLocation, String>>;
fn known_common_dir(&mut self) -> Option<PathBuf> {
let asked: Option<Result<RepositoryLocation, String>> = self.asked_location();
return asked.and_then(Result::ok).and_then(|found| { return common_directory(&found.identity).map(Path::to_path_buf); });
}
fn known_git_dir(&mut self) -> Option<PathBuf> {
let asked: Option<Result<RepositoryLocation, String>> = self.asked_location();
return asked.and_then(Result::ok).and_then(|found| { return git_directory(&found.identity).map(Path::to_path_buf); });
}
fn transaction_context(&self) -> GitContext;
}
#[derive(Clone, Debug)]
pub struct GitFacts {
real_git: PathBuf,
global_prefix: Vec<OsString>,
overlay: Vec<(OsString, OsString)>,
location: Option<Result<RepositoryLocation, String>>,
layout: Option<Option<RepoLayout>>,
pub queries: usize,
}
pub fn git_facts(
real_git: &Path,
global_prefix: &[OsString],
overlay: &[(OsString, OsString)],
) -> GitFacts {
return GitFacts {
real_git: real_git.to_path_buf(),
global_prefix: global_prefix.to_vec(),
overlay: overlay.to_vec(),
location: None,
layout: None,
queries: 0,
};
}
fn not_started(real_git: &Path, fact: &str, error: &std::io::Error) -> String {
return format!(
"cli-git could not start {} to read {fact}: {error}",
real_git.display()
);
}
impl GitFacts {
fn query(&mut self, arguments: Vec<OsString>, fact: &str) -> Result<MetadataOutput, String> {
self.queries += 1;
match run_metadata_git(
self.real_git.as_path(),
arguments.as_slice(),
self.overlay.as_slice(),
) {
Ok(output) => return Ok(output),
Err(error) => return Err(not_started(self.real_git.as_path(), fact, &error)),
}
}
fn layout(&mut self) -> Option<&RepoLayout> {
if self.layout.is_none() {
self.layout = Some(read_layout(self.global_prefix.as_slice(), with_real_git(self.overlay.as_slice(), self.real_git.as_path()).as_slice()));
}
return self.layout.as_ref().and_then(Option::as_ref);
}
}
impl RepositoryFacts for GitFacts {
fn location(&mut self) -> Result<RepositoryLocation, String> {
if let Some(known) = &self.location {
return known.clone();
}
let read: Option<RepositoryLocation> = self.layout().map(location_of);
if let Some(found) = read {
self.location = Some(Ok(found.clone()));
return Ok(found);
}
let arguments: Vec<OsString> = location_query_arguments(self.global_prefix.as_slice());
let answer: Result<RepositoryLocation, String> =
match self.query(arguments, "the repository location") {
Ok(output) => read_location(&output),
Err(message) => Err(message),
};
self.location = Some(answer.clone());
return answer;
}
fn index_vs_head(&mut self) -> Result<IndexVsHead, String> {
let arguments: Vec<OsString> = index_query_arguments(self.global_prefix.as_slice());
let output: MetadataOutput = self.query(arguments, "the index state")?;
return Ok(index_state_from_exit(output.code));
}
fn worktree_dirty(&mut self, scope: DirtyScope) -> Result<bool, String> {
let arguments: Vec<OsString> = dirty_query_arguments(self.global_prefix.as_slice(), scope);
let output: MetadataOutput = self.query(arguments, "the worktree status")?;
return dirty_from_output(&output);
}
fn sequencer_state(&mut self) -> Result<SequencerState, String> {
if let Some(state) = self.layout().and_then(sequencer_of) {
return Ok(state);
}
let arguments: Vec<OsString> = sequencer_query_arguments(self.global_prefix.as_slice());
let output: MetadataOutput = self.query(arguments, "the Git directory")?;
if !output.success {
return Ok(sequencer_state_when_query_fails());
}
let paths: Vec<Vec<u8>> = match sequencer_head_paths(output.stdout.as_slice()) {
Ok(found) => found,
Err(error) => return Err(error.to_string()),
};
return Ok(sequencer_state(SequencerFacts {
merge_head: head_file_exists(paths[0].as_slice()),
cherry_pick_head: head_file_exists(paths[1].as_slice()),
revert_head: head_file_exists(paths[2].as_slice()),
}));
}
fn remote_guess_creates_branch(&mut self, target: &OsStr) -> Result<bool, String> {
let arguments: Vec<OsString> =
remote_guess_query_arguments(self.global_prefix.as_slice(), target);
let output: MetadataOutput = self.query(arguments, "branch names")?;
if !output.success {
return Ok(false);
}
return Ok(remote_guess_creates_branch(
output.stdout.as_slice(),
target.as_encoded_bytes(),
));
}
fn candidates(
&mut self,
request: &CandidateRequest,
) -> Result<PreparedCandidates, CandidateError> {
return prepare_candidates(
self.real_git.as_path(),
self.global_prefix.as_slice(),
self.overlay.as_slice(),
request,
);
}
fn asked_location(&self) -> Option<Result<RepositoryLocation, String>> {
return self.location.clone();
}
fn known_common_dir(&mut self) -> Option<PathBuf> {
if let Some(Ok(found)) = &self.location {
return common_directory(&found.identity).map(Path::to_path_buf);
}
return self.layout().map(|found| { return found.common_dir.clone(); });
}
fn known_git_dir(&mut self) -> Option<PathBuf> {
if let Some(Ok(found)) = &self.location {
return git_directory(&found.identity).map(Path::to_path_buf);
}
return self.layout().map(|found| { return found.git_dir.clone(); });
}
fn transaction_context(&self) -> GitContext {
return GitContext {
real_git: self.real_git.clone(),
overlay: self.overlay.clone(),
global_prefix: self.global_prefix.clone(),
common_dir: match &self.location {
Some(Ok(found)) => common_directory(&found.identity).map(Path::to_path_buf),
Some(Err(_)) | None => None,
},
};
}
}
fn read_location(output: &MetadataOutput) -> Result<RepositoryLocation, String> {
match parse_location_output(output.success, output.stdout.as_slice()) {
Ok(location) => return Ok(location),
Err(error) => return Err(error.to_string()),
}
}
fn head_file_exists(path: &[u8]) -> bool {
match path_from_git_bytes(path) {
Some(found) => return std::fs::symlink_metadata(found).is_ok(),
None => return false,
}
}
#[cfg(test)]
#[path = "repository_facts_tests.rs"]
mod tests;