Skip to content

Commit 88cb831

Browse files
alpha(update): add --squash, --preserve-path, --output-branch for PR-friendly upgrades
This change makes `kubebuilder alpha update` produce a PR-ready branch and a single squashed commit when requested, improving automation and review UX. Key changes ----------- • New `--squash` flag: - Snapshots the exact tree of the temporary merge branch into ONE commit on a stable branch: `kubebuilder-alpha-update-to-<to-version>`. - Intended for opening/refreshing idempotent PRs. - Gracefully handles "no changes" (git commit exits 1 → treated as no-op). • New `--preserve-path` (repeatable): - When squashing, restore given paths from the base branch (e.g. `.github/workflows`) so CI/config files are kept as-is on the PR branch. • New `--output-branch`: - Overrides the default PR branch name created by `--squash`. • Commit message used by `--squash`: - `[kubebuilder-automated-update]: update scaffold from <from> to <to>; (squashed 3-way merge)` • Behavior/ergonomics: - Without `--force`: stops on conflicts on the temporary merge branch. - With `--force`: commits conflict markers on the merge branch (automation-friendly). - After merge, still best-effort run: `make manifests generate fmt vet lint-fix`. Defaults / Compatibility ------------------------ - `--squash` is off by default (no behavior change unless opted-in). - `--from-branch` defaults to `main`. - `--preserve-path` is empty by default (no restores). - Safe to run on projects scaffolded with v4.5.0+ (uses `alpha generate`). Motivation ---------- Make upgrades PR-centric and automation-ready by producing a deterministic, reviewable branch and a single squashed commit that mirrors the merge result. Assisted-by: ChatGPT (OpenAI)
1 parent 50755a0 commit 88cb831

File tree

6 files changed

+378
-136
lines changed

6 files changed

+378
-136
lines changed

docs/book/src/reference/commands/alpha_update.md

Lines changed: 102 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,150 +2,169 @@
22

33
## Overview
44

5-
The `kubebuilder alpha update` command helps you upgrade your project scaffold to a newer Kubebuilder version or plugin layout automatically.
5+
`kubebuilder alpha update` upgrades your project’s scaffold to a newer Kubebuilder version or plugin layout.
66

7-
It uses a **3-way merge strategy** to update your project with less manual work.
8-
To achieve that, the command creates the following branches:
7+
It uses a **3-way merge** so you do less manual work. The command creates these branches:
98

10-
- *Ancestor branch*: clean scaffold using the old version
11-
- *Current branch*: your existing project with your custom code
12-
- *Upgrade branch*: scaffold generated using the new version
9+
- **Ancestor**: clean scaffold from the **old** version
10+
- **Original**: your current project (from your base branch)
11+
- **Upgrade**: clean scaffold from the **new** version
12+
- **Merge**: result of merging **Original** into **Upgrade** (this is where conflicts appear)
1313

14-
Then, it creates a **merge branch** that combines everything.
15-
You can review and test this branch before applying the changes.
14+
You can review and test the merge result before applying it to your main branch.
15+
Optionally, use **`--squash`** to put the merge result into **one commit** on a stable output branch (great for PRs).
1616

1717
<aside class="note warning">
1818
<h1>Creates branches and deletes files</h1>
1919

20-
This command creates Git branches starting with `tmp-kb-update-` and deletes files during the process.
21-
Make sure to commit your work before running it.
20+
This command creates branches like `tmp-kb-update-*` and removes files during the process.
21+
Make sure your work is committed before you run it.
2222

2323
</aside>
2424

25-
## When to Use It?
25+
## When to Use It
2626

27-
Use this command when:
27+
Use this command when you:
2828

29-
- You want to upgrade your project to a newer Kubebuilder version or plugin layout
30-
- You prefer to automate the migration instead of updating files manually
31-
- You want to review scaffold changes in a separate Git branch
32-
- You want to focus only on fixing merge conflicts instead of re-applying all your code
29+
- Want to move to a newer Kubebuilder version or plugin layout
30+
- Prefer automation over manual file editing
31+
- Want to review scaffold changes on a separate branch
32+
- Want to focus on resolving merge conflicts (not re-applying your custom code)
3333

3434
## How It Works
3535

36-
The command performs the following steps:
36+
1. **Detect versions**
37+
Reads `--from-version` (or the `PROJECT` file) and `--to-version` (or uses the latest).
3738

