@@ -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
100104impl 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.
108124async 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