Skip to content

Commit 0b654d4

Browse files
committed
reposerver: support branch patterns
1 parent 3e2e025 commit 0b654d4

5 files changed

Lines changed: 232 additions & 184 deletions

File tree

reposerver/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# pointer-reposerver
22

3-
`pointer-reposerver` polls configured Git repositories, fetches only configured branches at depth 1,
4-
and runs `pointer-indexer` only when branch head commits change.
3+
`pointer-reposerver` polls configured Git repositories, discovers matching remote branches,
4+
fetches only the resolved branch set at depth 1, and runs `pointer-indexer` only when branch
5+
head commits change.
56

67
Logging is emitted to stderr via `tracing` (configure verbosity with `RUST_LOG`).
78

@@ -17,6 +18,9 @@ cargo run -p pointer-reposerver -- --config reposerver/example.reposerver.toml
1718

1819
See `reposerver/example.reposerver.toml` for a complete example.
1920

21+
`repo.branches` contains exact branch names.
22+
`repo.branch_patterns` contains glob patterns such as `release/*` or `rc-*`; these are matched
23+
against the remote branch list each cycle, and only the matched concrete branch names are fetched.
2024
`global.indexer_args` are applied first for every invocation, then `repo.indexer_args` are appended.
2125
Per-branch args can be set with `[[repo.per_branch]]`; those args are appended last.
2226
Hooks run as `<global.shell> -c "<command>"` and `global.shell` defaults to `sh`.

reposerver/example.reposerver.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ timeout = "30s"
1515
name = "pointer"
1616
url = "git@github.com:org/pointer.git"
1717
interval = "2m"
18-
branches = ["main", "release/*"]
18+
branches = ["main"]
19+
branch_patterns = ["release/*", "rc-*"]
1920
indexer_args = ["--keep-latest", "3"]
2021

2122
[[repo.per_branch]]

reposerver/src/config.rs

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)