git_policy_cli/
conclusion_copy.rs1use super::conclusion_cleanup::{
18 CONCLUSION_COPY_DIRECTORY, CONCLUSION_STATE_FILES, SEQUENCER_DIRECTORY, STORE_RECORD_FILENAME,
19 is_store_held, resolve_pseudoref,
20};
21use super::private_storage::{create_private_directory, write_private_file};
23use super::recovery_files::{path_present, read_optional};
25use super::json_record::ObjectWriter;
27use super::shadow_git::shadow_request;
29use super::transaction_git::{GitContext, GitFailure, GitRequest, run_git_checked};
30use super::transaction_journal::RefFormat;
32use std::path::{Path, PathBuf};
34
35fn copy_tree(source: &Path, destination: &Path) -> std::io::Result<()> {
45 create_private_directory(destination)?;
46 for entry_result in std::fs::read_dir(source)? {
47 let entry: std::fs::DirEntry = entry_result?;
48 let target: PathBuf = destination.join(entry.file_name());
49 let kind: std::fs::FileType = entry.file_type()?;
50 if kind.is_dir() {
51 copy_tree(entry.path().as_path(), target.as_path())?;
52 } else if kind.is_symlink() {
53 #[cfg(unix)]
54 std::os::unix::fs::symlink(std::fs::read_link(entry.path())?, &target)?;
55 #[cfg(not(unix))]
57 std::fs::copy(entry.path(), &target).map(|_| ())?;
58 } else {
59 std::fs::copy(entry.path(), &target)?;
60 }
61 }
62 return Ok(());
63}
64
65fn owning_pseudoref(context: &GitContext, cwd: &Path, name: &str) -> Option<String> {
75 let mut request: GitRequest = GitRequest::new(cwd, &["rev-parse", "--verify", "--quiet", name]);
76 request.without_prefix = true;
77 return resolve_pseudoref(context, request);
78}
79
80pub fn copy_conclusion_state(
90 context: &GitContext,
91 cwd: &Path,
92 git_dir: &Path,
93 shadow: &Path,
94 transaction_directory: &Path,
95 ref_format: RefFormat,
96) -> Result<(), GitFailure> {
97 let io = |step: &str, error: std::io::Error| { return GitFailure(format!("{step} failed: {error}")); };
98 let copies: PathBuf = transaction_directory.join(CONCLUSION_COPY_DIRECTORY);
99 create_private_directory(copies.as_path()).map_err(|error| { return io("creating the conclusion copies", error); })?;
100 let mut store_held: ObjectWriter = ObjectWriter::new();
102 for name in CONCLUSION_STATE_FILES {
103 if is_store_held(ref_format, name) {
104 if let Some(value) = owning_pseudoref(context, cwd, name) {
105 run_git_checked(
106 context,
107 &shadow_request(shadow, shadow, &["update-ref", name, value.as_str()]),
108 )?;
109 store_held.string(name, value.as_str());
110 }
111 continue;
112 }
113 let Some(bytes) = read_optional(git_dir.join(name).as_path())
114 .map_err(|error| { return GitFailure(error.0); })?
115 else {
116 continue;
117 };
118 write_private_file(shadow.join(name).as_path(), bytes.as_slice())
119 .map_err(|error| { return io("copying a conclusion entry into the shadow", error); })?;
120 write_private_file(copies.join(name).as_path(), bytes.as_slice())
121 .map_err(|error| { return io("copying a conclusion entry for comparison", error); })?;
122 }
123 let record: String = store_held.finish();
124 write_private_file(
125 copies.join(STORE_RECORD_FILENAME).as_path(),
126 format!("{record}\n").as_bytes(),
127 )
128 .map_err(|error| { return io("writing the store-held record", error); })?;
129 let sequencer: PathBuf = git_dir.join(SEQUENCER_DIRECTORY);
130 if path_present(sequencer.as_path()).map_err(|error| { return GitFailure(error.0); })? {
131 copy_tree(sequencer.as_path(), shadow.join(SEQUENCER_DIRECTORY).as_path())
132 .map_err(|error| { return io("copying the sequencer into the shadow", error); })?;
133 copy_tree(sequencer.as_path(), copies.join(SEQUENCER_DIRECTORY).as_path())
134 .map_err(|error| { return io("copying the sequencer for comparison", error); })?;
135 }
136 return Ok(());
137}
138
139#[cfg(test)]
141#[path = "conclusion_copy_tests.rs"]
142mod tests;