agcodex_chatgpt/
apply_command.rs1use std::path::PathBuf;
2
3use agcodex_common::CliConfigOverrides;
4use agcodex_core::config::Config;
5use agcodex_core::config::ConfigOverrides;
6use clap::Parser;
7
8use crate::chatgpt_token::init_chatgpt_token_from_auth;
9use crate::get_task::GetTaskResponse;
10use crate::get_task::OutputItem;
11use crate::get_task::PrOutputItem;
12use crate::get_task::get_task;
13
14#[derive(Debug, Parser)]
16pub struct ApplyCommand {
17 pub task_id: String,
18
19 #[clap(flatten)]
20 pub config_overrides: CliConfigOverrides,
21}
22pub async fn run_apply_command(
23 apply_cli: ApplyCommand,
24 cwd: Option<PathBuf>,
25) -> anyhow::Result<()> {
26 let config = Config::load_with_cli_overrides(
27 apply_cli
28 .config_overrides
29 .parse_overrides()
30 .map_err(anyhow::Error::msg)?,
31 ConfigOverrides::default(),
32 )?;
33
34 init_chatgpt_token_from_auth(&config.codex_home).await?;
35
36 let task_response = get_task(&config, apply_cli.task_id).await?;
37 apply_diff_from_task(task_response, cwd).await
38}
39
40pub async fn apply_diff_from_task(
41 task_response: GetTaskResponse,
42 cwd: Option<PathBuf>,
43) -> anyhow::Result<()> {
44 let diff_turn = match task_response.current_diff_task_turn {
45 Some(turn) => turn,
46 None => anyhow::bail!("No diff turn found"),
47 };
48 let output_diff = diff_turn.output_items.iter().find_map(|item| match item {
49 OutputItem::Pr(PrOutputItem { output_diff }) => Some(output_diff),
50 _ => None,
51 });
52 match output_diff {
53 Some(output_diff) => apply_diff(&output_diff.diff, cwd).await,
54 None => anyhow::bail!("No PR output item found"),
55 }
56}
57
58async fn apply_diff(diff: &str, cwd: Option<PathBuf>) -> anyhow::Result<()> {
59 let mut cmd = tokio::process::Command::new("git");
60 if let Some(cwd) = cwd {
61 cmd.current_dir(cwd);
62 }
63 let toplevel_output = cmd
64 .args(vec!["rev-parse", "--show-toplevel"])
65 .output()
66 .await?;
67
68 if !toplevel_output.status.success() {
69 anyhow::bail!("apply must be run from a git repository.");
70 }
71
72 let repo_root = String::from_utf8(toplevel_output.stdout)?
73 .trim()
74 .to_string();
75
76 let mut git_apply_cmd = tokio::process::Command::new("git")
77 .args(vec!["apply", "--3way"])
78 .current_dir(&repo_root)
79 .stdin(std::process::Stdio::piped())
80 .stdout(std::process::Stdio::piped())
81 .stderr(std::process::Stdio::piped())
82 .spawn()?;
83
84 if let Some(mut stdin) = git_apply_cmd.stdin.take() {
85 tokio::io::AsyncWriteExt::write_all(&mut stdin, diff.as_bytes()).await?;
86 drop(stdin);
87 }
88
89 let output = git_apply_cmd.wait_with_output().await?;
90
91 if !output.status.success() {
92 anyhow::bail!(
93 "Git apply failed with status {}: {}",
94 output.status,
95 String::from_utf8_lossy(&output.stderr)
96 );
97 }
98
99 println!("Successfully applied diff");
100 Ok(())
101}