38-
1. Downloads the older CLI version (from the `PROJECT` file or `--from-version`)
39-
2. Creates `tmp-kb-update-ancestor` with a clean scaffold using that version
40-
3. Creates `tmp-kb-update-current` and restores your current code on top
41-
4. Creates `tmp-kb-update-upgrade` using the latest scaffold
42-
5. Created `tmp-kb-update-merge` which is a merge of the above branches using the 3-way merge strategy
39+
2. **Create branches & re-scaffold**
40+
- `tmp-ancestor-*`: clean scaffold from **from-version**
41+
- `tmp-original-*`: snapshot of your **from-branch** (e.g., `main`)
42+
- `tmp-upgrade-*`: clean scaffold from **to-version**
4343

44-
You can push the `tmp-kb-update-merge` branch to your remote repository,
45-
review the diff, and test the changes before merging into your main branch.
44+
3. **3-way merge**
45+
Creates `tmp-merge-*` from **Upgrade** and merges **Original** into it.
46+
Runs `make manifests generate fmt vet lint-fix` to normalize outputs.
47+
48+
4. **(Optional) Squash**
49+
With `--squash`, copies the merge result to a stable output branch and commits **once**:
50+
- Default output branch: `kubebuilder-alpha-update-to-<to-version>`
51+
- Or set your own with `--output-branch`
52+
If there are conflicts, the single commit will include conflict markers.
53+
54+
Push either `tmp-merge-*` (no squash) or the output branch (with `--squash`) to open a PR.
4655

4756
## How to Use It
4857

49-
Run the command from your project directory:
58+
Run from your project root:
5059

51-
```sh
60+
```shell
5261
kubebuilder alpha update
5362
```
5463

55-
If needed, set a specific version or branch:
64+
Pin versions and base branch:
5665

57-
```sh
66+
```shell
5867
kubebuilder alpha update \
59-
--from-version=v4.5.2 \
60-
--to-version=v4.6.0 \
61-
--from-branch=main
68+
--from-version v4.5.2 \
69+
--to-version v4.6.0 \
70+
--from-branch main
6271
```
72+
Automation-friendly (proceed even with conflicts):
6373

