Skip to content

Commit 7a104aa

Browse files
factorydroidechobt
authored andcommitted
fix(cli): allow --info flag to work without git repository
Fixes bounty issue #1382 Added --repo flag to allow specifying the GitHub repository directly (in owner/repo format) when using the --info flag. This enables users to view PR details without needing to be inside a git repository, which is useful for quickly checking PR information.
1 parent 73e4dee commit 7a104aa

File tree

1 file changed

+86
-14
lines changed

1 file changed

+86
-14
lines changed

cortex-cli/src/pr_cmd.rs

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ pub struct PrCli {
9595
/// GitHub token for API access (for private repos).
9696
#[arg(long)]
9797
pub token: Option<String>,
98+
99+
/// GitHub repository in "owner/repo" format. Required when using --info outside a git repository.
100+
#[arg(short, long)]
101+
pub repo: Option<String>,
98102
}
99103

100104
impl PrCli {
@@ -104,31 +108,57 @@ impl PrCli {
104108
}
105109
}
106110

111+
/// Parse a repository string in "owner/repo" format.
112+
fn parse_repo_arg(repo_arg: &str) -> Result<(String, String)> {
113+
let parts: Vec<&str> = repo_arg.split('/').collect();
114+
if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
115+
bail!(
116+
"Invalid repository format: '{}'. Expected 'owner/repo' format.",
117+
repo_arg
118+
);
119+
}
120+
Ok((parts[0].to_string(), parts[1].to_string()))
121+
}
122+
107123
/// Checkout a pull request branch.
108124
async fn run_pr_checkout(args: PrCli) -> Result<()> {
109125
use cortex_engine::github::GitHubClient;
110126

111-
let repo_path = args.path.unwrap_or_else(|| PathBuf::from("."));
112127
let pr_number = args.number;
113128

114129
// Validate PR number locally before making any API calls
115130
validate_pr_number(pr_number)?;
116131

117-
// Change to repo directory
118-
std::env::set_current_dir(&repo_path)
119-
.with_context(|| format!("Failed to change to directory: {}", repo_path.display()))?;
120-
121-
// Check if we're in a git repo
122-
if !repo_path.join(".git").exists() {
123-
bail!("Not a git repository. Run this command from a git repository root.");
124-
}
132+
// Determine the repository - either from --repo flag or from git remote
133+
let repository = if let Some(ref repo_arg) = args.repo {
134+
// Use the provided --repo flag
135+
let (owner, repo) = parse_repo_arg(repo_arg)?;
136+
format!("{}/{}", owner, repo)
137+
} else {
138+
// Need to be in a git repository to determine owner/repo
139+
let repo_path = args.path.clone().unwrap_or_else(|| PathBuf::from("."));
140+
141+
// Change to repo directory
142+
std::env::set_current_dir(&repo_path)
143+
.with_context(|| format!("Failed to change to directory: {}", repo_path.display()))?;
144+
145+
// Check if we're in a git repo
146+
if !repo_path.join(".git").exists() {
147+
if args.info {
148+
bail!(
149+
"Not a git repository. Use --repo owner/repo to specify the repository when using --info outside a git repository."
150+
);
151+
}
152+
bail!("Not a git repository. Run this command from a git repository root.");
153+
}
125154

126-
// Get the remote URL to determine owner/repo
127-
let remote_url = get_git_remote_url()?;
128-
let (owner, repo) = parse_github_url(&remote_url)
129-
.with_context(|| format!("Failed to parse GitHub URL: {}", remote_url))?;
155+
// Get the remote URL to determine owner/repo
156+
let remote_url = get_git_remote_url()?;
157+
let (owner, repo) = parse_github_url(&remote_url)
158+
.with_context(|| format!("Failed to parse GitHub URL: {}", remote_url))?;
130159

131-
let repository = format!("{}/{}", owner, repo);
160+
format!("{}/{}", owner, repo)
161+
};
132162

133163
println!("🔀 Pull Request #{}", pr_number);
134164
println!("{}", "=".repeat(40));
@@ -173,6 +203,17 @@ async fn run_pr_checkout(args: PrCli) -> Result<()> {
173203
return Ok(());
174204
}
175205

206+
// For checkout operations, we need to be in a git repository
207+
// This check is needed when --repo was provided but --info was not
208+
if args.repo.is_some() {
209+
let repo_path = args.path.clone().unwrap_or_else(|| PathBuf::from("."));
210+
std::env::set_current_dir(&repo_path)
211+
.with_context(|| format!("Failed to change to directory: {}", repo_path.display()))?;
212+
if !repo_path.join(".git").exists() {
213+
bail!("Not a git repository. Checkout operations require a git repository.");
214+
}
215+
}
216+
176217
// Check for uncommitted changes
177218
if !args.force {
178219
let status_output = Command::new("git")
@@ -405,4 +446,35 @@ mod tests {
405446
let result = validate_pr_number(MAX_REASONABLE_PR_NUMBER + 1);
406447
assert!(result.is_err());
407448
}
449+
450+
#[test]
451+
fn test_parse_repo_arg_valid() {
452+
let (owner, repo) = parse_repo_arg("CortexLM/cortex-cli").unwrap();
453+
assert_eq!(owner, "CortexLM");
454+
assert_eq!(repo, "cortex-cli");
455+
}
456+
457+
#[test]
458+
fn test_parse_repo_arg_invalid_no_slash() {
459+
let result = parse_repo_arg("invalid");
460+
assert!(result.is_err());
461+
}
462+
463+
#[test]
464+
fn test_parse_repo_arg_invalid_empty_owner() {
465+
let result = parse_repo_arg("/repo");
466+
assert!(result.is_err());
467+
}
468+
469+
#[test]
470+
fn test_parse_repo_arg_invalid_empty_repo() {
471+
let result = parse_repo_arg("owner/");
472+
assert!(result.is_err());
473+
}
474+
475+
#[test]
476+
fn test_parse_repo_arg_invalid_too_many_slashes() {
477+
let result = parse_repo_arg("owner/repo/extra");
478+
assert!(result.is_err());
479+
}
408480
}

0 commit comments

Comments
 (0)