git_atomic/core/
effect.rs1use crate::cli::output::Printer;
2use crate::core::{Error, GitError};
3use std::path::PathBuf;
4
5#[derive(Debug)]
7pub struct PlannedRefEdit {
8 pub ref_name: String,
9 pub new_id: gix::ObjectId,
10 pub previous: Option<gix::ObjectId>,
11 pub component: String,
12 pub created: bool,
13}
14
15#[derive(Debug)]
17pub enum Effect {
18 RefTransaction {
20 repo_path: PathBuf,
21 edits: Vec<PlannedRefEdit>,
22 },
23 Push {
25 remote: String,
26 branches: Vec<String>,
27 },
28 WriteFile {
30 path: PathBuf,
31 content: String,
32 structured: Option<serde_json::Value>,
35 },
36}
37
38pub fn execute(
40 repo: Option<&gix::Repository>,
41 effects: &[Effect],
42 dry_run: bool,
43 printer: &Printer,
44) -> Result<(), Error> {
45 for effect in effects {
46 if dry_run {
47 printer.print_effect_preview(effect);
48 } else {
49 run_effect(repo, effect)?;
50 }
51 }
52 Ok(())
53}
54
55fn run_effect(repo: Option<&gix::Repository>, effect: &Effect) -> Result<(), Error> {
56 match effect {
57 Effect::RefTransaction { edits, .. } => {
58 let repo =
59 repo.ok_or_else(|| Error::General("RefTransaction requires a repository".into()))?;
60
61 let mut gix_edits: Vec<gix::refs::transaction::RefEdit> = Vec::new();
62 for e in edits {
63 let target = gix::refs::Target::Object(e.new_id);
64 let expected = match e.previous {
65 Some(id) => gix::refs::transaction::PreviousValue::MustExistAndMatch(
66 gix::refs::Target::Object(id),
67 ),
68 None => gix::refs::transaction::PreviousValue::MustNotExist,
69 };
70
71 gix_edits.push(gix::refs::transaction::RefEdit {
72 change: gix::refs::transaction::Change::Update {
73 log: gix::refs::transaction::LogChange {
74 mode: gix::refs::transaction::RefLog::AndReference,
75 force_create_reflog: false,
76 message: "git-atomic: commit".into(),
77 },
78 expected,
79 new: target,
80 },
81 name: gix::refs::FullName::try_from(e.ref_name.clone()).map_err(|err| {
82 GitError::RefUpdate {
83 branch: e.ref_name.clone(),
84 reason: format!("invalid ref name: {err}"),
85 }
86 })?,
87 deref: false,
88 });
89 }
90
91 if !gix_edits.is_empty() {
92 repo.edit_references(gix_edits)
93 .map_err(|e| GitError::RefUpdate {
94 branch: "batch update".into(),
95 reason: e.to_string(),
96 })?;
97 }
98 }
99 Effect::Push { remote, branches } => {
100 let mut cmd = std::process::Command::new("git");
101 cmd.arg("push").arg(remote);
102 for b in branches {
103 cmd.arg(b);
104 }
105 let status = cmd
106 .status()
107 .map_err(|e| Error::General(format!("failed to run git push: {e}")))?;
108 if !status.success() {
109 return Err(Error::General(format!(
110 "git push exited with status {}",
111 status
112 )));
113 }
114 }
115 Effect::WriteFile { path, content, .. } => {
116 if let Some(parent) = path.parent() {
117 std::fs::create_dir_all(parent).map_err(|e| {
118 Error::General(format!(
119 "failed to create directory {}: {e}",
120 parent.display()
121 ))
122 })?;
123 }
124 std::fs::write(path, content)
125 .map_err(|e| Error::General(format!("failed to write {}: {e}", path.display())))?;
126 }
127 }
128 Ok(())
129}