64-
Force update even with merge conflicts:
65-
66-
```sh
74+
```shell
6775
kubebuilder alpha update --force
6876
```
6977

78+
Create a **single squashed commit** on a stable PR branch:
79+
80+
```shell
81+
kubebuilder alpha update --force --squash
82+
```
83+
84+
Squash while **preserving** paths from your base branch (keep CI/workflows, docs, etc.):
85+
86+
```shell
87+
kubebuilder alpha update --force --squash \
88+
--preserve-path .github/workflows \
89+
--preserve-path docs
90+
```
91+
92+
Use a **custom output branch** name:
93+
94+
```shell
95+
kubebuilder alpha update --force --squash \
96+
-output-branch upgrade/kb-to-v4.7.0
97+
```
98+
99+
100+
### Commit message used in `--squash` mode
101+
102+
> [kubebuilder-automated-update]: update scaffold from <from> to <to>; (squashed 3-way merge)
103+
70104
<aside class="note warning">
71105
<h1>You might need to upgrade your project first</h1>
72106

73-
This command uses `kubebuilder alpha generate` internally.
74-
As a result, the version of the CLI originally used to create your project must support `alpha generate`.
107+
This command uses `kubebuilder alpha generate` under the hood.
108+
We support projects created with <strong>v4.5.0+</strong>.
109+
If yours is older, first run `kubebuilder alpha generate` once to modernize the scaffold.
110+
After that, you can use `kubebuilder alpha update` for future upgrades.
75111

76-
This command has only been tested with projects created using **v4.5.0** or later.
77-
It might not work with projects that were initially created using a Kubebuilder version older than **v4.5.0**.
78-
79-
If your project was created with an older version, run `kubebuilder alpha generate` first to re-scaffold it.
80-
Once updated, you can use `kubebuilder alpha update` for future upgrades.
81112
</aside>
82113

83114
## Flags
84115

85-
| Flag | Description |
86-
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
87-
| `--from-version` | **Required for projects initialized with versions earlier than v4.6.0.** Kubebuilder version your project was created with. If unset, uses the `PROJECT` file. |
88-
| `--to-version` | Version to upgrade to. Defaults to the latest version. |
89-
| `--from-branch` | Git branch that contains your current project code. Defaults to `main`. |
90-
| `--force` | Force the update even if conflicts occur. Conflicted files will include conflict markers, and a commit will be created automatically. Ideal for automation (e.g., cronjobs, CI). |
91-
| `-h, --help` | Show help for this command. |
116+
| Flag | Description |
117+
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
118+
| `--from-version` | **Required for projects initialized before v4.6.0.** Kubebuilder version your project was created with. If unset, taken from the `PROJECT` file. |
119+
| `--to-version` | Version to upgrade to. Defaults to the latest release. |
120+
| `--from-branch` | Git branch that has your current project code. Defaults to `main`. |
121+
| `--force` | Continue even if merge conflicts happen. Conflicted files are committed with conflict markers (useful for CI/cron). |
122+
| `--squash` | Write the merge result as **one commit** on a stable output branch. |
123+
| `--preserve-path` | Repeatable. With `--squash`, restore these paths from the base branch (e.g., `--preserve-path .github/workflows`). |
124+
| `--output-branch` | Branch name to use for the squashed commit (default: `kubebuilder-alpha-update-to-<to-version>`). |
125+
| `-h, --help` | Show help for this command. |
126+
92127
<aside class="note">
93-
Projects generated with **Kubebuilder v4.6.0** or later include the `cliVersion` field in the `PROJECT` file.
94-
This field is used by `kubebuilder alpha update` to determine the correct CLI
95-
version for upgrading your project.
128+
<h1>CLI Version Tracking</h1>
129+
130+
Projects created with **Kubebuilder v4.6.0+** include `cliVersion` in the `PROJECT` file.
131+
We use that value to pick the correct CLI for re-scaffolding.
132+
96133
</aside>
97134

98135
## Merge Conflicts with `--force`
99136

100-
When you use the `--force` flag with `kubebuilder alpha update`, Git will complete the merge even if there are conflicts. The resulting commit will include conflict markers like:
101-
```
137+
When you use `--force`, Git finishes the merge even if there are conflicts. The commit will include markers like:
138+
139+
```shell
102140
<<<<<<< HEAD
103141
Your changes
104142
=======
105143
Incoming changes
106-
>>>>>>> branch-name
144+
>>>>>>> tmp-original-…
107145
```
108-
These conflicts will be committed in the
109-
`tmp-kb-update-merge` branch.
110146

111-
<aside class="note warning">
112-
You must manually resolve these conflicts before merging into your main branch.
147+
- **Without `--force`**: the command stops on `tmp-merge-*` and prints guidance; no commit is created.
148+
- **With `--force`**: the merge is committed (on `tmp-merge-*`, or on the output branch if using `--squash`) and contains the markers.
113149

114150
<aside class="note warning">
115-
<H1>If you face conflicts (using or not the --force flag) </H1>
116-
If the merge introduces conflicts, you must resolve them and **ensure** you execute the following command to regenerate the manifests and organise the files properly:
151+
You must resolve these conflicts before merging into `main` (or your base branch).
152+
<strong>After resolving conflicts, always run:</strong>
117153

