Skip to content

Commit cc698a7

Browse files
Apply setup updates directly to the working tree
1 parent 089d095 commit cc698a7

8 files changed

Lines changed: 64 additions & 80 deletions

File tree

‎.github/releasing.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ Verify both the downloaded gem and `release.json` with `gh attestation verify`,
1414

1515
The initial implementation supports public repositories, a single gemspec, stable patch/minor/major versions, and merge/squash commits. Keep rebase merges and merge queues disabled. Generation hooks must be repeatable from the same source and version. Run setup and account recovery checks before enabling publishing; see the companion gem's setup guide.
1616

17-
After upgrading `bake-gem-github`, run `bundle exec bake gem:github:setup:update` to write `pkg/release-setup.patch`. Review every hunk, including differences from local customizations, then apply the desired changes and open a PR. The task does not modify repository files or remote settings.
17+
After upgrading `bake-gem-github`, start from a clean working tree and run `bundle exec bake gem:github:setup:update`. It updates generated files without staging or committing them. Review `git diff`, selectively keep changes and restore customizations as needed, then open a PR.

‎bake/gem/github/setup.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def apply
1414
Bake::Gem::GitHub::Project.new(context.root).apply
1515
end
1616

17-
# Write a reviewable update patch using config/release.yaml and the installed templates.
17+
# Update generated files in the working tree using config/release.yaml and the installed templates.
1818
def update
1919
require_relative "../../../lib/bake/gem/github/setup"
2020
Bake::Gem::GitHub::Setup.new(context.root).update

‎context/getting-started.md‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,14 @@ bundle exec bake gem:github:setup:plan
2020

2121
Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. Setup does not replace other publishers: remove conflicting release workflows during migration.
2222

23-
To adopt template fixes after upgrading the gem, or change policy, edit `config/release.yaml` as needed and generate an update patch:
23+
To adopt template fixes after upgrading the gem, start from a clean working tree, edit `config/release.yaml` as needed, and regenerate:
2424

2525
``` bash
2626
bundle exec bake gem:github:setup:update
27-
cat pkg/release-setup.patch
28-
git apply --check pkg/release-setup.patch
29-
git apply pkg/release-setup.patch
3027
git diff
3128
```
3229

33-
The task leaves repository files untouched and writes only `pkg/release-setup.patch`. The patch proposes the current templates, policy payloads, configuration formatting, and maintainer instructions. It includes any differences from repository customizations: inspect every hunk, remove unwanted hunks or apply selected changes manually, then commit the result through a PR. Repeating the task after applying all changes produces an empty patch. Intentionally retained customizations remain visible on later updates. Apply remote rulesets only after the corresponding workflows are merged and their checks are running.
30+
The task updates the managed workflows, policy payloads, configuration formatting, and maintainer instructions directly in the working tree and returns the changed paths. It does not stage, commit, or change remote settings. An agent or maintainer can review the diff and selectively keep changes, restoring repository-specific customizations from Git where needed. Commit or stash existing edits first: generated files are replaced by the current templates. Repeating an update produces no further changes; intentionally retained customizations will appear in later update diffs. Apply remote rulesets after the corresponding workflows are running.
3431

3532
The default is two approvals with explicit administrator bypass, dismissed stale reviews, approval of the last push, strict up-to-date CI, and immutable default-branch history/release tags. These branch rules affect **all PRs** into the default branch. Ordinary administrator reviews count as one review. A human who dispatches a bot-authored PR is not its author under GitHub's native rules.
3633

‎guides/getting-started/readme.md‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,14 @@ bundle exec bake gem:github:setup:plan
2020

2121
Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. Setup does not replace other publishers: remove conflicting release workflows during migration.
2222

23-
To adopt template fixes after upgrading the gem, or change policy, edit `config/release.yaml` as needed and generate an update patch:
23+
To adopt template fixes after upgrading the gem, start from a clean working tree, edit `config/release.yaml` as needed, and regenerate:
2424

2525
``` bash
2626
bundle exec bake gem:github:setup:update
27-
cat pkg/release-setup.patch
28-
git apply --check pkg/release-setup.patch
29-
git apply pkg/release-setup.patch
3027
git diff
3128
```
3229

33-
The task leaves repository files untouched and writes only `pkg/release-setup.patch`. The patch proposes the current templates, policy payloads, configuration formatting, and maintainer instructions. It includes any differences from repository customizations: inspect every hunk, remove unwanted hunks or apply selected changes manually, then commit the result through a PR. Repeating the task after applying all changes produces an empty patch. Intentionally retained customizations remain visible on later updates. Apply remote rulesets only after the corresponding workflows are merged and their checks are running.
30+
The task updates the managed workflows, policy payloads, configuration formatting, and maintainer instructions directly in the working tree and returns the changed paths. It does not stage, commit, or change remote settings. An agent or maintainer can review the diff and selectively keep changes, restoring repository-specific customizations from Git where needed. Commit or stash existing edits first: generated files are replaced by the current templates. Repeating an update produces no further changes; intentionally retained customizations will appear in later update diffs. Apply remote rulesets after the corresponding workflows are running.
3431

