Skip to content

Commit

Permalink
Add --git-metadata flag to buf push (#2953)
Browse files Browse the repository at this point in the history
  • Loading branch information
doriable authored May 16, 2024
1 parent d740a18 commit 6004e28
Show file tree
Hide file tree
Showing 6 changed files with 782 additions and 24 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,19 @@
is now deprecated.
- Move `buf mod prune` to `buf dep prune`. `buf mod prune` is now deprecated.
- Move `buf mod update` to `buf dep update`. `buf mod update` is now deprecated.
- Move `buf mod {clear-cache,cc}` to `buf registry cc`. `buf mod {clear-cache,cc}` is now deprecated.
- Move `buf mod {clear-cache,cc}` to `buf registry cc`. `buf mod {clear-cache,cc}` is now
deprecated.
- Move `buf beta graph` to stable as `buf dep graph`.
- Change the default visibility of `buf push --create-visibility` to `private` when the `--create`
flag is set. Users are no longer required to set `--create-visibility` when running
`buf push --create`.
- Add `buf push --label`, which allows users to set labels when pushing new commits to the BSR.
- Add `buf push --source-control-url`, which allows users to associate commits pushed to the BSR
with a URL to a source code repository.
- Add `buf push --create-default-label`, which allows users to set a default label for a repository
when calling `buf push --create`.
- Add `buf push --git-metadata`, which automatically sets appropriate `--label`,
`--source-control-url`, and `--create-default-label` flags based on the current Git repository.
- Add `buf convert --validate` to apply [protovalidate](https://github.com/bufbuild/protovalidate)
rules to incoming messages specified with `--from`.
- Deprecate `buf mod open`.
Expand Down
4 changes: 2 additions & 2 deletions private/buf/bufcli/flags_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ func BindCreateVisibility(flagSet *pflag.FlagSet, addr *string, flagName string,
flagSet.StringVar(
addr,
flagName,
"",
fmt.Sprintf(`The repository's visibility setting, if created. Must be set if --%s is set. Must be one of %s`, createFlagName, stringutil.SliceToString(allVisibiltyStrings)),
privateVisibility,
fmt.Sprintf(`The repository's visibility setting, if created. Can only be set with --%s. Must be one of %s`, createFlagName, stringutil.SliceToString(allVisibiltyStrings)),
)
}

Expand Down
212 changes: 191 additions & 21 deletions private/buf/cmd/buf/command/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,21 @@ package push

import (
"context"
"errors"
"fmt"
"strings"

"github.com/bufbuild/buf/private/buf/bufcli"
"github.com/bufbuild/buf/private/buf/bufctl"
"github.com/bufbuild/buf/private/buf/buffetch"
"github.com/bufbuild/buf/private/buf/bufworkspace"
"github.com/bufbuild/buf/private/bufpkg/bufanalysis"
"github.com/bufbuild/buf/private/bufpkg/bufmodule"
"github.com/bufbuild/buf/private/pkg/app"
"github.com/bufbuild/buf/private/pkg/app/appcmd"
"github.com/bufbuild/buf/private/pkg/app/appext"
"github.com/bufbuild/buf/private/pkg/command"
"github.com/bufbuild/buf/private/pkg/git"
"github.com/bufbuild/buf/private/pkg/slicesext"
"github.com/bufbuild/buf/private/pkg/stringutil"
"github.com/bufbuild/buf/private/pkg/syserror"
Expand All @@ -41,12 +46,15 @@ const (
createVisibilityFlagName = "create-visibility"
createDefaultLabelFlagName = "create-default-label"
sourceControlURLFlagName = "source-control-url"
gitMetadataFlagName = "git-metadata"

// All deprecated.
tagFlagName = "tag"
tagFlagShortName = "t"
draftFlagName = "draft"
branchFlagName = "branch"

gitOriginRemote = "origin"
)

var (
Expand Down Expand Up @@ -84,6 +92,7 @@ type flags struct {
CreateVisibility string
CreateDefaultLabel string
SourceControlURL string
GitMetadata bool
// special
InputHashtag string
}
Expand Down Expand Up @@ -116,7 +125,7 @@ func (f *flags) Bind(flagSet *pflag.FlagSet) {
createFlagName,
false,
fmt.Sprintf(
"Create the repository if it does not exist. Must set --%s",
"Create the repository if it does not exist. Defaults to creating a private repository if --%s is not set.",
createVisibilityFlagName,
),
)
Expand All @@ -132,6 +141,30 @@ func (f *flags) Bind(flagSet *pflag.FlagSet) {
"",
"The URL for viewing the source code of the pushed modules (e.g. the specific commit in source control).",
)
flagSet.BoolVar(
&f.GitMetadata,
gitMetadataFlagName,
false,
fmt.Sprintf(
`Uses the Git source control state to set flag values. If this flag is set, we will use the following values for your flags:
--%s to <git remote URL>/<repository name>/<route>/<checked out commit sha> (e.g. https://github.com/acme/weather/commit/ffac537e6cbbf934b08745a378932722df287a53).
--%s for each Git tag and branch pointing to the currently checked out commit. You can set additional labels using --%s with this flag.
--%s to the Git default branch (e.g. main) - this is only in effect if --%s is also set.
The source control URL and default branch is based on the required Git remote %q.
This flag is only compatible with checkouts of Git source repositories.
This flag does not allow you to set any of the following flags yourself: --%s, --%s.`,
sourceControlURLFlagName,
labelFlagName,
labelFlagName,
createDefaultLabelFlagName,
createFlagName,
gitOriginRemote,
sourceControlURLFlagName,
createDefaultLabelFlagName,
),
)

flagSet.StringSliceVarP(&f.Tags, tagFlagName, tagFlagShortName, nil, useLabelInstead)
_ = flagSet.MarkHidden(tagFlagName)
Expand Down Expand Up @@ -166,21 +199,34 @@ func run(
}

var uploadOptions []bufmodule.UploadOption
if labelUploadOption := getLabelUploadOption(flags); labelUploadOption != nil {
uploadOptions = append(uploadOptions, labelUploadOption)
}
if flags.Create {
createModuleVisiblity, err := bufmodule.ParseModuleVisibility(flags.CreateVisibility)
if flags.GitMetadata {
gitMetadataUploadOptions, err := getGitMetadataUploadOptions(ctx, container, flags)
if err != nil {
return err
}
uploadOptions = append(
uploadOptions,
bufmodule.UploadWithCreateIfNotExist(createModuleVisiblity, flags.CreateDefaultLabel),
)
}
if flags.SourceControlURL != "" {
uploadOptions = append(uploadOptions, bufmodule.UploadWithSourceControlURL(flags.SourceControlURL))
uploadOptions = append(uploadOptions, gitMetadataUploadOptions...)
// Accept any additional labels the user has set using `--label`
if len(flags.Labels) > 0 {
uploadOptions = append(uploadOptions, bufmodule.UploadWithLabels(slicesext.ToUniqueSorted(flags.Labels)...))
}
} else {
// Otherwise, we parse the flags set individually by the user.
if labelUploadOption := getLabelUploadOption(flags); labelUploadOption != nil {
uploadOptions = append(uploadOptions, labelUploadOption)
}
if flags.Create {
createModuleVisiblity, err := bufmodule.ParseModuleVisibility(flags.CreateVisibility)
if err != nil {
return err
}
uploadOptions = append(
uploadOptions,
bufmodule.UploadWithCreateIfNotExist(createModuleVisiblity, flags.CreateDefaultLabel),
)
}
if flags.SourceControlURL != "" {
uploadOptions = append(uploadOptions, bufmodule.UploadWithSourceControlURL(flags.SourceControlURL))
}
}

commits, err := uploader.Upload(ctx, workspace, uploadOptions...)
Expand Down Expand Up @@ -263,7 +309,7 @@ func validateFlags(flags *flags) error {
if err := validateLabelFlags(flags); err != nil {
return err
}
return nil
return validateGitMetadataFlags(flags)
}

func validateCreateFlags(flags *flags) error {
Expand All @@ -279,13 +325,6 @@ func validateCreateFlags(flags *flags) error {
return appcmd.NewInvalidArgumentError(err.Error())
}
} else {
if flags.CreateVisibility != "" {
return appcmd.NewInvalidArgumentErrorf(
"Cannot set --%s without --%s",
createVisibilityFlagName,
createFlagName,
)
}
if flags.CreateDefaultLabel != "" {
return appcmd.NewInvalidArgumentErrorf(
"Cannot set --%s without --%s",
Expand Down Expand Up @@ -347,6 +386,137 @@ func validateLabelFlagValues(flags *flags) error {
return nil
}

// We do not allow users to set --source-control-url, --create-default-label, and --label
// flags if the --git-metadata flag is set.
func validateGitMetadataFlags(flags *flags) error {
if flags.GitMetadata {
var usedFlags []string
if flags.SourceControlURL != "" {
usedFlags = append(usedFlags, sourceControlURLFlagName)
}
if flags.CreateDefaultLabel != "" {
usedFlags = append(usedFlags, createDefaultLabelFlagName)
}
if len(flags.Tags) > 0 {
usedFlags = append(usedFlags, tagFlagName)
}
if flags.Branch != "" {
usedFlags = append(usedFlags, branchFlagName)
}
if flags.Draft != "" {
usedFlags = append(usedFlags, draftFlagName)
}
if len(usedFlags) > 0 {
usedFlagsErrStr := strings.Join(
slicesext.Map(
usedFlags,
func(flag string) string { return fmt.Sprintf("--%s", flag) },
),
", ",
)
return appcmd.NewInvalidArgumentErrorf(
"The following flag(s) cannot be used in combination with --%s: %s.",
gitMetadataFlagName,
usedFlagsErrStr,
)
}
}
return nil
}

func getGitMetadataUploadOptions(
ctx context.Context,
container appext.Container,
flags *flags,
) ([]bufmodule.UploadOption, error) {
input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".")
if err != nil {
return nil, err
}
runner := command.NewRunner()
// validate that input is a dirRef and is a valid git checkout
if err := validateInputIsValidDirAndGitCheckout(ctx, runner, container, input); err != nil {
return nil, err
}
uncommittedFiles, err := git.CheckForUncommittedGitChanges(ctx, runner, input)
if err != nil {
return nil, err
}
if len(uncommittedFiles) > 0 {
return nil, fmt.Errorf("--%s requires that there are no uncommitted changes, uncommitted changes found in the following files: %v", gitMetadataFlagName, uncommittedFiles)
}
originRemote, err := git.GetRemote(ctx, runner, container, input, gitOriginRemote)
if err != nil {
if errors.Is(err, git.ErrRemoteNotFound) {
return nil, appcmd.NewInvalidArgumentErrorf("remote %s must be present on Git checkout: %w", gitOriginRemote, err)
}
return nil, err
}
currentGitCommit, err := git.GetCurrentHEADGitCommit(ctx, runner, input)
if err != nil {
return nil, err
}
var gitMetadataUploadOptions []bufmodule.UploadOption
gitLabelsUploadOption, err := getGitMetadataLabelsUploadOptions(ctx, runner, container, input, currentGitCommit)
if err != nil {
return nil, err
}
if gitLabelsUploadOption != nil {
gitMetadataUploadOptions = append(gitMetadataUploadOptions, gitLabelsUploadOption)
}
sourceControlURL := originRemote.SourceControlURL(currentGitCommit)
if sourceControlURL == "" {
return nil, appcmd.NewInvalidArgumentError("unable to determine source control URL for this repository; only GitHub/GitLab/BitBucket are supported")
}
gitMetadataUploadOptions = append(gitMetadataUploadOptions, bufmodule.UploadWithSourceControlURL(sourceControlURL))
if flags.Create {
createModuleVisibility, err := bufmodule.ParseModuleVisibility(flags.CreateVisibility)
if err != nil {
return nil, err
}
gitMetadataUploadOptions = append(
gitMetadataUploadOptions,
bufmodule.UploadWithCreateIfNotExist(createModuleVisibility, originRemote.HEADBranch()),
)
}
return gitMetadataUploadOptions, nil
}

func validateInputIsValidDirAndGitCheckout(
ctx context.Context,
runner command.Runner,
container appext.Container,
input string,
) error {
if _, err := buffetch.NewDirRefParser(container.Logger()).GetDirRef(ctx, input); err != nil {
return appcmd.NewInvalidArgumentErrorf("input %q is not a valid directory: %w", input, err)
}
if err := git.CheckDirectoryIsValidGitCheckout(ctx, runner, container, input); err != nil {
if errors.Is(err, git.ErrInvalidGitCheckout) {
return appcmd.NewInvalidArgumentErrorf("input %q is not a local Git repository checkout", input)
}
return err
}
return nil
}

func getGitMetadataLabelsUploadOptions(
ctx context.Context,
runner command.Runner,
envContainer app.EnvContainer,
input string,
gitCommitSha string,
) (bufmodule.UploadOption, error) {
refs, err := git.GetRefsForGitCommitAndRemote(ctx, runner, envContainer, input, gitOriginRemote, gitCommitSha)
if err != nil {
return nil, err
}
if len(refs) == 0 {
return nil, fmt.Errorf("no tags or branches found for HEAD, %s", gitCommitSha)
}
return bufmodule.UploadWithLabels(refs...), nil
}

func getLabelUploadOption(flags *flags) bufmodule.UploadOption {
// We do not allow the mixing of flags, so post-validation, we only expect one of the
// flags to be set. And so we return the corresponding bufmodule.UploadOption if any
Expand Down
Loading

0 comments on commit 6004e28

Please sign in to comment.