@@ -74,6 +74,10 @@ pub struct PrCli {
7474 /// GitHub token for API access (for private repos).
7575 #[ arg( long) ]
7676 pub token : Option < String > ,
77+
78+ /// GitHub repository in "owner/repo" format. Required when using --info outside a git repository.
79+ #[ arg( short, long) ]
80+ pub repo : Option < String > ,
7781}
7882
7983impl PrCli {
@@ -83,28 +87,54 @@ impl PrCli {
8387 }
8488}
8589
90+ /// Parse a repository string in "owner/repo" format.
91+ fn parse_repo_arg ( repo_arg : & str ) -> Result < ( String , String ) > {
92+ let parts: Vec < & str > = repo_arg. split ( '/' ) . collect ( ) ;
93+ if parts. len ( ) != 2 || parts[ 0 ] . is_empty ( ) || parts[ 1 ] . is_empty ( ) {
94+ bail ! (
95+ "Invalid repository format: '{}'. Expected 'owner/repo' format." ,
96+ repo_arg
97+ ) ;
98+ }
99+ Ok ( ( parts[ 0 ] . to_string ( ) , parts[ 1 ] . to_string ( ) ) )
100+ }
101+
86102/// Checkout a pull request branch.
87103async fn run_pr_checkout ( args : PrCli ) -> Result < ( ) > {
88104 use cortex_engine:: github:: GitHubClient ;
89105
90- let repo_path = args. path . unwrap_or_else ( || PathBuf :: from ( "." ) ) ;
91106 let pr_number = args. number ;
92107
93- // Change to repo directory
94- std:: env:: set_current_dir ( & repo_path)
95- . with_context ( || format ! ( "Failed to change to directory: {}" , repo_path. display( ) ) ) ?;
96-
97- // Check if we're in a git repo
98- if !repo_path. join ( ".git" ) . exists ( ) {
99- bail ! ( "Not a git repository. Run this command from a git repository root." ) ;
100- }
108+ // Determine the repository - either from --repo flag or from git remote
109+ let repository = if let Some ( ref repo_arg) = args. repo {
110+ // Use the provided --repo flag
111+ let ( owner, repo) = parse_repo_arg ( repo_arg) ?;
112+ format ! ( "{}/{}" , owner, repo)
113+ } else {
114+ // Need to be in a git repository to determine owner/repo
115+ let repo_path = args. path . clone ( ) . unwrap_or_else ( || PathBuf :: from ( "." ) ) ;
116+
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+ if args. info {
124+ bail ! (
125+ "Not a git repository. Use --repo owner/repo to specify the repository when using --info outside a git repository."
126+ ) ;
127+ }
128+ bail ! ( "Not a git repository. Run this command from a git repository root." ) ;
129+ }
101130
102- // Get the remote URL to determine owner/repo
103- let remote_url = get_git_remote_url ( ) ?;
104- let ( owner, repo) = parse_github_url ( & remote_url)
105- . with_context ( || format ! ( "Failed to parse GitHub URL: {}" , remote_url) ) ?;
131+ // Get the remote URL to determine owner/repo
132+ let remote_url = get_git_remote_url ( ) ?;
133+ let ( owner, repo) = parse_github_url ( & remote_url)
134+ . with_context ( || format ! ( "Failed to parse GitHub URL: {}" , remote_url) ) ?;
106135
107- let repository = format ! ( "{}/{}" , owner, repo) ;
136+ format ! ( "{}/{}" , owner, repo)
137+ } ;
108138
109139 println ! ( "🔀 Pull Request #{}" , pr_number) ;
110140 println ! ( "{}" , "=" . repeat( 40 ) ) ;
@@ -149,6 +179,17 @@ async fn run_pr_checkout(args: PrCli) -> Result<()> {
149179 return Ok ( ( ) ) ;
150180 }
151181
182+ // For checkout operations, we need to be in a git repository
183+ // This check is needed when --repo was provided but --info was not
184+ if args. repo . is_some ( ) {
185+ let repo_path = args. path . clone ( ) . unwrap_or_else ( || PathBuf :: from ( "." ) ) ;
186+ std:: env:: set_current_dir ( & repo_path)
187+ . with_context ( || format ! ( "Failed to change to directory: {}" , repo_path. display( ) ) ) ?;
188+ if !repo_path. join ( ".git" ) . exists ( ) {
189+ bail ! ( "Not a git repository. Checkout operations require a git repository." ) ;
190+ }
191+ }
192+
152193 // Check for uncommitted changes
153194 if !args. force {
154195 let status_output = Command :: new ( "git" )
@@ -342,4 +383,35 @@ mod tests {
342383 assert_eq ! ( owner, "Cortex-ai" ) ;
343384 assert_eq ! ( repo, "Cortex" ) ;
344385 }
386+
387+ #[ test]
388+ fn test_parse_repo_arg_valid ( ) {
389+ let ( owner, repo) = parse_repo_arg ( "CortexLM/cortex-cli" ) . unwrap ( ) ;
390+ assert_eq ! ( owner, "CortexLM" ) ;
391+ assert_eq ! ( repo, "cortex-cli" ) ;
392+ }
393+
394+ #[ test]
395+ fn test_parse_repo_arg_invalid_no_slash ( ) {
396+ let result = parse_repo_arg ( "invalid" ) ;
397+ assert ! ( result. is_err( ) ) ;
398+ }
399+
400+ #[ test]
401+ fn test_parse_repo_arg_invalid_empty_owner ( ) {
402+ let result = parse_repo_arg ( "/repo" ) ;
403+ assert ! ( result. is_err( ) ) ;
404+ }
405+
406+ #[ test]
407+ fn test_parse_repo_arg_invalid_empty_repo ( ) {
408+ let result = parse_repo_arg ( "owner/" ) ;
409+ assert ! ( result. is_err( ) ) ;
410+ }
411+
412+ #[ test]
413+ fn test_parse_repo_arg_invalid_too_many_slashes ( ) {
414+ let result = parse_repo_arg ( "owner/repo/extra" ) ;
415+ assert ! ( result. is_err( ) ) ;
416+ }
345417}
0 commit comments