@@ -47,12 +47,20 @@ fn clap<'a, 'b>() -> App<'a, 'b> {
4747 . takes_value ( false )
4848 . help ( "Use shields.io badges for PR status (requires public repo visibility)" ) ;
4949
50+ let origin = Arg :: with_name ( "origin" )
51+ . long ( "origin" )
52+ . short ( "o" )
53+ . takes_value ( true )
54+ . default_value ( "origin" )
55+ . help ( "Name of the git remote to detect repository from (default: origin)" ) ;
56+
5057 let annotate = SubCommand :: with_name ( "annotate" )
5158 . about ( "Annotate the descriptions of all PRs in a stack with metadata about all PRs in the stack" )
5259 . setting ( AppSettings :: ArgRequiredElseHelp )
5360 . arg ( identifier. clone ( ) )
5461 . arg ( exclude. clone ( ) )
5562 . arg ( repository. clone ( ) )
63+ . arg ( origin. clone ( ) )
5664 . arg ( ci. clone ( ) )
5765 . arg ( prefix. clone ( ) )
5866 . arg ( badges. clone ( ) )
@@ -74,6 +82,7 @@ fn clap<'a, 'b>() -> App<'a, 'b> {
7482 . arg ( identifier. clone ( ) . index ( 2 ) )
7583 . arg ( exclude. clone ( ) )
7684 . arg ( repository. clone ( ) )
85+ . arg ( origin. clone ( ) )
7786 . arg (
7887 Arg :: with_name ( "project" )
7988 . long ( "project" )
@@ -129,6 +138,7 @@ fn clap<'a, 'b>() -> App<'a, 'b> {
129138 . arg ( identifier. clone ( ) )
130139 . arg ( exclude. clone ( ) )
131140 . arg ( repository. clone ( ) )
141+ . arg ( origin. clone ( ) )
132142 . arg (
133143 Arg :: with_name ( "no-approval" )
134144 . long ( "no-approval" )
@@ -209,6 +219,45 @@ fn get_excluded(m: &ArgMatches) -> Vec<String> {
209219 }
210220}
211221
222+ /// Resolve the repository to use, with fallback chain:
223+ /// 1. -r flag (explicit override)
224+ /// 2. GHSTACK_TARGET_REPOSITORY env var
225+ /// 3. Auto-detect from git remote
226+ fn resolve_repository (
227+ arg_value : Option < & str > ,
228+ env_value : & str ,
229+ remote_name : & str ,
230+ ) -> Result < String , String > {
231+ // Priority 1: Explicit -r flag
232+ if let Some ( repo) = arg_value {
233+ if !repo. is_empty ( ) {
234+ return Ok ( repo. to_string ( ) ) ;
235+ }
236+ }
237+
238+ // Priority 2: Environment variable
239+ if !env_value. is_empty ( ) {
240+ return Ok ( env_value. to_string ( ) ) ;
241+ }
242+
243+ // Priority 3: Auto-detect from git remote
244+ if let Some ( repo) = tree:: detect_repo_from_remote ( remote_name) {
245+ eprintln ! (
246+ "Detected repository: {} (from {} remote)" ,
247+ style( & repo) . cyan( ) ,
248+ remote_name
249+ ) ;
250+ return Ok ( repo) ;
251+ }
252+
253+ Err ( format ! (
254+ "Could not determine repository. Either:\n \
255+ - Run from inside a git repo with a GitHub remote\n \
256+ - Set GHSTACK_TARGET_REPOSITORY environment variable\n \
257+ - Use the -r flag"
258+ ) )
259+ }
260+
212261fn remove_title_prefixes ( title : String , prefix : & str ) -> String {
213262 let regex = Regex :: new ( & format ! ( "[{}]" , prefix) . to_string ( ) ) . unwrap ( ) ;
214263 regex. replace_all ( & title, "" ) . into_owned ( )
@@ -231,34 +280,29 @@ async fn main() -> Result<(), Box<dyn Error>> {
231280 let prefix = regex:: escape ( prefix) ;
232281 // if ci flag is set, set ci to true
233282 let ci = m. is_present ( "ci" ) ;
234- // replace it with the -r argument value if set
235- let repository = m. value_of ( "repository" ) . unwrap_or ( & repository) ;
236- // if repository is still unset, throw an error
237- if repository. is_empty ( ) {
238- let error = format ! (
239- "Invalid target repository {repo}. You must pass a repository with the -r flag or set GHSTACK_TARGET_REPOSITORY" , repo = repository
240- ) ;
241- panic ! ( "{}" , error) ;
242- }
283+ // resolve repository with fallback chain
284+ let remote_name = m. value_of ( "origin" ) . unwrap_or ( "origin" ) ;
285+ let repository = resolve_repository ( m. value_of ( "repository" ) , & repository, remote_name)
286+ . unwrap_or_else ( |e| panic ! ( "{}" , e) ) ;
243287
244288 let identifier = remove_title_prefixes ( identifier. to_string ( ) , & prefix) ;
245289
246290 println ! (
247291 "Searching for {} identifier in {} repo" ,
248292 style( & identifier) . bold( ) ,
249- style( repository) . bold( )
293+ style( & repository) . bold( )
250294 ) ;
251295
252296 let stack =
253- build_pr_stack_for_repo ( & identifier, repository, & credentials, get_excluded ( m) )
297+ build_pr_stack_for_repo ( & identifier, & repository, & credentials, get_excluded ( m) )
254298 . await ?;
255299
256300 let use_badges = m. is_present ( "badges" ) ;
257301 let table = markdown:: build_table (
258302 & stack,
259303 & identifier,
260304 m. value_of ( "prelude" ) ,
261- repository,
305+ & repository,
262306 use_badges,
263307 ) ;
264308
@@ -288,22 +332,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
288332 }
289333 } ;
290334
291- // replace it with the -r argument value if set
292- let repository = m. value_of ( "repository" ) . unwrap_or ( & repository) ;
293- // if repository is still unset, throw an error
294- if repository. is_empty ( ) {
295- panic ! (
296- "You must pass a repository with the -r flag or set GHSTACK_TARGET_REPOSITORY"
297- ) ;
298- }
335+ // resolve repository with fallback chain
336+ let remote_name = m. value_of ( "origin" ) . unwrap_or ( "origin" ) ;
337+ let repository = resolve_repository ( m. value_of ( "repository" ) , & repository, remote_name)
338+ . unwrap_or_else ( |e| panic ! ( "{}" , e) ) ;
299339
300340 println ! (
301341 "Searching for {} identifier in {} repo" ,
302342 style( identifier) . bold( ) ,
303- style( repository) . bold( )
343+ style( & repository) . bold( )
304344 ) ;
305345 let stack =
306- build_pr_stack_for_repo ( identifier, repository, & credentials, get_excluded ( m) )
346+ build_pr_stack_for_repo ( identifier, & repository, & credentials, get_excluded ( m) )
307347 . await ?;
308348
309349 // Check for empty stack
@@ -356,34 +396,29 @@ async fn main() -> Result<(), Box<dyn Error>> {
356396 ( "autorebase" , Some ( m) ) => {
357397 let identifier = m. value_of ( "identifier" ) . unwrap ( ) ;
358398
359- // store the value of GHSTACK_TARGET_REPOSITORY
360- let repository = env:: var ( "GHSTACK_TARGET_REPOSITORY" ) . unwrap_or_default ( ) ;
361- // replace it with the -r argument value if set
362- let repository = m. value_of ( "repository" ) . unwrap_or ( & repository) ;
363- // if repository is still unset, throw an error
364- if repository. is_empty ( ) {
365- panic ! (
366- "You must pass a repository with the -r flag or set GHSTACK_TARGET_REPOSITORY"
367- ) ;
368- }
399+ // defaults to "origin" if no remote is specified
400+ let remote_name = m. value_of ( "origin" ) . unwrap_or ( "origin" ) ;
401+
402+ // resolve repository with fallback chain
403+ let repository = resolve_repository ( m. value_of ( "repository" ) , & repository, remote_name)
404+ . unwrap_or_else ( |e| panic ! ( "{}" , e) ) ;
369405
370406 println ! (
371407 "Searching for {} identifier in {} repo" ,
372408 style( identifier) . bold( ) ,
373- style( repository) . bold( )
409+ style( & repository) . bold( )
374410 ) ;
375411 let stack =
376- build_pr_stack_for_repo ( identifier, repository, & credentials, get_excluded ( m) )
412+ build_pr_stack_for_repo ( identifier, & repository, & credentials, get_excluded ( m) )
377413 . await ?;
378414
379415 let project = m
380416 . value_of ( "project" )
381417 . expect ( "The --project argument is required." ) ;
382418 let project = Repository :: open ( project) ?;
383419
384- // defaults to "origin" if no remote is specified
385- let remote = m. value_of ( "origin" ) . unwrap_or ( "origin" ) ;
386- let remote = project. find_remote ( remote) . unwrap ( ) ;
420+ // use the same remote name for finding the remote to push to
421+ let remote = project. find_remote ( remote_name) . unwrap ( ) ;
387422
388423 // if ci flag is set, set ci to true
389424 let ci = m. is_present ( "ci" ) ;
@@ -402,22 +437,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
402437 ( "land" , Some ( m) ) => {
403438 let identifier = m. value_of ( "identifier" ) . unwrap ( ) ;
404439
405- // Get repository from args or environment
406- let repository = m. value_of ( "repository" ) . unwrap_or ( & repository) ;
407- if repository. is_empty ( ) {
408- panic ! (
409- "You must pass a repository with the -r flag or set GHSTACK_TARGET_REPOSITORY"
410- ) ;
411- }
440+ // resolve repository with fallback chain
441+ let remote_name = m. value_of ( "origin" ) . unwrap_or ( "origin" ) ;
442+ let repository = resolve_repository ( m. value_of ( "repository" ) , & repository, remote_name)
443+ . unwrap_or_else ( |e| panic ! ( "{}" , e) ) ;
412444
413445 println ! (
414446 "Analyzing stack for {} in {}...\n " ,
415447 style( identifier) . bold( ) ,
416- style( repository) . bold( )
448+ style( & repository) . bold( )
417449 ) ;
418450
419451 let stack =
420- build_pr_stack_for_repo ( identifier, repository, & credentials, get_excluded ( m) )
452+ build_pr_stack_for_repo ( identifier, & repository, & credentials, get_excluded ( m) )
421453 . await ?;
422454
423455 if stack. is_empty ( ) {
@@ -438,7 +470,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
438470 } ;
439471
440472 // Create the landing plan
441- let plan = match land:: create_land_plan ( & stack, repository, & options) {
473+ let plan = match land:: create_land_plan ( & stack, & repository, & options) {
442474 Ok ( plan) => plan,
443475 Err ( e) => {
444476 match & e {
0 commit comments