use super::command_conflict_side::{SideRequest, conflict_side_request};
use super::conflict_sides::{
Operation, SAVED_DIRECTORY, SaveFailure, SavedCopy, SideDecision, clean_paths_note, conflicted_paths, decide_sides, operation_in_progress,
save_copies, saved_notice,
};
use super::discarded_prune::{Limits, directory_name_of, prune};
use super::global_arguments::command_tokens;
use super::private_storage::read_regular_file;
use super::repo_read_index::{IndexEntry, parse_index};
use super::repo_read_layout::{RepoLayout, read_layout};
use super::transaction_git::GitContext;
use std::ffi::OsString;
const INDEX_LIMIT_BYTES: u64 = 1 << 30;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SideVerdict {
NotApplicable,
Allowed {
notice: String,
copies: Vec<SavedCopy>,
},
Refused(String),
CopyFailed(String),
}
fn copy_failed_text(failure: &SaveFailure) -> String {
return format!(
"cli-git: this command would replace a conflicted file, and your copy of it could not be saved first ({}: {}), so it is \
rejected and nothing was changed. Free the space or fix the permissions of the Git directory and run it again, or \
save the file yourself and pass `--no-enforce-worktree` directly after the subcommand.",
failure.subject, failure.reason
);
}
fn read_conflicts(layout: &RepoLayout) -> Result<std::collections::BTreeSet<Vec<u8>>, String> {
let declined = || -> String {
return String::from(
" The index could not be read in process (it is split, sparse, unreadable, or chosen by `GIT_INDEX_FILE`), so no \
file can be shown to be conflicted.",
);
};
if std::env::var_os("GIT_INDEX_FILE").is_some() {
return Err(declined());
}
let bytes: Vec<u8> = read_regular_file(layout.git_dir.join("index").as_path(), INDEX_LIMIT_BYTES).map_err(|_| { return declined(); })?;
let entries: Vec<IndexEntry> = parse_index(bytes.as_slice(), layout.object_format).ok_or_else(declined)?;
return Ok(conflicted_paths(entries.as_slice()));
}
fn prune_after_save(layout: &RepoLayout, copies: &[SavedCopy], milliseconds: i64, limits: Limits) {
let saved_root: std::path::PathBuf = layout.common_dir.join(SAVED_DIRECTORY);
let keep: Option<String> = copies.iter().find_map(|copy| { return copy.saved_to.as_deref().and_then(|place| { return directory_name_of(saved_root.as_path(), place); }); });
if keep.is_none() {
return;
}
let _removed = prune(layout.common_dir.as_path(), milliseconds, limits, keep.as_deref());
}
pub fn judge_conflict_sides(context: &GitContext, arguments: &[OsString], milliseconds: i64, limits: Limits) -> SideVerdict {
let Some((word, region)) = command_tokens(arguments) else {
return SideVerdict::NotApplicable;
};
let Some(request): Option<SideRequest> = conflict_side_request(word.as_encoded_bytes(), region) else {
return SideVerdict::NotApplicable;
};
let Some(layout) = read_layout(context.global_prefix.as_slice(), context.overlay.as_slice()) else {
return SideVerdict::NotApplicable;
};
let Some(operation): Option<Operation> = operation_in_progress(layout.git_dir.as_path()) else {
return SideVerdict::NotApplicable;
};
let conflicted = match read_conflicts(&layout) {
Ok(found) => found,
Err(note) => return SideVerdict::Refused(note),
};
match decide_sides(request.paths.as_slice(), layout.prefix.as_slice(), layout.worktree_root.as_path(), &conflicted) {
SideDecision::Refuse(others) => return SideVerdict::Refused(clean_paths_note(others.as_slice(), operation)),
SideDecision::Allow(paths) => {
match save_copies(layout.worktree_root.as_path(), layout.common_dir.as_path(), paths.as_slice(), milliseconds) {
Ok(copies) => {
prune_after_save(&layout, copies.as_slice(), milliseconds, limits);
return SideVerdict::Allowed { notice: saved_notice(copies.as_slice()), copies };
}
Err(failure) => return SideVerdict::CopyFailed(copy_failed_text(&failure)),
}
}
}
}