Skip to content

Commit 5e5b396

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 5e5b396

File tree

6 files changed

+360
-117
lines changed

6 files changed

+360
-117
lines changed

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

Lines changed: 88 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ The `kubebuilder alpha update` command helps you upgrade your project scaffold t
77
It uses a **3-way merge strategy** to update your project with less manual work.
88
To achieve that, the command creates the following branches:
99

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
10+
- **Ancestor**: clean scaffold using the _old_ version
11+
- **Original**: your current project (from your base branch)
12+
- **Upgrade**: scaffold generated using the _new_ version
13+
- **Merge**: a merge of **Original** into **Upgrade** (where any conflicts show up)
1314

14-
Then, it creates a **merge branch** that combines everything.
15-
You can review and test this branch before applying the changes.
15+
You can review and test the merge result before applying it to your main branch.
16+
Optionally, you can **squash** the merge result into a single commit on a stable output branch for easier PRs.
1617

1718
<aside class="note warning">
1819
<h1>Creates branches and deletes files</h1>
@@ -29,120 +30,143 @@ Use this command when:
2930
- You want to upgrade your project to a newer Kubebuilder version or plugin layout
3031
- You prefer to automate the migration instead of updating files manually
3132
- 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
33+
- You want to focus on resolving merge conflicts instead of re-applying all your custom code
3334

3435
## How It Works
3536

36-
The command performs the following steps:
37+
1. **Detect versions**
38+
Determines `--from-version` (or reads from your `PROJECT` file) and `--to-version` (or the latest available).
3739

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
40+
2. **Create branches & re-scaffold**
41+
- `tmp-ancestor-*`: clean scaffold from **from-version**
42+
- `tmp-original-*`: your **from-branch** snapshot (e.g., `main`)
43+
- `tmp-upgrade-*`: clean scaffold from **to-version**
4344

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.
45+
3. **3-way merge**
46+
Creates `tmp-merge-*` from **Upgrade** and merges **Original** into it.
47+
Runs: `make manifests generate fmt vet lint-fix` to normalize outputs.
48+
49+
4. **(Optional) Squash**
50+
With `--squash`, copies the exact merge tree to a stable output branch:
51+
- Default: `kubebuilder-alpha-update-to-<to-version>`
52+
- Or whatever you pass via `--output-branch`
53+
It then commits **once**. If there were conflicts, the single commit will contain conflict markers.
54+
55+
You can push either `tmp-merge-*` (no squashing) or the output branch (with `--squash`) to your remote and open a PR.
4656

4757
## How to Use It
4858

49-
Run the command from your project directory:
59+
Run from your project root:
5060

5161
```sh
5262
kubebuilder alpha update
5363
```
5464

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

5767
```sh
5868
kubebuilder alpha update \
59-
--from-version=v4.5.2 \
60-
--to-version=v4.6.0 \
61-
--from-branch=main
69+
--from-version v4.5.2 \
70+
--to-version v4.6.0 \
71+
--from-branch main
6272
```
6373

64-
Force update even with merge conflicts:
74+
Automation-friendly: proceed even with conflicts:
6575

6676
```sh
6777
kubebuilder alpha update --force
6878
```
6979

70-
<aside class="note warning">
71-
<h1>You might need to upgrade your project first</h1>
80+
Create a **single squashed commit** on a stable PR branch:
81+
82+
```sh
83+
kubebuilder alpha update --force --squash
84+
```
85+
86+
Squash while **preserving** paths from your base branch (keep CI/workflows, etc.):
87+
88+
```sh
89+
kubebuilder alpha update --force --squash \
90+
--preserve-path .github/workflows \
91+
--preserve-path docs
92+
```
93+
94+
Use a **custom output branch** name:
95+
96+
```sh
97+
kubebuilder alpha update --force --squash \
98+
--output-branch upgrade/kb-to-v4.7.0
99+
```
72100

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`.
101+
### Commit message used in `--squash` mode
75102

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**.
103+
```
104+
[kubebuilder-automated-update]: update scaffold from <from> to <to>; (squashed 3-way merge)
105+
```
78106

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.
107+
<aside class="note warning">
108+
<h1>You might need to upgrade your project first</h1>
109+
This command uses <code>kubebuilder alpha generate</code> internally. The Kubebuilder version originally used to create your project must support <code>alpha generate</code>.<br><br>
110+
This command has been tested with projects created using <strong>v4.5.0+</strong>. If your project predates that, first run <code>kubebuilder alpha generate</code> once to modernize the scaffold. After that, you can use <code>kubebuilder alpha update</code> for future upgrades.
81111
</aside>
82112

83113
## Flags
84114

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. |
115+
| Flag | Description |
116+
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
117+
| `--from-version` | **Required for projects initialized before v4.6.0.** Kubebuilder version your project was created with. If unset, uses the `PROJECT` file. |
118+
| `--to-version` | Version to upgrade to. Defaults to the latest release. |
119+
| `--from-branch` | Git branch that contains your current project code. Defaults to `main`. |
120+
| `--force` | Continue even if there are merge conflicts. Conflicted files are committed with conflict markers (great for CI/cron). |
121+
| `--squash` | Write the merge result as a **single commit** on a stable output branch. |
122+
| `--preserve-path` | Repeatable. When squashing, restore these paths from the base branch (e.g., `--preserve-path .github/workflows`). |
123+
| `--output-branch` | Custom branch name for the squashed commit (defaults to `kubebuilder-alpha-update-to-<to-version>`). |
124+
| `-h, --help` | Show help for this command. |
125+
92126
<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.
127+
Projects generated with **Kubebuilder v4.6.0+** include <code>cliVersion</code> in the <code>PROJECT</code> file. `kubebuilder alpha update` uses this to pick the correct CLI for re-scaffolding.
96128
</aside>
97129

98130
## Merge Conflicts with `--force`
99131

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:
132+
When you use `--force`, Git completes the merge even if there are conflicts. The resulting commit includes conflict markers like:
133+
101134
```
102135
<<<<<<< HEAD
103136
Your changes
104137
=======
105138
Incoming changes
106-
>>>>>>> branch-name
139+
>>>>>>> tmp-original-…
107140
```
108-
These conflicts will be committed in the
109-
`tmp-kb-update-merge` branch.
110141

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

114145
<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:
146+
You must manually resolve these conflicts before merging into your main branch.<br><br>
147+
<strong>After resolving conflicts, always run:</strong>
148+
</aside>
117149

118150
```bash
119151
make manifests generate fmt vet lint-fix
120-
```
121-
122-
Alternatively, you may want to run:
123-
124-
```bash
152+
# or
125153
make all
126154
```
127-
</aside>
128-
129155

130156
## 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`
136157

137-
This ensures the update proceeds without manual blocking but shifts responsibility for conflict resolution to a follow-up manual step.
158+
Use `--force` when:
159+
160+
- Running in automated environments (CI/cron) and you want a PR/branch even if conflicts occur
161+
- A human will resolve conflicts later
162+
- You prefer the tool to be non-blocking
138163

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.
164+
This ensures the update proceeds without manual blocking but
165+
shifts responsibility for conflict resolution to a follow-up step.
140166

141-
## Requirements
167+
## Demonstration
142168

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
169+
<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>
146170

147171
## Further Resources
148172

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

Lines changed: 65 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,14 @@ type Update struct {
4243
// Force commits the update changes even with merge conflicts
4344
Force bool
4445

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

0 commit comments

Comments
 (0)