@@ -31,6 +31,7 @@ pub struct RepoConfig {
3131 pub url : String ,
3232 pub interval : Duration ,
3333 pub branches : Vec < String > ,
34+ pub branch_patterns : Vec < String > ,
3435 pub indexer_args : Vec < String > ,
3536 pub per_branch : Vec < PerBranchConfig > ,
3637 pub pre_index_hooks : Vec < HookConfig > ,
@@ -77,6 +78,8 @@ struct RawRepoConfig {
7778 interval : Option < String > ,
7879 branches : Vec < String > ,
7980 #[ serde( default ) ]
81+ branch_patterns : Vec < String > ,
82+ #[ serde( default ) ]
8083 indexer_args : Vec < String > ,
8184 #[ serde( default ) ]
8285 per_branch : Vec < RawPerBranchConfig > ,
@@ -185,22 +188,43 @@ impl AppConfig {
185188 if repo. url . trim ( ) . is_empty ( ) {
186189 bail ! ( "repo.url must not be empty for repo '{}'" , repo. name) ;
187190 }
188- if repo. branches . is_empty ( ) {
191+ if repo. branches . is_empty ( ) && repo . branch_patterns . is_empty ( ) {
189192 bail ! (
190- "repo '{}' must define at least one branch pattern" ,
193+ "repo '{}' must define at least one exact branch or branch pattern" ,
191194 repo. name
192195 ) ;
193196 }
194197
195- for pattern in & repo. branches {
198+ for branch in & repo. branches {
199+ if branch. trim ( ) . is_empty ( ) {
200+ bail ! ( "repo '{}' contains an empty branch name" , repo. name) ;
201+ }
202+ if is_glob_pattern ( branch) {
203+ bail ! (
204+ "repo '{}' branches must be exact names; move pattern '{}' to branch_patterns" ,
205+ repo. name,
206+ branch
207+ ) ;
208+ }
209+ }
210+
211+ for pattern in & repo. branch_patterns {
196212 if pattern. trim ( ) . is_empty ( ) {
197213 bail ! ( "repo '{}' contains an empty branch pattern" , repo. name) ;
198214 }
199- if is_glob_pattern ( pattern) {
200- Pattern :: new ( pattern) . with_context ( || {
201- format ! ( "repo '{}' has invalid branch glob '{}'" , repo. name, pattern)
202- } ) ?;
215+ if !is_glob_pattern ( pattern) {
216+ bail ! (
217+ "repo '{}' branch_patterns entries must contain glob syntax, got '{}'" ,
218+ repo. name,
219+ pattern
220+ ) ;
203221 }
222+ Pattern :: new ( pattern) . with_context ( || {
223+ format ! (
224+ "repo '{}' has invalid branch pattern '{}'" ,
225+ repo. name, pattern
226+ )
227+ } ) ?;
204228 }
205229
206230 for hook in repo
@@ -282,6 +306,7 @@ fn build_repo(raw: RawRepoConfig, default_interval: Duration) -> Result<RepoConf
282306 url : raw. url ,
283307 interval,
284308 branches,
309+ branch_patterns : raw. branch_patterns ,
285310 indexer_args : raw. indexer_args ,
286311 per_branch,
287312 pre_index_hooks,
@@ -475,11 +500,69 @@ mod tests {
475500 cfg. repos[ 0 ] . branches,
476501 vec![ "main" . to_string( ) , "release" . to_string( ) ]
477502 ) ;
503+ assert ! ( cfg. repos[ 0 ] . branch_patterns. is_empty( ) ) ;
478504 assert_eq ! ( cfg. repos[ 0 ] . per_branch. len( ) , 1 ) ;
479505 assert_eq ! ( cfg. repos[ 0 ] . per_branch[ 0 ] . branch, "release" ) ;
480506 assert_eq ! (
481507 cfg. repos[ 0 ] . per_branch[ 0 ] . indexer_args,
482508 vec![ "--live" . to_string( ) ]
483509 ) ;
484510 }
511+
512+ #[ test]
513+ fn parses_explicit_branch_patterns ( ) {
514+ let raw = r#"
515+ [[repo]]
516+ name = "foo"
517+ url = "git@example.com:foo.git"
518+ branches = ["main"]
519+ branch_patterns = ["rc-*", "release/*"]
520+ "# ;
521+
522+ let parsed: FileConfig = toml:: from_str ( raw) . expect ( "parse config" ) ;
523+ let cfg = AppConfig :: from_raw ( parsed) . expect ( "normalize" ) ;
524+
525+ assert_eq ! ( cfg. repos[ 0 ] . branches, vec![ "main" . to_string( ) ] ) ;
526+ assert_eq ! (
527+ cfg. repos[ 0 ] . branch_patterns,
528+ vec![ "rc-*" . to_string( ) , "release/*" . to_string( ) ]
529+ ) ;
530+ }
531+
532+ #[ test]
533+ fn rejects_glob_in_branches ( ) {
534+ let raw = r#"
535+ [[repo]]
536+ name = "foo"
537+ url = "git@example.com:foo.git"
538+ branches = ["rc-*"]
539+ "# ;
540+
541+ let parsed: FileConfig = toml:: from_str ( raw) . expect ( "parse config" ) ;
542+ let cfg = AppConfig :: from_raw ( parsed) . expect ( "normalize" ) ;
543+ let err = cfg. validate_config ( ) . expect_err ( "should fail" ) ;
544+ assert ! (
545+ err. to_string( )
546+ . contains( "move pattern 'rc-*' to branch_patterns" )
547+ ) ;
548+ }
549+
550+ #[ test]
551+ fn rejects_non_glob_branch_pattern ( ) {
552+ let raw = r#"
553+ [[repo]]
554+ name = "foo"
555+ url = "git@example.com:foo.git"
556+ branches = ["main"]
557+ branch_patterns = ["release"]
558+ "# ;
559+
560+ let parsed: FileConfig = toml:: from_str ( raw) . expect ( "parse config" ) ;
561+ let cfg = AppConfig :: from_raw ( parsed) . expect ( "normalize" ) ;
562+ let err = cfg. validate_config ( ) . expect_err ( "should fail" ) ;
563+ assert ! (
564+ err. to_string( )
565+ . contains( "branch_patterns entries must contain glob syntax" )
566+ ) ;
567+ }
485568}
0 commit comments