118-
```bash
154+
```shell
119155
make manifests generate fmt vet lint-fix
120-
```
121-
122-
Alternatively, you may want to run:
123-
124-
```bash
156+
# or
125157
make all
126158
```
127-
</aside>
128-
129159

130-
## When to Use `--force`
131-
Use `--force` only in scenarios like:
132-
- Automated environments (e.g., CI pipelines or cron jobs)
133-
- When you need to create a PR even if conflicts are present
134-
- When a human will resolve the conflicts later
135-
`kubebuilder alpha update --force`
136-
137-
This ensures the update proceeds without manual blocking but shifts responsibility for conflict resolution to a follow-up manual step.
138-
139-
This approach is typically used in automation workflows where conflict markers are later addressed by a human, or where preserving the conflicting changes is acceptable for follow-up processing.
160+
</aside>
140161

141-
## Requirements
162+
## Demonstration
142163

143-
- A valid [PROJECT][project-config] file at the root of your project
144-
- A clean Git working directory (no uncommitted changes)
145-
- Git must be installed and available
164+
<iframe width="560" height="315" src="https://www.youtube.com/embed/J8zonID__8k?si=WC-FXOHX0mCjph71" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
146165

147166
## Further Resources
148167

149-
- [WIP: Design proposal for update automation](https://github.com/kubernetes-sigs/kubebuilder/pull/4302)
168+
- WIP: Design proposal for update automationhttps://github.com/kubernetes-sigs/kubebuilder/pull/4302
150169

151170
[project-config]: ../../reference/project-config.md

pkg/cli/alpha/internal/update/update.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"net/http"
2525
"os"
2626
"os/exec"
27+
"strings"
2728
"time"
2829

2930
"github.com/spf13/afero"
@@ -42,6 +43,18 @@ type Update struct {
4243
// Force commits the update changes even with merge conflicts
4344
Force bool
4445

46+
// Squash writes the merge result as a single commit on a stable branch when true.
47+
// The branch defaults to "kubebuilder-alpha-update-to-<ToVersion>" unless OutputBranch is set.
48+
Squash bool
49+
50+
// PreservePath lists paths to restore from the base branch when squashing (repeatable).
51+
// Example: ".github/workflows"
52+
PreservePath []string
53+
54+
// OutputBranch is the branch name to use with Squash.
55+
// If empty, it defaults to "kubebuilder-alpha-update-to-<ToVersion>".
56+
OutputBranch string
57+
4558
// UpdateBranches
4659
AncestorBranch string
4760
OriginalBranch string
@@ -105,6 +118,62 @@ func (opts *Update) Update() error {
105118
if err := opts.mergeOriginalToUpgrade(); err != nil {
106119
return fmt.Errorf("failed to merge upgrade into merge branch: %w", err)
107120
}
121+
// If requested, collapse the merge result into a single commit on a fixed branch
122+
if opts.Squash {
123+
if err := opts.squashToOutputBranch(); err != nil {
124+
return fmt.Errorf("failed to squash to output branch: %w", err)
125+
}
126+
}
127+
return nil
128+
}
129+
130+
// squashToOutputBranch takes the exact tree of the MergeBranch and writes it as ONE commit
131+
// on a branch derived from FromBranch (e.g., "main"). If PreservePath is set, those paths
132+
// are restored from the base branch after copying the merge tree, so CI config etc. stays put.
133+
func (opts *Update) squashToOutputBranch() error {
134+
// Default output branch name if not provided
135+
out := opts.OutputBranch
136+
if out == "" {
137+
out = "kubebuilder-alpha-update-to-" + opts.ToVersion
138+
}
139+
140+
// 1. Start from base (FromBranch)
141+
if err := exec.Command("git", "checkout", opts.FromBranch).Run(); err != nil {
142+
return fmt.Errorf("checkout %s: %w", opts.FromBranch, err)
143+
}
144+
if err := exec.Command("git", "checkout", "-B", out, opts.FromBranch).Run(); err != nil {
145+
return fmt.Errorf("create/reset %s from %s: %w", out, opts.FromBranch, err)
146+
}
147+
148+
// 2. Clean working tree (except .git) so the next checkout is a verbatim snapshot
149+
if err := exec.Command("sh", "-c",
150+
"find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +").Run(); err != nil {
151+
return fmt.Errorf("cleanup output branch: %w", err)
152+
}
153+
154+
// 3. Bring in the exact content from the merge branch (no re-merge -> no new conflicts)
155+
if err := exec.Command("git", "checkout", opts.MergeBranch, "--", ".").Run(); err != nil {
156+
return fmt.Errorf("checkout merge content: %w", err)
157+
}
158+
159+
// 4. Optionally restore preserved paths from base (keep CI, etc.)
160+
for _, p := range opts.PreservePath {
161+
p = strings.TrimSpace(p)
162+
if p != "" {
163+
_ = exec.Command("git", "restore", "--source", opts.FromBranch, "--staged", "--worktree", p).Run()
164+
}
165+
}
166+
167+
// 5. One commit (keep markers; bypass hooks if repos have pre-commit on conflicts)
168+
if err := exec.Command("git", "add", "--all").Run(); err != nil {
169+
return fmt.Errorf("stage output: %w", err)
170+
}
171+
msg := fmt.Sprintf("[kubebuilder-automated-update]: update scaffold from %s to %s; (squashed 3-way merge)",
172+
opts.FromVersion, opts.ToVersion)
173+
if err := exec.Command("git", "commit", "--no-verify", "-m", msg).Run(); err != nil {
174+
return nil
175+
}
176+
108177
return nil
109178
}
110179

0 commit comments

Comments
 (0)