xbp_cli/commands/github_cmd/
mod.rs1mod client;
4mod interactive;
5
6use crate::cli::commands::{GithubCmd, GithubSubCommand};
7use crate::commands::cloudflare_config::require_interactive_terminal;
8use crate::config::resolve_github_oauth2_key;
9use crate::utils::{
10 find_xbp_config_upwards, git_remote_url_from_metadata, parse_github_repo_from_remote_url,
11};
12use std::env;
13use std::path::PathBuf;
14
15pub use client::{
16 create_comment, create_issue, ensure_label, get_issue, list_comments, list_issues, list_labels,
17 update_issue, CreateIssueInput, GithubComment, GithubIssue, ListIssuesFilter, UpdateIssueInput,
18};
19
20pub async fn run_github(cmd: GithubCmd) -> Result<(), String> {
21 let token = resolve_github_token()?;
22 let (owner, repo) = resolve_repo(cmd.owner.as_deref(), cmd.repo.as_deref())?;
23 match cmd.command {
24 None => interactive::run_hub(&token, &owner, &repo).await,
25 Some(GithubSubCommand::List(args)) => client::run_list(&token, &owner, &repo, args).await,
26 Some(GithubSubCommand::Show(args)) => client::run_show(&token, &owner, &repo, args).await,
27 Some(GithubSubCommand::Create(args)) => {
28 client::run_create(&token, &owner, &repo, args).await
29 }
30 Some(GithubSubCommand::Edit(args)) => client::run_edit(&token, &owner, &repo, args).await,
31 Some(GithubSubCommand::Status(args)) => {
32 client::run_status(&token, &owner, &repo, args).await
33 }
34 Some(GithubSubCommand::Labels(args)) => {
35 client::run_labels(&token, &owner, &repo, args).await
36 }
37 Some(GithubSubCommand::Assign(args)) => {
38 client::run_assign(&token, &owner, &repo, args).await
39 }
40 Some(GithubSubCommand::Comment(args)) => {
41 client::run_comment(&token, &owner, &repo, args).await
42 }
43 Some(GithubSubCommand::Comments(args)) => {
44 client::run_comments(&token, &owner, &repo, args).await
45 }
46 }
47}
48
49pub(crate) fn resolve_github_token() -> Result<String, String> {
50 resolve_github_oauth2_key().ok_or_else(|| {
51 "No GitHub token found. Configure one with `xbp config github set-key` (or set `github_oauth2_key` in global config).".to_string()
52 })
53}
54
55pub(crate) fn resolve_repo(
56 owner_override: Option<&str>,
57 repo_override: Option<&str>,
58) -> Result<(String, String), String> {
59 if let (Some(owner), Some(repo)) = (owner_override, repo_override) {
60 let owner = owner.trim();
61 let repo = repo.trim();
62 if !owner.is_empty() && !repo.is_empty() {
63 return Ok((owner.to_string(), repo.to_string()));
64 }
65 }
66 let project_root = detect_project_root();
67 let remote = git_remote_url_from_metadata(&project_root, "origin")?.ok_or_else(|| {
68 format!(
69 "Could not resolve git remote `origin` under {}. Pass `--owner` and `--repo`.",
70 project_root.display()
71 )
72 })?;
73 parse_github_repo_from_remote_url(&remote).ok_or_else(|| {
74 format!("Remote is not a GitHub URL (`{remote}`). Pass `--owner` and `--repo`.")
75 })
76}
77
78fn detect_project_root() -> PathBuf {
79 let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
80 for dir in cwd.ancestors() {
83 if dir.join(".git").exists() {
84 return dir.to_path_buf();
85 }
86 }
87 if let Some(found) = find_xbp_config_upwards(&cwd) {
88 return found.project_root;
89 }
90 cwd
91}
92
93pub(crate) fn require_interactive(context: &str) -> Result<(), String> {
94 require_interactive_terminal(context)
95}