Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 108 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ghscaff"
version = "0.7.1"
version = "0.7.2"
edition = "2021"
description = "Interactive CLI wizard for creating and configuring GitHub repositories"
license = "MIT"
Expand Down Expand Up @@ -31,7 +31,7 @@ crypto_box = "0.9"
blake2 = "0.10"
crypto_secretbox = "0.1"
whoami = "1"
serde_yaml = "0.9.34"
serde-saphyr = "1.3"
sha2 = "0.10"

[dev-dependencies]
Expand Down
4 changes: 2 additions & 2 deletions src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub fn derive_required_contexts(files: &[WorkflowSource]) -> Vec<String> {
.iter()
.filter(|f| is_workflow_path(f.path))
.filter_map(|f| {
serde_yaml::from_str::<Workflow>(f.content)
serde_saphyr::from_str::<Workflow>(f.content)
.ok()
.map(|w| (f.path, w))
})
Expand Down Expand Up @@ -78,7 +78,7 @@ pub fn derive_required_contexts(files: &[WorkflowSource]) -> Vec<String> {
/// workflows. Used to fetch just those extra files when the caller only has
/// one workflow's content in hand (e.g. reading a live repo).
pub fn referenced_local_workflows(content: &str) -> Vec<String> {
let Ok(workflow) = serde_yaml::from_str::<Workflow>(content) else {
let Ok(workflow) = serde_saphyr::from_str::<Workflow>(content) else {
return Vec::new();
};
workflow
Expand Down
52 changes: 29 additions & 23 deletions src/wizard/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,34 +235,40 @@ fn apply_wizard_protections_step(
.collect();
let required_contexts = crate::checks::derive_required_contexts(&workflow_sources);

progress.run(
&format!("apply branch protection ({})", c.default_branch),
|| {
branches::apply_branch_protection(
client,
&c.owner,
&c.name,
&c.default_branch,
&required_contexts,
)?;
Ok(())
},
)?;
protect_branch(progress, client, c, &c.default_branch, &required_contexts)?;
if c.create_develop {
progress.run("apply branch protection (develop)", || {
branches::apply_branch_protection(
client,
&c.owner,
&c.name,
"develop",
&required_contexts,
)?;
Ok(())
})?;
protect_branch(progress, client, c, "develop", &required_contexts)?;
}
Ok(())
}

/// Protect one branch. A 403 means the account's plan has no branch protection
/// for this repo (a private repo on a free plan): the wizard says so and goes on
/// to labels, topics, teams and secrets instead of leaving the repo half set up.
/// Any other error still stops the wizard.
fn protect_branch(
progress: &mut StepCounter,
client: &GithubClient,
c: &WizardConfig,
branch: &str,
required_contexts: &[String],
) -> Result<()> {
let result = progress.run(&format!("apply branch protection ({branch})"), || {
branches::apply_branch_protection(client, &c.owner, &c.name, branch, required_contexts)
});
match result {
Err(e) if format!("{e:#}").contains("403") => {
println!("skipped");
println!(" ⚠ GitHub refused branch protection (403): a private repo on a free plan has none.");
println!(
" Protect {branch} by hand if the plan changes, or make the repo public."
);
Ok(())
}
other => other,
}
}

/// Step 6 — labels.
fn sync_wizard_labels_step(
progress: &mut StepCounter,
Expand Down
16 changes: 16 additions & 0 deletions src/wizard/execution/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,22 @@ fn apply_wizard_protections_step_puts_main_and_develop() {
));
}

#[test]
fn apply_wizard_protections_step_skips_a_plan_refusal() {
let (url, log) = start_recording_mock_server(protections_handler(403));
let client = mock_client(&url);
let mut config = minimal_config();
config.create_develop = true;
let files = workflow_files();
let mut progress = StepCounter::new(4, false);
apply_wizard_protections_step(&mut progress, &client, &config, &files).unwrap();
assert!(has_call(
&log,
"PUT",
"/repos/owner/repo/branches/develop/protection"
));
}

#[test]
fn apply_wizard_protections_step_propagates_error() {
let (url, log) = start_recording_mock_server(protections_handler(500));
Expand Down
Loading