vault/tools/pipeline/internal/pkg/git/am.go
Ryan Cragun 025a6d5071
[VAULT-34829] pipeline(backport): add github create backport command (#30713)
Add a new `github create backport` sub-command that can create a
backport of a given pull request. The command has been designed around a
Github Actions workflow where it is triggered on a closed pull request
event with a guard that checks for merges:

```yaml
pull_request_target:
  types: closed

jobs:
  backport:
    if: github.even.pull_request.merged
    runs-on: "..."
```

Eventually this sub-command (or another similar one) can be used to
implemente backporting a CE pull request to the corresponding ce/*
branch in vault-enterprise. This functionality will be implemented in
VAULT-34827.

This backport runner has several new behaviors not present in the
existing backport assistant:
  - If the source PR was made against an enterprise branch we'll assume
    that we want create a CE backport.
  - Enterprise only files will be automatically _removed_ from the CE
    backport for you. This will not guarantee a working CE pull request
    but does quite a bit of the heavy lifting for you.
  - If the change only contains enterprise files we'll skip creating a
    CE backport.
  - If the corresponding CE branch is inactive (as defined in
    .release/versions.hcl) then we will skip creating a backport in most
    cases. The exceptions are changes that include docs, README, or
    pipeline changes as we assume that even active branches will want
    those changes.
  - Backport labels still work but _only_ to enterprise PR's. It is
    assumed that when the subsequent PRs are merged that their
    corresponding CE backports will be created.
  - Backport labels no longer include editions. They will now use the
    same schema as active versions defined .release/verions.hcl. E.g.
    `backport/1.19.x`. `main` is always assumed to be active.
  - The runner will always try and update the source PR with a Github
    comment regarding the status of each individual backport. Even if
    one attempt at backporting fails we'll continue until we've
    attempted all backports.

Signed-off-by: Ryan Cragun <me@ryan.ec>
2025-05-23 14:02:24 -06:00

136 lines
3.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package git
import (
"context"
"fmt"
"strings"
)
// AmOpts are the git am flags and arguments
// See: https://git-scm.com/docs/git-am
type AmOpts struct {
// Options
CommitterDateIsAuthorDate bool // --committer-date-is-author-date
Empty EmptyCommit // --empty=<mode>
Keep bool // --keep
KeepNonPatch bool // --keep-non-patch
MessageID bool // --message-id
NoMessageID bool // --no-message-id
NoReReReAutoupdate bool // --no-rerere-autoupdate
NoVerify bool // --no-verify
Quiet bool // --quiet
ReReReAutoupdate bool // --rerere-autoupdate
Signoff bool // --signoff
ThreeWayMerge bool // --3way
Whitespace ApplyWhitespaceAction // --whitespace=<action>
// Targets, depending on which combination of options you're setting
Mbox []string // <mbox|Maildir>
// Sequences
Abort bool // --abort
Continue bool // --continue
Quit bool // --quit
Resolved bool // --resolved
Retry bool // --retry
// Options that are allowed on sequences
AllowEmpty bool // --allow-empty
}
// Am runs the git am command
func (c *Client) Am(ctx context.Context, opts *AmOpts) (*ExecResponse, error) {
return c.Exec(ctx, "am", opts)
}
// String returns the options as a string
func (o *AmOpts) String() string {
return strings.Join(o.Strings(), " ")
}
// Strings returns the options as a string slice
func (o *AmOpts) Strings() []string {
if o == nil {
return nil
}
opts := []string{}
switch {
case o.Abort:
return append(opts, "--abort")
case o.Continue:
return append(opts, "--continue")
case o.Quit:
return append(opts, "--quit")
case o.Resolved:
if o.AllowEmpty {
opts = append(opts, "--allow-empty")
}
return append(opts, "--resolved")
case o.Retry:
return append(opts, "--retry")
}
if o.CommitterDateIsAuthorDate {
opts = append(opts, "--committer-date-is-author-date")
}
if o.Empty != "" {
opts = append(opts, fmt.Sprintf("--empty=%s", string(o.Empty)))
}
if o.Keep {
opts = append(opts, "--keep")
}
if o.KeepNonPatch {
opts = append(opts, "--keep-non-patch")
}
if o.MessageID {
opts = append(opts, "--message-id")
}
if o.NoMessageID {
opts = append(opts, "--no-message-id")
}
if o.NoReReReAutoupdate {
opts = append(opts, "--no-rerere-autoupdate")
}
if o.NoVerify {
opts = append(opts, "--no-verify")
}
if o.Quiet {
opts = append(opts, "--quiet")
}
if o.ReReReAutoupdate {
opts = append(opts, "--rerere-autoupdate")
}
if o.Signoff {
opts = append(opts, "--signoff")
}
if o.ThreeWayMerge {
opts = append(opts, "--3way")
}
if o.Whitespace != "" {
opts = append(opts, fmt.Sprintf("--whitespace=%s", string(o.Whitespace)))
}
if len(o.Mbox) > 0 {
opts = append(opts, o.Mbox...)
}
return opts
}