blob: 1cb8c5eabba5abf27db156538023a4e237e7e8f0 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"fmt"
"regexp"
"strings"
"github.com/maruel/subcommands"
"go.chromium.org/luci/common/errors"
)
var (
skipSync bool
)
var cmdCreateBranch = &subcommands.Command{
UsageLine: "create <options>",
ShortDesc: "Create a branch.",
LongDesc: "Create a branch.",
CommandRun: func() subcommands.CommandRun {
c := &createBranchRun{}
c.Init()
c.Flags.BoolVar(&c.yes, "yes", false,
"If set, disables the boolean prompt confirming the branch name.")
// Arguments for determining branch name.
c.Flags.StringVar(&c.descriptor, "descriptor", "",
"Optional descriptor for this branch. Typically, this is a build "+
"target or a device, depending on the nature of the branch. Used "+
"to generate the branch name. Cannot be used with --custom.")
// Which manifest should be branched?
// TODO(@jackneus): Implement version logic
//c.Flags.StringVar(&c.version, "version", "",
// "Manifest version to branch off, e.g. '10509.0.0'. You may not branch "+
// "off of the same version twice unless you run "+
// "with --force.")
c.Flags.StringVar(&c.file, "file", "", "Path to manifest file to branch off.")
// What kind of branch is this?
// TODO(@jackneus): Figure out how to group these flags in the
// help dialog. Right now all flags are displayed in alphabetic
// order, which is less helpful.
c.Flags.BoolVar(&c.release, "release", false,
"The new branch is a release branch. "+
"Named as 'release-<descriptor>-R<Milestone>-<Major Version>.B'.")
c.Flags.BoolVar(&c.factory, "factory", false,
"The new branch is a factory branch. "+
"Named as 'factory-<Descriptor>-<Major Version>.B'.")
c.Flags.BoolVar(&c.firmware, "firmware", false,
"The new branch is a firmware branch. "+
"Named as 'firmware-<Descriptor>-<Major Version>.B'.")
c.Flags.BoolVar(&c.stabilize, "stabilize", false,
"The new branch is a minibranch. "+
"Named as 'stabilize-<Descriptor>-<Major Version>.B'.")
c.Flags.StringVar(&c.custom, "custom", "",
"Use a custom branch type with an explicit name. "+
"WARNING: custom names are dangerous. This tool greps branch "+
"names to determine which versions have already been branched. "+
"Version validation is not possible when the naming convention "+
"is broken. Use this at your own risk.")
return c
},
}
type createBranchRun struct {
CommonFlags
yes bool
descriptor string
version string
file string
release bool
factory bool
firmware bool
stabilize bool
custom string
skipSync bool
}
func (c *createBranchRun) getBranchType() (string, bool) {
var branch_type string
branch_types_selected := 0
if c.release {
branch_types_selected++
branch_type = "release"
}
if c.factory {
branch_types_selected++
branch_type = "factory"
}
if c.firmware {
branch_types_selected++
branch_type = "firmware"
}
if c.stabilize {
branch_types_selected++
branch_type = "stabilize"
}
if c.custom != "" {
branch_types_selected++
branch_type = "custom"
}
if branch_types_selected != 1 {
return "", false
}
return branch_type, true
}
func (c *createBranchRun) validate(args []string) (bool, string) {
if c.file == "" {
return false, "must set --file."
}
_, ok := c.getBranchType()
if !ok {
return false, "must select exactly one branch type " +
"(--release, --factory, --firmware, --stabilize, --custom)."
}
if c.descriptor != "" && c.custom != "" {
return false, "--descriptor cannot be used with --custom."
}
if c.version != "" && c.version[len(c.version)-1] != '0' {
return false, "cannot branch version from nonzero patch number."
}
return true, ""
}
// Getters so that functions using the branchCommand interface
// can access CommonFlags in the underlying struct.
func (c *createBranchRun) getRoot() string {
return c.Root
}
func (c *createBranchRun) getManifestUrl() string {
return c.ManifestUrl
}
// Determine the name for a branch.
// By convention, standard branch names must end with the stripped version
// string from which they were created, followed by '.B'.
//
// For example:
// - A branch created from 1.0.0 must end with -1.B
// - A branch created from 1.2.0 must end with -1.2.B
//
// Release branches have a slightly different naming scheme. They include
// the milestone from which they were created. Example: release-R12-1.2.B
func (c *createBranchRun) newBranchName() string {
if c.custom != "" {
return c.custom
}
vinfo := checkout.ReadVersion()
branchType, _ := c.getBranchType()
branchNameParts := []string{branchType}
if branchType == "release" {
branchNameParts = append(branchNameParts, fmt.Sprintf("R%d", vinfo.ChromeBranch))
}
if c.descriptor != "" {
branchNameParts = append(branchNameParts, c.descriptor)
}
branchNameParts = append(branchNameParts, vinfo.StrippedVersionString()+".B")
return strings.Join(branchNameParts, "-")
}
func (c *createBranchRun) Run(a subcommands.Application, args []string,
env subcommands.Env) int {
// Common setup (argument validation, repo init, etc.)
ret := Run(c, a, args, env)
if ret != 0 {
return ret
}
// Sync repo to manifest at provided path.
err := checkout.SyncToManifest(c.file)
if err != nil {
fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err.Error())
return 1
}
// Validate the version.
// Double check that the checkout has a zero patch number. Otherwise,
// we cannot branch from it.
vinfo := checkout.ReadVersion()
if vinfo.PatchNumber != 0 {
fmt.Fprintf(a.GetErr(), "Cannot branch version with nonzero patch number (version %s).",
vinfo.VersionString())
return 1
}
// Check that we did not already branch from this version.
// manifest-internal serves as the sentinel project.
manifest := checkout.Manifest()
manifestInternal, err := manifest.GetUniqueProject("chromeos/manifest-internal")
if err != nil {
fmt.Fprintf(a.GetErr(),
errors.Annotate(err, "Could not get chromeos/manifest-internal project.").Err().Error())
return 1
}
pattern := regexp.MustCompile(fmt.Sprintf(`.*-%s.B$`, vinfo.StrippedVersionString()))
branchExists, err := checkout.BranchExists(manifestInternal, pattern)
if err != nil {
fmt.Fprintf(a.GetErr(), err.Error())
return 1
}
if branchExists && !c.Force {
fmt.Fprintf(a.GetErr(), "Already branched %s. Please rerun with --force if you "+
"would like to proceed.", vinfo.VersionString())
}
// TODO(@jackneus): double check name with user via boolean CLI prompt
return 0
}