3532
The default is two approvals with explicit administrator bypass, dismissed stale reviews, approval of the last push, strict up-to-date CI, and immutable default-branch history/release tags. These branch rules affect **all PRs** into the default branch. Ordinary administrator reviews count as one review. A human who dispatches a bot-authored PR is not its author under GitHub's native rules.
3633

‎lib/bake/gem/github/setup.rb‎

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
require "yaml"
88
require "json"
99
require "fileutils"
10-
require "tempfile"
1110

1211
module Bake
1312
module Gem
@@ -29,33 +28,15 @@ def generate(repository:, branch: "main", checks:, approvals: 2, signing: File.f
2928
files = render(config)
3029
conflicts = files.keys.select{|name| File.exist?(File.join(@root, name)) && File.read(File.join(@root, name)) != files[name]}
3130
raise "Existing files differ; review them before regenerating: #{conflicts.join(', ')}" unless conflicts.empty?
32-
files.each do |name, content|
33-
path = File.join(@root, name)
34-
FileUtils.mkdir_p(File.dirname(path))
35-
File.write(path, content) unless File.exist?(path)
36-
end
31+
write(files)
3732
files.keys
3833
end
3934

40-
# Write a patch proposing updates from the existing configuration, without replacing repository files.
35+
# Update generated files in the working tree using the existing configuration; return changed paths.
4136
def update
4237
config = YAML.safe_load_file(File.join(@root, "config/release.yaml"))
4338
raise "Unsupported release configuration." unless config.fetch("schema") == 1
44-
patch = render(config).map do |name, content|
45-
path = File.join(@root, name)
46-
Tempfile.create("release-update") do |file|
47-
file.write(content)
48-
file.flush
49-
exists = File.exist?(path)
50-
diff = IO.popen(["diff", "-u", "--label", exists ? "a/#{name}" : "/dev/null", "--label", "b/#{name}", exists ? path : File::NULL, file.path], &:read)
51-
raise "Unable to compare #{name}." unless [0, 1].include?($?.exitstatus)
52-
diff
53-
end
54-
end.join
55-
path = File.join(@root, "pkg/release-setup.patch")
56-
FileUtils.mkdir_p(File.dirname(path))
57-
File.write(path, patch)
58-
path
39+
write(render(config))
5940
end
6041

6142
# Native review/check rules allow PR-only administrator bypass; history rules have no bypass.
@@ -73,6 +54,16 @@ def self.rules(config)
7354

7455
private
7556

57+
def write(files)
58+
files.filter_map do |name, content|
59+
path = File.join(@root, name)
60+
next if File.exist?(path) && File.read(path) == content
61+
FileUtils.mkdir_p(File.dirname(path))
62+
File.write(path, content)
63+
name
64+
end
65+
end
66+
7667
def render(config)
7768
branch = config.fetch("branch")
7869
ruby = config.fetch("ruby")

‎releases.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Unreleased
44

5-
- Generate reviewable setup updates with `gem:github:setup:update`.
5+
- Update generated release files in the working tree with `gem:github:setup:update`.
66

77
## v0.0.5
88

‎templates/releasing.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ Verify both the downloaded gem and `release.json` with `gh attestation verify`,
1414

1515
The initial implementation supports public repositories, a single gemspec, stable patch/minor/major versions, and merge/squash commits. Keep rebase merges and merge queues disabled. Generation hooks must be repeatable from the same source and version. Run setup and account recovery checks before enabling publishing; see the companion gem's setup guide.
1616

17-
After upgrading `bake-gem-github`, run `bundle exec bake gem:github:setup:update` to write `pkg/release-setup.patch`. Review every hunk, including differences from local customizations, then apply the desired changes and open a PR. The task does not modify repository files or remote settings.
17+
After upgrading `bake-gem-github`, start from a clean working tree and run `bundle exec bake gem:github:setup:update`. It updates generated files without staging or committing them. Review `git diff`, selectively keep changes and restore customizations as needed, then open a PR.

‎test/bake/gem/github/setup/update.rb‎

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,53 @@
44
# Copyright, 2026, by Samuel Williams.
55

66
require "bake/gem/github/setup"
7-
require "bake/context"
8-
require "sus/fixtures/temporary_directory_context"
7+
require "bake/gem/github/repository_context"
98

109
describe Bake::Gem::GitHub::Setup do
11-
include Sus::Fixtures::TemporaryDirectoryContext
10+
include Bake::Gem::GitHub::RepositoryContext
1211

13-
let(:setup) {subject.new(root)}
12+
let(:setup) {subject.new(repository)}
1413

15-
before do
16-
setup.generate(repository: "socketry/example", checks: ["Tests"], signing: false)
17-
end
18-
19-
it "proposes configuration changes and customizations without overwriting them" do
20-
path = File.join(root, "config/release.yaml")
21-
config = YAML.safe_load_file(path)
22-
config["approvals"] = 3
23-
config["checks"] << "New check"
24-
File.write(path, YAML.dump(config))
25-
workflow = File.join(root, ".github/workflows/release-validate.yaml")
26-
File.write(workflow, "# Custom workflow\n" + File.read(workflow))
27-
before = Dir.glob("{config,.github}/**/*", File::FNM_DOTMATCH, base: root).select{|name| File.file?(File.join(root, name))}.to_h{|name| [name, File.read(File.join(root, name))]}
28-
registry = Bake::Registry::Aggregate.new
29-
registry.append_path(::Gem.loaded_specs.fetch("bake-gem-github").full_gem_path)
30-
patch = Bake::Context.new(registry, root).call("gem:github:setup:update")
31-
expect(File.read(patch)).to be(:include?, "-# Custom workflow")
32-
expect(File.read(patch)).to be(:include?, '+ "context": "New check"')
33-
before.each do |name, content|
34-
expect(File.read(File.join(root, name))).to be == content
14+
with "#update" do
15+
it "leaves generated updates in the working tree for selective review" do
16+
workflow = File.join(repository, ".github/workflows/release-validate.yaml")
17+
File.write(workflow, "# Custom workflow\n" + File.read(workflow))
18+
File.write(File.join(repository, ".github/workflows/custom.yaml"), "Custom workflow\n")
19+
git("add", "--all")
20+
git("commit", "--quiet", "-m", "Repository customizations")
21+
original = git("rev-parse", "HEAD")
22+
path = File.join(repository, "config/release.yaml")
23+
config = YAML.safe_load_file(path)
24+
config["approvals"] = 3
25+
config["checks"] << "New check"
26+
File.write(path, YAML.dump(config))
27+
registry = Bake::Registry::Aggregate.new
28+
registry.append_path(::Gem.loaded_specs.fetch("bake-gem-github").full_gem_path)
29+
changed = Bake::Context.new(registry, repository).call("gem:github:setup:update")
30+
expect(changed.sort).to be == [".github/release-rules/checks.json", ".github/release-rules/reviews.json", ".github/workflows/release-validate.yaml"]
31+
diff = git("diff")
32+
expect(diff).to be(:include?, "-# Custom workflow")
33+
expect(diff).to be(:include?, '+ "context": "New check"')
34+
expect(git("rev-parse", "HEAD")).to be == original
35+
expect(git("diff", "--cached")).to be == ""
36+
expect(File.read(File.join(repository, ".github/workflows/custom.yaml"))).to be == "Custom workflow\n"
37+
expect(setup.update).to be == []
38+
expect(git("diff")).to be == diff
39+
expect(YAML.safe_load_file(path)).to be == config
40+
end
41+
42+
it "restores missing generated files" do
43+
path = File.join(repository, ".github/releasing.md")
44+
original = File.read(path)
45+
File.unlink(path)
46+
expect(setup.update).to be == [".github/releasing.md"]
47+
expect(File.read(path)).to be == original
48+
expect(setup.update).to be == []
49+
end
50+
51+
it "refuses unsupported configuration schemas" do
52+
File.write(File.join(repository, "config/release.yaml"), YAML.dump("schema" => 2))
53+
expect{setup.update}.to raise_exception(RuntimeError, message: be =~ /Unsupported release configuration/)
3554
end
36-
expect(system("git", "apply", "--check", patch, chdir: root)).to be == true
37-
expect(system("git", "apply", patch, chdir: root)).to be == true
38-
expect(File.read(setup.update)).to be == ""
39-
expect(YAML.safe_load_file(path)).to be == config
40-
end
41-
42-
it "restores missing generated files through the patch" do
43-
path = File.join(root, ".github/releasing.md")
44-
original = File.read(path)
45-
File.unlink(path)
46-
patch = setup.update
47-
expect(File.exist?(path)).to be == false
48-
expect(system("git", "apply", patch, chdir: root)).to be == true
49-
expect(File.read(path)).to be == original
50-
expect(File.read(setup.update)).to be == ""
51-
end
52-
53-
it "refuses unsupported configuration schemas" do
54-
File.write(File.join(root, "config/release.yaml"), YAML.dump("schema" => 2))
55-
expect{setup.update}.to raise_exception(RuntimeError, message: be =~ /Unsupported release configuration/)
5655
end
5756
end

0 commit comments

Comments
 (0)