• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / kubebuilder / 16231804143

11 Jul 2025 11:47PM UTC coverage: 58.994%. Remained the same
16231804143

Pull #4922

github

web-flow
:seedling: Bump golang.org/x/tools from 0.34.0 to 0.35.0

Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.34.0 to 0.35.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.34.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4922: :seedling: Bump golang.org/x/tools from 0.34.0 to 0.35.0

2404 of 4075 relevant lines covered (58.99%)

13.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/cli/alpha/internal/update.go
1
/*
2
Copyright 2025 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package internal
18

19
import (
20
        "fmt"
21
        "io"
22
        "net/http"
23
        "os"
24
        "os/exec"
25
        "runtime"
26
        "strings"
27

28
        log "github.com/sirupsen/logrus"
29
        "github.com/spf13/afero"
30
        "golang.org/x/mod/semver"
31
        "sigs.k8s.io/kubebuilder/v4/pkg/config/store"
32
        "sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml"
33
        "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
34
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
35
)
36

37
// Update contains configuration for the update operation
38
type Update struct {
39
        // FromVersion specifies which version of Kubebuilder to use for the update.
40
        // If empty, the version from the PROJECT file will be used.
41
        FromVersion string
42
        // FromBranch specifies which branch to use as current when updating
43
        FromBranch string
44
        // CliVersion holds the version to be used during the upgrade process
45
        CliVersion string
46
        // BinaryURL holds the URL for downloading the specified binary from
47
        // the releases on GitHub
48
        BinaryURL string
49
}
50

51
// Update performs a complete project update by creating a three-way merge to help users
52
// upgrade their Kubebuilder projects. The process creates multiple Git branches:
53
// - ancestor: Clean state with old Kubebuilder version scaffolding
54
// - current: User's current project state
55
// - upgrade: New Kubebuilder version scaffolding
56
// - merge: Attempts to merge upgrade changes into current state
57
func (opts *Update) Update() error {
×
58
        // Download the specific Kubebuilder binary version for generating clean scaffolding
×
59
        tempDir, err := opts.downloadKubebuilderBinary()
×
60
        if err != nil {
×
61
                return fmt.Errorf("failed to download Kubebuilder %s binary: %w", opts.CliVersion, err)
×
62
        }
×
63
        log.Infof("Downloaded binary kept at %s for debugging purposes", tempDir)
×
64

×
65
        // Create ancestor branch with clean state for three-way merge
×
66
        if err := opts.checkoutAncestorBranch(); err != nil {
×
67
                return fmt.Errorf("failed to checkout the ancestor branch: %w", err)
×
68
        }
×
69

70
        // Remove all existing files to create a clean slate for re-scaffolding
71
        if err := opts.cleanUpAncestorBranch(); err != nil {
×
72
                return fmt.Errorf("failed to clean up the ancestor branch: %w", err)
×
73
        }
×
74

75
        // Generate clean scaffolding using the old Kubebuilder version
76
        if err := opts.runAlphaGenerate(tempDir, opts.CliVersion); err != nil {
×
77
                return fmt.Errorf("failed to run alpha generate on ancestor branch: %w", err)
×
78
        }
×
79

80
        // Create current branch representing user's existing project state
81
        if err := opts.checkoutCurrentOffAncestor(); err != nil {
×
82
                return fmt.Errorf("failed to checkout current off ancestor: %w", err)
×
83
        }
×
84

85
        // Create upgrade branch with new Kubebuilder version scaffolding
86
        if err := opts.checkoutUpgradeOffAncestor(); err != nil {
×
87
                return fmt.Errorf("failed to checkout upgrade off ancestor: %w", err)
×
88
        }
×
89

90
        // Create merge branch to attempt automatic merging of changes
91
        if err := opts.checkoutMergeOffCurrent(); err != nil {
×
92
                return fmt.Errorf("failed to checkout merge branch off current: %w", err)
×
93
        }
×
94

95
        // Attempt to merge upgrade changes into the user's current state
96
        if err := opts.mergeUpgradeIntoMerge(); err != nil {
×
97
                return fmt.Errorf("failed to merge upgrade into merge branch: %w", err)
×
98
        }
×
99

100
        return nil
×
101
}
102

103
// downloadKubebuilderBinary downloads the specified version of Kubebuilder binary
104
// from GitHub releases and saves it to a temporary directory with executable permissions.
105
// Returns the temporary directory path containing the binary.
106
func (opts *Update) downloadKubebuilderBinary() (string, error) {
×
107
        // Construct GitHub release URL based on current OS and architecture
×
108
        url := opts.BinaryURL
×
109

×
110
        log.Infof("Downloading the Kubebuilder %s binary from: %s", opts.CliVersion, url)
×
111

×
112
        // Create temporary directory for storing the downloaded binary
×
113
        fs := afero.NewOsFs()
×
114
        tempDir, err := afero.TempDir(fs, "", "kubebuilder"+opts.CliVersion+"-")
×
115
        if err != nil {
×
116
                return "", fmt.Errorf("failed to create temporary directory: %w", err)
×
117
        }
×
118

119
        // Create the binary file in the temporary directory
120
        binaryPath := tempDir + "/kubebuilder"
×
121
        file, err := os.Create(binaryPath)
×
122
        if err != nil {
×
123
                return "", fmt.Errorf("failed to create the binary file: %w", err)
×
124
        }
×
125
        defer func() {
×
126
                if err = file.Close(); err != nil {
×
127
                        log.Errorf("failed to close the file: %v", err)
×
128
                }
×
129
        }()
130

131
        // Download the binary from GitHub releases
132
        response, err := http.Get(url)
×
133
        if err != nil {
×
134
                return "", fmt.Errorf("failed to download the binary: %w", err)
×
135
        }
×
136
        defer func() {
×
137
                if err = response.Body.Close(); err != nil {
×
138
                        log.Errorf("failed to close the connection: %v", err)
×
139
                }
×
140
        }()
141

142
        // Check if download was successful
143
        if response.StatusCode != http.StatusOK {
×
144
                return "", fmt.Errorf("failed to download the binary: HTTP %d", response.StatusCode)
×
145
        }
×
146

147
        // Copy the downloaded content to the local file
148
        _, err = io.Copy(file, response.Body)
×
149
        if err != nil {
×
150
                return "", fmt.Errorf("failed to write the binary content to file: %w", err)
×
151
        }
×
152

153
        // Make the binary executable
154
        if err := os.Chmod(binaryPath, 0o755); err != nil {
×
155
                return "", fmt.Errorf("failed to make binary executable: %w", err)
×
156
        }
×
157

158
        log.Infof("Kubebuilder version %s successfully downloaded to %s", opts.CliVersion, binaryPath)
×
159

×
160
        return tempDir, nil
×
161
}
162

163
// checkoutAncestorBranch creates and switches to the 'ancestor' branch.
164
// This branch will serve as the common ancestor for the three-way merge,
165
// containing clean scaffolding from the old Kubebuilder version.
166
func (opts *Update) checkoutAncestorBranch() error {
×
167
        gitCmd := exec.Command("git", "checkout", "-b", "tmp-kb-update-ancestor")
×
168
        if err := gitCmd.Run(); err != nil {
×
169
                return fmt.Errorf("failed to create and checkout ancestor branch: %w", err)
×
170
        }
×
171
        log.Info("Created and checked out ancestor branch")
×
172

×
173
        return nil
×
174
}
175

176
// cleanUpAncestorBranch removes all files from the ancestor branch to create
177
// a clean state for re-scaffolding. This ensures the ancestor branch only
178
// contains pure scaffolding without any user modifications.
179
func (opts *Update) cleanUpAncestorBranch() error {
×
180
        log.Info("Cleaning all files and folders except .git and PROJECT")
×
181
        // Remove all tracked files from the Git repository
×
182
        cmd := exec.Command("find", ".", "-mindepth", "1", "-maxdepth", "1",
×
183
                "!", "-name", ".git",
×
184
                "!", "-name", "PROJECT",
×
185
                "-exec", "rm", "-rf", "{}", "+")
×
186
        log.Infof("Running cleanup command: %v", cmd.Args)
×
187
        if err := cmd.Run(); err != nil {
×
188
                return fmt.Errorf("failed to clean up files in ancestor branch: %w", err)
×
189
        }
×
190
        log.Info("Successfully cleanup files in ancestor branch")
×
191

×
192
        // Remove all untracked files and directories
×
193
        gitCmd := exec.Command("git", "add", ".")
×
194
        if err := gitCmd.Run(); err != nil {
×
195
                return fmt.Errorf("failed to stage changes in ancestor: %w", err)
×
196
        }
×
197
        log.Info("Successfully staged changes in ancestor")
×
198

×
199
        // Commit the cleanup to establish the clean state
×
200
        gitCmd = exec.Command("git", "commit", "-m", "Clean up the ancestor branch")
×
201
        if err := gitCmd.Run(); err != nil {
×
202
                return fmt.Errorf("failed to commit the cleanup in ancestor branch: %w", err)
×
203
        }
×
204
        log.Info("Successfully committed cleanup on ancestor")
×
205

×
206
        return nil
×
207
}
208

209
// runMakeTargets is a helper function to run make with the targets necessary
210
// to ensure all the necessary components are generated, formatted and linted.
211
func runMakeTargets() error {
×
212
        targets := []string{"manifests", "generate", "fmt", "vet", "lint-fix"}
×
213
        for _, target := range targets {
×
214
                log.Infof("Running: make %s", target)
×
215
                err := util.RunCmd(fmt.Sprintf("Running make %s", target), "make", target)
×
216
                if err != nil {
×
217
                        return fmt.Errorf("make %s failed: %v", target, err)
×
218
                }
×
219
        }
220
        return nil
×
221
}
222

223
// runAlphaGenerate executes the old Kubebuilder version's 'alpha generate' command
224
// to create clean scaffolding in the ancestor branch. This uses the downloaded
225
// binary with the original PROJECT file to recreate the project's initial state.
226
func (opts *Update) runAlphaGenerate(tempDir, version string) error {
×
227
        // Temporarily modify PATH to use the downloaded Kubebuilder binary
×
228
        tempBinaryPath := tempDir + "/kubebuilder"
×
229
        originalPath := os.Getenv("PATH")
×
230
        tempEnvPath := tempDir + ":" + originalPath
×
231

×
232
        if err := os.Setenv("PATH", tempEnvPath); err != nil {
×
233
                return fmt.Errorf("failed to set temporary PATH: %w", err)
×
234
        }
×
235

236
        // Restore original PATH when function completes
237
        defer func() {
×
238
                if err := os.Setenv("PATH", originalPath); err != nil {
×
239
                        log.Errorf("failed to restore original PATH: %v", err)
×
240
                }
×
241
        }()
242

243
        // Prepare the alpha generate command with proper I/O redirection
244
        cmd := exec.Command(tempBinaryPath, "alpha", "generate")
×
245
        cmd.Stdout = os.Stdout
×
246
        cmd.Stderr = os.Stderr
×
247
        cmd.Env = os.Environ()
×
248

×
249
        // Execute the alpha generate command to create clean scaffolding
×
250
        if err := cmd.Run(); err != nil {
×
251
                return fmt.Errorf("failed to run alpha generate: %w", err)
×
252
        }
×
253
        log.Info("Successfully ran alpha generate using Kubebuilder ", version)
×
254

×
255
        // Run make targets to ensure all the necessary components are generated,
×
256
        // formatted and linted.
×
257
        log.Info("Running 'make manifests generate fmt vet lint-fix'")
×
258
        if err := runMakeTargets(); err != nil {
×
259
                return fmt.Errorf("failed to run make: %w", err)
×
260
        }
×
261
        log.Info("Successfully ran make targets in ancestor")
×
262

×
263
        // Stage all generated files
×
264
        gitCmd := exec.Command("git", "add", ".")
×
265
        if err := gitCmd.Run(); err != nil {
×
266
                return fmt.Errorf("failed to stage changes in ancestor: %w", err)
×
267
        }
×
268
        log.Info("Successfully staged all changes in ancestor")
×
269

×
270
        // Commit the re-scaffolded project to the ancestor branch
×
271
        gitCmd = exec.Command("git", "commit", "-m", "Re-scaffold in ancestor")
×
272
        if err := gitCmd.Run(); err != nil {
×
273
                return fmt.Errorf("failed to commit changes in ancestor: %w", err)
×
274
        }
×
275
        log.Info("Successfully committed changes in ancestor")
×
276

×
277
        return nil
×
278
}
279

280
// checkoutCurrentOffAncestor creates the 'current' branch from ancestor and
281
// populates it with the user's actual project content from the default branch.
282
// This represents the current state of the user's project.
283
func (opts *Update) checkoutCurrentOffAncestor() error {
×
284
        // Create current branch starting from the clean ancestor state
×
285
        gitCmd := exec.Command("git", "checkout", "-b", "tmp-kb-update-current", "tmp-kb-update-ancestor")
×
286
        if err := gitCmd.Run(); err != nil {
×
287
                return fmt.Errorf("failed to checkout current branch off ancestor: %w", err)
×
288
        }
×
289
        log.Info("Successfully checked out current branch off ancestor")
×
290

×
291
        // Overlay the user's actual project content from default branch
×
292
        gitCmd = exec.Command("git", "checkout", opts.FromBranch, "--", ".")
×
293
        if err := gitCmd.Run(); err != nil {
×
294
                return fmt.Errorf("failed to checkout content from default branch onto current: %w", err)
×
295
        }
×
296
        log.Info("Successfully checked out content from main onto current branch")
×
297

×
298
        // Stage all the user's current project content
×
299
        gitCmd = exec.Command("git", "add", ".")
×
300
        if err := gitCmd.Run(); err != nil {
×
301
                return fmt.Errorf("failed to stage all changes in current: %w", err)
×
302
        }
×
303
        log.Info("Successfully staged all changes in current")
×
304

×
305
        // Commit the user's current state to the current branch
×
306
        gitCmd = exec.Command("git", "commit", "-m", "Add content from main onto current branch")
×
307
        if err := gitCmd.Run(); err != nil {
×
308
                return fmt.Errorf("failed to commit changes: %w", err)
×
309
        }
×
310
        log.Info("Successfully committed changes in current")
×
311

×
312
        return nil
×
313
}
314

315
// checkoutUpgradeOffAncestor creates the 'upgrade' branch from ancestor and
316
// generates fresh scaffolding using the current (latest) Kubebuilder version.
317
// This represents what the project should look like with the new version.
318
func (opts *Update) checkoutUpgradeOffAncestor() error {
×
319
        // Create upgrade branch starting from the clean ancestor state
×
320
        gitCmd := exec.Command("git", "checkout", "-b", "tmp-kb-update-upgrade", "tmp-kb-update-ancestor")
×
321
        if err := gitCmd.Run(); err != nil {
×
322
                return fmt.Errorf("failed to checkout upgrade branch off ancestor: %w", err)
×
323
        }
×
324
        log.Info("Successfully checked out upgrade branch off ancestor")
×
325

×
326
        // Run alpha generate with the current (new) Kubebuilder version
×
327
        // This uses the system's installed kubebuilder binary
×
328
        cmd := exec.Command("kubebuilder", "alpha", "generate")
×
329
        cmd.Stdout = os.Stdout
×
330
        cmd.Stderr = os.Stderr
×
331

×
332
        if err := cmd.Run(); err != nil {
×
333
                return fmt.Errorf("failed to run alpha generate on upgrade branch: %w", err)
×
334
        }
×
335
        log.Info("Successfully ran alpha generate on upgrade branch")
×
336

×
337
        // Run make targets to ensure all the necessary components are generated,
×
338
        // formatted and linted.
×
339
        log.Info("Running 'make manifests generate fmt vet lint-fix'")
×
340
        if err := runMakeTargets(); err != nil {
×
341
                return fmt.Errorf("failed to run make: %w", err)
×
342
        }
×
343
        log.Info("Successfully ran make targets in upgrade")
×
344

×
345
        // Stage all the newly generated files
×
346
        gitCmd = exec.Command("git", "add", ".")
×
347
        if err := gitCmd.Run(); err != nil {
×
348
                return fmt.Errorf("failed to stage changes on upgrade: %w", err)
×
349
        }
×
350
        log.Info("Successfully staged all changes in upgrade branch")
×
351

×
352
        // Commit the new version's scaffolding to the upgrade branch
×
353
        gitCmd = exec.Command("git", "commit", "-m", "alpha generate in upgrade branch")
×
354
        if err := gitCmd.Run(); err != nil {
×
355
                return fmt.Errorf("failed to commit changes in upgrade branch: %w", err)
×
356
        }
×
357
        log.Info("Successfully committed changes in upgrade branch")
×
358

×
359
        return nil
×
360
}
361

362
// checkoutMergeOffCurrent creates the 'merge' branch from the current branch.
363
// This branch will be used to attempt automatic merging of upgrade changes
364
// with the user's current project state.
365
func (opts *Update) checkoutMergeOffCurrent() error {
×
366
        gitCmd := exec.Command("git", "checkout", "-b", "tmp-kb-update-merge", "tmp-kb-update-current")
×
367
        if err := gitCmd.Run(); err != nil {
×
368
                return fmt.Errorf("failed to checkout merge branch off current: %w", err)
×
369
        }
×
370

371
        return nil
×
372
}
373

374
// mergeUpgradeIntoMerge attempts to merge the upgrade branch (containing new
375
// Kubebuilder scaffolding) into the merge branch (containing user's current state).
376
// If conflicts occur, it warns the user to resolve them manually rather than failing.
377
func (opts *Update) mergeUpgradeIntoMerge() error {
×
378
        gitCmd := exec.Command("git", "merge", "upgrade")
×
379
        err := gitCmd.Run()
×
380
        if err != nil {
×
381
                // Check if the error is due to merge conflicts (exit code 1)
×
382
                if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
×
383
                        log.Warn("Merge with conflicts. Please resolve them manually")
×
384
                        return nil // Don't treat conflicts as fatal errors
×
385
                }
×
386
                return fmt.Errorf("failed to merge the upgrade branch into the merge branch: %w", err)
×
387
        }
388

389
        // Run make targets to ensure all the necessary components are generated,
390
        // formatted and linted.
391
        log.Info("Running 'make manifests generate fmt vet lint-fix'")
×
392
        if err := runMakeTargets(); err != nil {
×
393
                return fmt.Errorf("failed to run make: %w", err)
×
394
        }
×
395
        log.Info("Successfully ran make targets in merge")
×
396

×
397
        return nil
×
398
}
399

400
// Validate checks if the user is in a git repository and if the repository is in a clean state.
401
// It also validates if the version specified by the user is in a valid format and available for
402
// download as a binary.
403
func (opts *Update) Validate() error {
×
404
        // Validate git repository
×
405
        if err := opts.validateGitRepo(); err != nil {
×
406
                return fmt.Errorf("failed to validate git repository: %w", err)
×
407
        }
×
408

409
        // Validate --from-branch
410
        if err := opts.validateFromBranch(); err != nil {
×
411
                return fmt.Errorf("failed to validate --from-branch: %w", err)
×
412
        }
×
413

414
        // Load the PROJECT configuration file
415
        projectConfigFile, err := opts.loadConfigFile()
×
416
        if err != nil {
×
417
                return fmt.Errorf("failed to load the PROJECT file: %w", err)
×
418
        }
×
419

420
        // Extract the cliVersion field from the PROJECT file
421
        opts.CliVersion = projectConfigFile.Config().GetCliVersion()
×
422

×
423
        // Determine which Kubebuilder version to use for the update
×
424
        if err := opts.defineFromVersion(); err != nil {
×
425
                return fmt.Errorf("failed to define version: %w", err)
×
426
        }
×
427

428
        // Validate if the specified version is available as a binary in the releases
429
        if err := opts.validateBinaryAvailability(); err != nil {
×
430
                return fmt.Errorf("failed to validate binary availability: %w", err)
×
431
        }
×
432

433
        return nil
×
434
}
435

436
// Load the PROJECT configuration file to get the current CLI version
437
func (opts *Update) loadConfigFile() (store.Store, error) {
×
438
        projectConfigFile := yaml.New(machinery.Filesystem{FS: afero.NewOsFs()})
×
439
        // TODO: assess if DefaultPath could be renamed to a more self-descriptive name
×
440
        if err := projectConfigFile.LoadFrom(yaml.DefaultPath); err != nil {
×
441
                if _, statErr := os.Stat(yaml.DefaultPath); os.IsNotExist(statErr) {
×
442
                        return projectConfigFile, fmt.Errorf("no PROJECT file found. Make sure you're in the project root directory")
×
443
                }
×
444
                return projectConfigFile, fmt.Errorf("fail to load the PROJECT file: %w", err)
×
445
        }
446
        return projectConfigFile, nil
×
447
}
448

449
// Define the version of the binary to be downloaded
450
func (opts *Update) defineFromVersion() error {
×
451
        // Allow override of the version from PROJECT file via command line flag
×
452
        if opts.FromVersion != "" {
×
453
                if !semver.IsValid(opts.FromVersion) {
×
454
                        return fmt.Errorf("invalid semantic version. Expect: vX.Y.Z (Ex: v4.5.0)")
×
455
                }
×
456
                opts.CliVersion = opts.FromVersion
×
457
        }
458

459
        if opts.CliVersion == "" {
×
460
                return fmt.Errorf("failed to retrieve Kubebuilder version from PROJECT file. Please use --from-version to inform it")
×
461
        }
×
462

463
        return nil
×
464
}
465

466
// Validate if the version specified is available as a binary for download
467
// from the releases
468
func (opts *Update) validateBinaryAvailability() error {
×
469
        // Ensure version has 'v' prefix for consistency with GitHub releases
×
470
        if !strings.HasPrefix(opts.CliVersion, "v") {
×
471
                opts.CliVersion = "v" + opts.CliVersion
×
472
        }
×
473

474
        // Construct the URL for pulling the binary from GitHub releases
475
        opts.BinaryURL = fmt.Sprintf("https://github.com/kubernetes-sigs/kubebuilder/releases/download/%s/kubebuilder_%s_%s",
×
476
                opts.CliVersion, runtime.GOOS, runtime.GOARCH)
×
477

×
478
        resp, err := http.Head(opts.BinaryURL)
×
479
        if err != nil {
×
480
                return fmt.Errorf("failed to check binary availability: %w", err)
×
481
        }
×
482
        defer func() {
×
483
                if err = resp.Body.Close(); err != nil {
×
484
                        log.Errorf("failed to close connection: %s", err)
×
485
                }
×
486
        }()
487

488
        switch resp.StatusCode {
×
489
        case http.StatusOK:
×
490
                log.Infof("Binary version %v is available", opts.CliVersion)
×
491
                return nil
×
492
        case http.StatusNotFound:
×
493
                return fmt.Errorf("binary version %s not found. Check versions available in releases",
×
494
                        opts.CliVersion)
×
495
        default:
×
496
                return fmt.Errorf("unexpected response %d when checking binary availability for version %s",
×
497
                        resp.StatusCode, opts.CliVersion)
×
498
        }
499
}
500

501
// Validate if in a git repository with clean state
502
func (opts *Update) validateGitRepo() error {
×
503
        // Check if in a git repository
×
504
        gitCmd := exec.Command("git", "rev-parse", "--git-dir")
×
505
        if err := gitCmd.Run(); err != nil {
×
506
                return fmt.Errorf("not in a git repository")
×
507
        }
×
508

509
        // Check if the branch has uncommitted changes
510
        gitCmd = exec.Command("git", "status", "--porcelain")
×
511
        output, err := gitCmd.Output()
×
512
        if err != nil {
×
513
                return fmt.Errorf("failed to check branch status: %w", err)
×
514
        }
×
515

516
        if len(strings.TrimSpace(string(output))) > 0 {
×
517
                return fmt.Errorf("working directory has uncommitted changes. Please commit or stash them before updating")
×
518
        }
×
519

520
        return nil
×
521
}
522

523
// Validate the branch passed to the --from-branch flag
524
func (opts *Update) validateFromBranch() error {
×
525
        // Set default if not specified
×
526
        if opts.FromBranch == "" {
×
527
                opts.FromBranch = "main"
×
528
        }
×
529

530
        // Check if the branch exists
531
        gitCmd := exec.Command("git", "rev-parse", "--verify", opts.FromBranch)
×
532
        if err := gitCmd.Run(); err != nil {
×
533
                return fmt.Errorf("%s branch does not exist locally. Run 'git branch -a' to see all available branches",
×
534
                        opts.FromBranch)
×
535
        }
×
536

537
        return nil
×
538
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc