battlecommand_forge/
github.rs1use anyhow::{bail, Result};
5use std::process::Command;
6
7pub struct GitHubOps {
8 workspace: String,
9}
10
11#[derive(Debug)]
12pub struct PrResult {
13 pub url: String,
14 pub number: u32,
15}
16
17impl GitHubOps {
18 pub fn new(workspace: &str) -> Self {
19 Self {
20 workspace: workspace.to_string(),
21 }
22 }
23
24 pub fn is_available() -> bool {
25 Command::new("gh")
26 .args(["auth", "status"])
27 .output()
28 .map(|o| o.status.success())
29 .unwrap_or(false)
30 }
31
32 pub fn create_repo(&self, name: &str, private: bool) -> Result<String> {
33 let vis = if private { "--private" } else { "--public" };
34 let output = Command::new("gh")
35 .args([
36 "repo",
37 "create",
38 name,
39 vis,
40 "--source",
41 &self.workspace,
42 "--push",
43 ])
44 .output()?;
45 if output.status.success() {
46 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
47 } else {
48 bail!(
49 "Failed to create repo: {}",
50 String::from_utf8_lossy(&output.stderr)
51 )
52 }
53 }
54
55 pub fn add_remote(&self, repo_url: &str) -> Result<()> {
56 let output = Command::new("git")
57 .args(["remote", "add", "origin", repo_url])
58 .current_dir(&self.workspace)
59 .output()?;
60 if !output.status.success() {
61 Command::new("git")
62 .args(["remote", "set-url", "origin", repo_url])
63 .current_dir(&self.workspace)
64 .output()?;
65 }
66 Ok(())
67 }
68
69 pub fn push(&self, branch: &str) -> Result<()> {
70 let output = Command::new("git")
71 .args(["push", "-u", "origin", branch])
72 .current_dir(&self.workspace)
73 .output()?;
74 if output.status.success() {
75 Ok(())
76 } else {
77 bail!("Push failed: {}", String::from_utf8_lossy(&output.stderr))
78 }
79 }
80
81 pub fn create_pr(&self, title: &str, body: &str, base: &str) -> Result<PrResult> {
82 let output = Command::new("gh")
83 .args([
84 "pr", "create", "--title", title, "--body", body, "--base", base,
85 ])
86 .current_dir(&self.workspace)
87 .output()?;
88 if output.status.success() {
89 let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
90 let number = url
91 .rsplit('/')
92 .next()
93 .and_then(|n| n.parse().ok())
94 .unwrap_or(0);
95 Ok(PrResult { url, number })
96 } else {
97 bail!(
98 "PR creation failed: {}",
99 String::from_utf8_lossy(&output.stderr)
100 )
101 }
102 }
103
104 pub fn status(&self) -> Result<String> {
105 let branch = run_git(&self.workspace, &["branch", "--show-current"])?;
106 let remote = run_git(&self.workspace, &["remote", "-v"]).unwrap_or_default();
107 Ok(format!(
108 "Branch: {}\nRemote:\n{}",
109 branch.trim(),
110 remote.trim()
111 ))
112 }
113}
114
115fn run_git(cwd: &str, args: &[&str]) -> Result<String> {
116 let output = Command::new("git").args(args).current_dir(cwd).output()?;
117 Ok(String::from_utf8_lossy(&output.stdout).to_string())
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_gh_availability() {
126 let _ = GitHubOps::is_available();
128 }
129}