mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-06 14:47:01 +02:00
[VAULT-36201] pipeline(git): add Go git client (#30645)
As I was working on VAULT-34829 it became clear that we needed to solve the problem of using Git from Go. Initially I tried to use the go-git/go-git pure Go implementation of Git, but it lacked several features that we needed. The next best option seemed to be shelling out to Git. What started as a simple way to execute Git commands with the requisite environment and configuration led to the implementation. A few notes: - I did not add support for all flags for the implemented sub-commands - I wanted it to handle automatic inject and configuration of PATs when operating against private remote repositories in Github. - I did not want to rely on the local machine having been configured. The client that is built in is capable of configuring everything that we need via environment variables. - I chose to go the environment variable route for configuration as it's the only way to not overwrite local configuration or set up our own cred store. As the git client ended up being ~50% of the work for VAULT-34829, I decided to extract it out into its own PR to reduce the review burden. NOTE: While we don't use it in CI, there is .golanci.yml present in the the tree and it causes problems in editors that expect it to be V2. As such I migrated it. Signed-off-by: Ryan Cragun <me@ryan.ec>
This commit is contained in:
parent
0d8f1d30f3
commit
cb321ce774
@ -1,20 +1,39 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
linters-settings:
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- depguard
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
list-mode: lax
|
||||
files:
|
||||
- "./sdk/**"
|
||||
- ./sdk/**
|
||||
allow:
|
||||
- "github.com/hashicorp/go-metrics/compat"
|
||||
- github.com/hashicorp/go-metrics/compat
|
||||
deny:
|
||||
- pkg: "github.com/hashicorp/go-metrics"
|
||||
- pkg: github.com/hashicorp/go-metrics
|
||||
desc: not allowed, use github.com/hashicorp/go-metrics/compat instead
|
||||
- pkg: "github.com/armon/go-metrics"
|
||||
- pkg: github.com/armon/go-metrics
|
||||
desc: not allowed, use github.com/hashicorp/go-metrics/compat instead
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- depguard
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
115
tools/pipeline/internal/pkg/git/apply.go
Normal file
115
tools/pipeline/internal/pkg/git/apply.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ApplyWhitespaceAction are actions Git can take when encountering whitespace
|
||||
// conflicts during apply.
|
||||
type ApplyWhitespaceAction = string
|
||||
|
||||
const (
|
||||
ApplyWhitespaceActionNoWarn ApplyWhitespaceAction = "nowarn"
|
||||
ApplyWhitespaceActionWarn ApplyWhitespaceAction = "warn"
|
||||
ApplyWhitespaceActionFix ApplyWhitespaceAction = "fix"
|
||||
ApplyWhitespaceActionError ApplyWhitespaceAction = "error"
|
||||
ApplyWhitespaceActionErrorAll ApplyWhitespaceAction = "error-all"
|
||||
)
|
||||
|
||||
// ApplyOpts are the git apply flags and arguments
|
||||
// See: https://git-scm.com/docs/git-apply
|
||||
type ApplyOpts struct {
|
||||
// Options
|
||||
AllowEmpty bool // --allow-empty
|
||||
Cached bool // --cached
|
||||
Check bool // --check
|
||||
Index bool // --index
|
||||
Ours bool // --ours
|
||||
Recount bool // --recount
|
||||
Stat bool // --stat
|
||||
Summary bool // --summary
|
||||
Theirs bool // --theirs
|
||||
ThreeWayMerge bool // -3way
|
||||
Union bool // --union
|
||||
Whitespace ApplyWhitespaceAction // --whitespace=<action>
|
||||
|
||||
// Targets, depending on which combination of options you're setting
|
||||
Patch []string // <patch>
|
||||
}
|
||||
|
||||
// Apply runs the git apply command
|
||||
func (c *Client) Apply(ctx context.Context, opts *ApplyOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "apply", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *ApplyOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *ApplyOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
if o.AllowEmpty {
|
||||
opts = append(opts, "--allow-empty")
|
||||
}
|
||||
|
||||
if o.Cached {
|
||||
opts = append(opts, "--cached")
|
||||
}
|
||||
|
||||
if o.Check {
|
||||
opts = append(opts, "--check")
|
||||
}
|
||||
|
||||
if o.Index {
|
||||
opts = append(opts, "--index")
|
||||
}
|
||||
|
||||
if o.Ours {
|
||||
opts = append(opts, "--ours")
|
||||
}
|
||||
|
||||
if o.Recount {
|
||||
opts = append(opts, "--recount")
|
||||
}
|
||||
|
||||
if o.Stat {
|
||||
opts = append(opts, "--stat")
|
||||
}
|
||||
|
||||
if o.Summary {
|
||||
opts = append(opts, "--summary")
|
||||
}
|
||||
|
||||
if o.Theirs {
|
||||
opts = append(opts, "--theirs")
|
||||
}
|
||||
|
||||
if o.ThreeWayMerge {
|
||||
opts = append(opts, "--3way")
|
||||
}
|
||||
|
||||
if o.Union {
|
||||
opts = append(opts, "--union")
|
||||
}
|
||||
|
||||
if o.Whitespace != "" {
|
||||
opts = append(opts, fmt.Sprintf("--whitespace=%s", string(o.Whitespace)))
|
||||
}
|
||||
|
||||
if len(o.Patch) > 0 {
|
||||
opts = append(opts, o.Patch...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
216
tools/pipeline/internal/pkg/git/branch.go
Normal file
216
tools/pipeline/internal/pkg/git/branch.go
Normal file
@ -0,0 +1,216 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BranchTrack are supported branch tracking options
|
||||
type BranchTrack = string
|
||||
|
||||
const (
|
||||
BranchTrackDirect BranchTrack = "direct"
|
||||
BranchTrackInherit BranchTrack = "inherit"
|
||||
)
|
||||
|
||||
// BranchOpts are the git branch flags and arguments
|
||||
// See: https://git-scm.com/docs/git-branch
|
||||
type BranchOpts struct {
|
||||
// Options
|
||||
Abbrev uint // --abbrev=<n>
|
||||
All bool // --all
|
||||
Contains string // --contains <commit>
|
||||
Copy bool // --copy
|
||||
CreateReflog bool // --create-reflog
|
||||
Delete bool // --delete
|
||||
Force bool // -f
|
||||
Format string // --format <format>
|
||||
IgnoreCase bool // --ignore-case
|
||||
List bool // --list
|
||||
Merged string // --merged <commit>
|
||||
Move bool // --move
|
||||
NoAbbrev bool // --no-abbrev
|
||||
NoColor bool // --no-color
|
||||
NoColumn bool // --no-column
|
||||
NoContains string // --no-contains <commit>
|
||||
NoMerged string // --no-merged <commit>
|
||||
NoTrack bool // --no-track
|
||||
OmitEmpty bool // --omit-empty
|
||||
PointsAt string // --points-at <object>
|
||||
Remotes bool // --remotes
|
||||
Quiet bool // --quiet
|
||||
SetUpstream bool // --set-upstream
|
||||
SetUpstreamTo string // --set-upstream-to=<upstream>
|
||||
ShowCurrent bool // --show-current
|
||||
Sort string // --sort=<key>
|
||||
Track BranchTrack // --track
|
||||
UnsetUpstream bool // --unset-upstream
|
||||
|
||||
// Targets. The branch command has several different modules. Set the correct
|
||||
// targets depending on which combination of options you're setting.
|
||||
BranchName string // <branchname>
|
||||
StartPoint string // <start-point>
|
||||
OldBranch string // <oldbranch>
|
||||
NewBranch string // <newbranch>
|
||||
Pattern []string // <pattern>
|
||||
}
|
||||
|
||||
// Branch runs the git branch command
|
||||
func (c *Client) Branch(ctx context.Context, opts *BranchOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "branch", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *BranchOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *BranchOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if o.Abbrev > 0 {
|
||||
opts = append(opts, fmt.Sprintf("--abbrev=%d", o.Abbrev))
|
||||
}
|
||||
|
||||
if o.All {
|
||||
opts = append(opts, "--all")
|
||||
}
|
||||
|
||||
if o.Contains != "" {
|
||||
opts = append(opts, fmt.Sprintf("--contains=%s", string(o.Contains)))
|
||||
}
|
||||
|
||||
if o.Copy {
|
||||
opts = append(opts, "--copy")
|
||||
}
|
||||
|
||||
if o.CreateReflog {
|
||||
opts = append(opts, "--create-reflog")
|
||||
}
|
||||
|
||||
if o.Delete {
|
||||
opts = append(opts, "--delete")
|
||||
}
|
||||
|
||||
if o.Force {
|
||||
opts = append(opts, "--force")
|
||||
}
|
||||
|
||||
if o.Format != "" {
|
||||
opts = append(opts, fmt.Sprintf("--format=%s", string(o.Format)))
|
||||
}
|
||||
|
||||
if o.IgnoreCase {
|
||||
opts = append(opts, "--ignore-case")
|
||||
}
|
||||
|
||||
if o.List {
|
||||
opts = append(opts, "--list")
|
||||
}
|
||||
|
||||
if o.Merged != "" {
|
||||
opts = append(opts, fmt.Sprintf("--merged=%s", string(o.Merged)))
|
||||
}
|
||||
|
||||
if o.Move {
|
||||
opts = append(opts, "--move")
|
||||
}
|
||||
|
||||
if o.NoAbbrev {
|
||||
opts = append(opts, "--no-abbrev")
|
||||
}
|
||||
|
||||
if o.NoColor {
|
||||
opts = append(opts, "--no-color")
|
||||
}
|
||||
|
||||
if o.NoColumn {
|
||||
opts = append(opts, "--no-column")
|
||||
}
|
||||
|
||||
if o.NoTrack {
|
||||
opts = append(opts, "--no-track")
|
||||
}
|
||||
|
||||
if o.NoContains != "" {
|
||||
opts = append(opts, fmt.Sprintf("--no-contains=%s", string(o.NoContains)))
|
||||
}
|
||||
|
||||
if o.NoMerged != "" {
|
||||
opts = append(opts, fmt.Sprintf("--no-merged=%s", string(o.NoMerged)))
|
||||
}
|
||||
|
||||
if o.OmitEmpty {
|
||||
opts = append(opts, "--omit-empty")
|
||||
}
|
||||
|
||||
if o.PointsAt != "" {
|
||||
opts = append(opts, fmt.Sprintf("--points-at=%s", string(o.PointsAt)))
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.Remotes {
|
||||
opts = append(opts, "--remotes")
|
||||
}
|
||||
|
||||
if o.SetUpstream {
|
||||
opts = append(opts, "--set-upstream")
|
||||
}
|
||||
|
||||
if o.SetUpstreamTo != "" {
|
||||
opts = append(opts, fmt.Sprintf("--set-upstream-to=%s", string(o.SetUpstreamTo)))
|
||||
}
|
||||
|
||||
if o.ShowCurrent {
|
||||
opts = append(opts, "--show-current")
|
||||
}
|
||||
|
||||
if o.Sort != "" {
|
||||
opts = append(opts, fmt.Sprintf("--sort=%s", string(o.Sort)))
|
||||
}
|
||||
|
||||
if o.Track != "" {
|
||||
opts = append(opts, fmt.Sprintf("--track=%s", string(o.Track)))
|
||||
}
|
||||
|
||||
if o.UnsetUpstream {
|
||||
opts = append(opts, "--unset-upstream")
|
||||
}
|
||||
|
||||
// Not all of these can be used at once but we try to put them in an order
|
||||
// where we won't cause problems if the correct flags and targets are set.
|
||||
|
||||
if o.BranchName != "" {
|
||||
opts = append(opts, o.BranchName)
|
||||
}
|
||||
|
||||
if o.OldBranch != "" {
|
||||
opts = append(opts, o.OldBranch)
|
||||
}
|
||||
|
||||
if o.NewBranch != "" {
|
||||
opts = append(opts, o.NewBranch)
|
||||
}
|
||||
|
||||
if o.StartPoint != "" {
|
||||
opts = append(opts, o.StartPoint)
|
||||
}
|
||||
|
||||
if len(o.Pattern) > 0 {
|
||||
opts = append(opts, o.Pattern...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
122
tools/pipeline/internal/pkg/git/checkout.go
Normal file
122
tools/pipeline/internal/pkg/git/checkout.go
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckoutOpts are the git checkout flags and arguments
|
||||
// See: https://git-scm.com/docs/git-checkout
|
||||
type CheckoutOpts struct {
|
||||
// Options
|
||||
NewBranch string // -b
|
||||
NewBranchForceCheckout string // -B
|
||||
Detach bool // --detach
|
||||
Force bool // -f
|
||||
Guess bool // --guess
|
||||
Progress bool // --progress
|
||||
NoTrack bool // --no-track
|
||||
Orphan string // --orphan
|
||||
Ours bool // --ours
|
||||
Quiet bool // --quiet
|
||||
Theirs bool // --theirs
|
||||
Track BranchTrack // --track
|
||||
|
||||
// Targets
|
||||
Branch string // <new-branch>
|
||||
StartPoint string // <start-point>
|
||||
Treeish string // <tree-ish>
|
||||
|
||||
// Paths
|
||||
PathSpec []string // -- <pathspec>
|
||||
}
|
||||
|
||||
// Branch runs the git checkout command
|
||||
func (c *Client) Checkout(ctx context.Context, opts *CheckoutOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "checkout", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *CheckoutOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *CheckoutOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
if o.NewBranch != "" {
|
||||
opts = append(opts, "-b", o.NewBranch)
|
||||
}
|
||||
|
||||
if o.NewBranchForceCheckout != "" {
|
||||
opts = append(opts, "-B", o.NewBranchForceCheckout)
|
||||
}
|
||||
|
||||
if o.Detach {
|
||||
opts = append(opts, "--detach")
|
||||
}
|
||||
|
||||
if o.Force {
|
||||
opts = append(opts, "--force")
|
||||
}
|
||||
|
||||
if o.Guess {
|
||||
opts = append(opts, "--guess")
|
||||
}
|
||||
|
||||
if o.NoTrack {
|
||||
opts = append(opts, "--no-track")
|
||||
}
|
||||
|
||||
if o.Orphan != "" {
|
||||
opts = append(opts, "--orphan", string(o.Orphan))
|
||||
}
|
||||
|
||||
if o.Ours {
|
||||
opts = append(opts, "--ours")
|
||||
}
|
||||
|
||||
if o.Progress {
|
||||
opts = append(opts, "--progress")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.Theirs {
|
||||
opts = append(opts, "--theirs")
|
||||
}
|
||||
|
||||
if o.Track != "" {
|
||||
opts = append(opts, fmt.Sprintf("--track=%s", string(o.Track)))
|
||||
}
|
||||
|
||||
// Do the <branch>, <start-point>, and <tree-ish> before pathspec
|
||||
if o.Branch != "" {
|
||||
opts = append(opts, o.Branch)
|
||||
}
|
||||
|
||||
if o.StartPoint != "" {
|
||||
opts = append(opts, o.StartPoint)
|
||||
}
|
||||
|
||||
if o.Treeish != "" {
|
||||
opts = append(opts, o.Treeish)
|
||||
}
|
||||
|
||||
// If there's a pathspec always set it last
|
||||
if len(o.PathSpec) > 0 {
|
||||
opts = append(append(opts, "--"), o.PathSpec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
147
tools/pipeline/internal/pkg/git/cherry-pick.go
Normal file
147
tools/pipeline/internal/pkg/git/cherry-pick.go
Normal file
@ -0,0 +1,147 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EmptyCommit are supported empty commit handling options
|
||||
type EmptyCommit = string
|
||||
|
||||
const (
|
||||
EmptyCommitDrop EmptyCommit = "drop"
|
||||
EmptyCommitKeep EmptyCommit = "keep"
|
||||
EmptyCommitStop EmptyCommit = "stop"
|
||||
)
|
||||
|
||||
// CherryPickOpts are the git cherry-pick flags and arguments
|
||||
// See: https://git-scm.com/docs/git-cherry-pick
|
||||
type CherryPickOpts struct {
|
||||
// Options
|
||||
AllowEmpty bool // --allow-empty
|
||||
AllowEmptyMessage bool // --allow-empty-message
|
||||
Empty EmptyCommit // --empty=
|
||||
FF bool // --ff
|
||||
GPGSign bool // --gpgsign
|
||||
GPGSignKeyID string // --gpgsign=<key-id>
|
||||
Mainline string // --mainline
|
||||
NoReReReAutoupdate bool // --no-rerere-autoupdate
|
||||
Record bool // -x
|
||||
ReReReAutoupdate bool // --rerere-autoupdate
|
||||
Signoff bool // --signoff
|
||||
Strategy MergeStrategy // --strategy
|
||||
StrategyOptions []MergeStrategyOption // --strategy-option=<option>
|
||||
|
||||
// Target
|
||||
Commit string // <commit>
|
||||
|
||||
// Sequences
|
||||
Continue bool // --continue
|
||||
Abort bool // --abort
|
||||
Quit bool // --quit
|
||||
}
|
||||
|
||||
// CherryPick runs the git cherry-pick command
|
||||
func (c *Client) CherryPick(ctx context.Context, opts *CherryPickOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "cherry-pick", opts)
|
||||
}
|
||||
|
||||
// CherryPickAbort aborts an in-progress cherry-pick
|
||||
func (c *Client) CherryPickAbort(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.CherryPick(ctx, &CherryPickOpts{Abort: true})
|
||||
}
|
||||
|
||||
// CherryPickContinue continues an in-progress cherry-pick
|
||||
func (c *Client) CherryPickContinue(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.CherryPick(ctx, &CherryPickOpts{Continue: true})
|
||||
}
|
||||
|
||||
// CherryPickQuit quits an in-progress cherry-pick
|
||||
func (c *Client) CherryPickQuit(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.CherryPick(ctx, &CherryPickOpts{Quit: true})
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *CherryPickOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *CherryPickOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.Abort:
|
||||
return []string{"--abort"}
|
||||
case o.Continue:
|
||||
return []string{"--continue"}
|
||||
case o.Quit:
|
||||
return []string{"--quit"}
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if o.AllowEmpty {
|
||||
opts = append(opts, "--allow-empty")
|
||||
}
|
||||
|
||||
if o.AllowEmptyMessage {
|
||||
opts = append(opts, "--allow-empty-message")
|
||||
}
|
||||
|
||||
if o.Empty != "" {
|
||||
opts = append(opts, fmt.Sprintf("--empty=%s", string(o.Empty)))
|
||||
}
|
||||
|
||||
if o.FF {
|
||||
opts = append(opts, "--ff")
|
||||
}
|
||||
|
||||
if o.GPGSign {
|
||||
opts = append(opts, "--gpg-sign")
|
||||
}
|
||||
|
||||
if o.GPGSignKeyID != "" {
|
||||
opts = append(opts, fmt.Sprintf("--gpg-sign=%s", o.GPGSignKeyID))
|
||||
}
|
||||
|
||||
if o.Mainline != "" {
|
||||
opts = append(opts, fmt.Sprintf("--mainline=%s", o.Mainline))
|
||||
}
|
||||
|
||||
if o.NoReReReAutoupdate {
|
||||
opts = append(opts, "--no-rerere-autoupdate")
|
||||
}
|
||||
|
||||
if o.Record {
|
||||
opts = append(opts, "-x")
|
||||
}
|
||||
|
||||
if o.ReReReAutoupdate {
|
||||
opts = append(opts, "--rerere-autoupdate")
|
||||
}
|
||||
|
||||
if o.Signoff {
|
||||
opts = append(opts, "--signoff")
|
||||
}
|
||||
|
||||
if o.Strategy != "" {
|
||||
opts = append(opts, fmt.Sprintf("--strategy=%s", string(o.Strategy)))
|
||||
}
|
||||
|
||||
for _, opt := range o.StrategyOptions {
|
||||
opts = append(opts, fmt.Sprintf("--strategy-option=%s", string(opt)))
|
||||
}
|
||||
|
||||
if o.Commit != "" {
|
||||
opts = append(opts, o.Commit)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
182
tools/pipeline/internal/pkg/git/client.go
Normal file
182
tools/pipeline/internal/pkg/git/client.go
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
oexec "os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
slogctx "github.com/veqryn/slog-context"
|
||||
)
|
||||
|
||||
// Client is the local git client.
|
||||
type Client struct {
|
||||
Token string
|
||||
envOnce sync.Once
|
||||
envVal []string
|
||||
config map[string]string
|
||||
}
|
||||
|
||||
// OptStringer is an interface that all sub-command configuration options must
|
||||
// implement.
|
||||
type OptStringer interface {
|
||||
String() string
|
||||
Strings() []string
|
||||
}
|
||||
|
||||
// ExecResponse is the response from the client running a sub-command with Exec()
|
||||
type ExecResponse struct {
|
||||
Cmd string
|
||||
Env []string
|
||||
Stdout []byte
|
||||
Stderr []byte
|
||||
}
|
||||
|
||||
// NewClientOpt is a NewClient() functional option
|
||||
type NewClientOpt func(*Client)
|
||||
|
||||
// NewClient takes variable options and returns a default Client.
|
||||
func NewClient(opts ...NewClientOpt) *Client {
|
||||
client := &Client{
|
||||
config: map[string]string{
|
||||
"core.pager": "",
|
||||
"user.name": "hc-github-team-secure-vault-core",
|
||||
"user.email": "github-team-secure-vault-core@hashicorp.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(client)
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// WithToken sets the Token in NewClient()
|
||||
func WithToken(token string) NewClientOpt {
|
||||
return func(client *Client) {
|
||||
client.Token = token
|
||||
}
|
||||
}
|
||||
|
||||
// WithToken sets additional gitconfig in NewClient()
|
||||
func WithConfig(config map[string]string) NewClientOpt {
|
||||
return func(client *Client) {
|
||||
maps.Copy(client.config, config)
|
||||
}
|
||||
}
|
||||
|
||||
// WithLoadTokenFromEnv sets the Token from known env vars in NewClient()
|
||||
func WithLoadTokenFromEnv() NewClientOpt {
|
||||
return func(client *Client) {
|
||||
if token, ok := os.LookupEnv("GITHUB_TOKEN"); ok {
|
||||
client.Token = token
|
||||
return
|
||||
}
|
||||
if token, ok := os.LookupEnv("GH_TOKEN"); ok {
|
||||
client.Token = token
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exec executes a git sub-command.
|
||||
func (c *Client) Exec(ctx context.Context, subCmd string, opts OptStringer) (*ExecResponse, error) {
|
||||
env := os.Environ()
|
||||
res := &ExecResponse{Env: os.Environ()}
|
||||
if c.Token != "" {
|
||||
res.Env = c.configEnv()
|
||||
env = append(env, res.Env...)
|
||||
}
|
||||
|
||||
cmd := oexec.Command("git", append([]string{subCmd}, opts.Strings()...)...)
|
||||
cmd.Env = env
|
||||
res.Cmd = cmd.String()
|
||||
ctx = slogctx.Append(ctx, slog.String("cmd", cmd.String()))
|
||||
slog.Default().DebugContext(ctx, "executing git command")
|
||||
var err error
|
||||
res.Stdout, err = cmd.Output()
|
||||
if err != nil {
|
||||
slog.Default().ErrorContext(slogctx.Append(ctx,
|
||||
slog.String("error", err.Error()),
|
||||
), "executing git command failed")
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
res.Stderr = exitErr.Stderr
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// String returns the ExecResponse command and output as a string
|
||||
func (e *ExecResponse) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
b := strings.Builder{}
|
||||
b.WriteString(e.Cmd)
|
||||
b.WriteString("\n")
|
||||
for _, line := range strings.Split(string(e.Stdout), "\n") {
|
||||
b.WriteString(line)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
for _, line := range strings.Split(string(e.Stderr), "\n") {
|
||||
b.WriteString(line)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// configEnv creates a slice of all git configuration as environment variables
|
||||
// to avoid:
|
||||
// - modifying local or global gitconfig
|
||||
// - relying on preconfigured gitconfig
|
||||
// - requiring a credstore
|
||||
// - sensitive values like tokens being passed via flags and thus potentially
|
||||
// bleeding into STDOUT
|
||||
//
|
||||
// As this is relatively expensive it's only done once and cached so subsequent
|
||||
// requests can reuse the same configuration.
|
||||
func (c *Client) configEnv() []string {
|
||||
c.envOnce.Do(func() {
|
||||
env := c.config
|
||||
|
||||
if c.Token != "" {
|
||||
// NOTE: This basic auth token probably only works with Github right now,
|
||||
// which is fine because our pipeline only supports Github. Other SCM repos
|
||||
// have different rules around the user in the auth portion of the URL.
|
||||
// Github doesn't care what the username is but requires one to be set so
|
||||
// we always set it to user.
|
||||
token := url.UserPassword("user", c.Token).String()
|
||||
env[fmt.Sprintf("url.https://%s@github.com.insteadOf", token)] = "https://github.com"
|
||||
}
|
||||
|
||||
vars := []string{fmt.Sprintf("GIT_CONFIG_COUNT=%d", len(env))}
|
||||
count := 0
|
||||
for k, v := range env {
|
||||
vars = append(
|
||||
vars,
|
||||
fmt.Sprintf("GIT_CONFIG_KEY_%d=%s", count, k),
|
||||
fmt.Sprintf("GIT_CONFIG_VALUE_%d=%s", count, v),
|
||||
)
|
||||
count++
|
||||
}
|
||||
|
||||
c.envVal = vars
|
||||
})
|
||||
|
||||
return c.envVal
|
||||
}
|
107
tools/pipeline/internal/pkg/git/clone.go
Normal file
107
tools/pipeline/internal/pkg/git/clone.go
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CloneOpts are the git clone flags and arguments
|
||||
// See: https://git-scm.com/docs/git-clone
|
||||
type CloneOpts struct {
|
||||
// Options
|
||||
Branch string // --branch
|
||||
Depth uint // --depth
|
||||
NoCheckout bool // --no-checkout
|
||||
NoTags bool // --no-tags
|
||||
Origin string // --origin
|
||||
Progress bool // --progress
|
||||
Quiet bool // --quiet
|
||||
Revision string // --revision
|
||||
SingleBranch bool // --single-branch
|
||||
Sparse bool // --sparse
|
||||
Verbose bool // --verbose
|
||||
|
||||
// Targets
|
||||
Repository string // <repository>
|
||||
Directory string // <directory>
|
||||
}
|
||||
|
||||
// Clone runs the git clone command
|
||||
func (c *Client) Clone(ctx context.Context, opts *CloneOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "clone", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *CloneOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *CloneOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
if o.Branch != "" {
|
||||
opts = append(opts, "--branch", o.Branch)
|
||||
}
|
||||
|
||||
if o.Depth > 0 {
|
||||
opts = append(opts, "--depth", strconv.FormatUint(uint64(o.Depth), 10))
|
||||
}
|
||||
|
||||
if o.NoCheckout {
|
||||
opts = append(opts, "--no-checkout")
|
||||
}
|
||||
|
||||
if o.NoTags {
|
||||
opts = append(opts, "--no-tags")
|
||||
}
|
||||
|
||||
if o.Origin != "" {
|
||||
opts = append(opts, "--origin", o.Origin)
|
||||
}
|
||||
|
||||
if o.Progress {
|
||||
opts = append(opts, "--progress")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.Revision != "" {
|
||||
opts = append(opts, "--revision", o.Revision)
|
||||
}
|
||||
|
||||
if o.SingleBranch {
|
||||
opts = append(opts, "--single-branch")
|
||||
}
|
||||
|
||||
if o.Sparse {
|
||||
opts = append(opts, "--sparse")
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
opts = append(opts, "--verbose")
|
||||
}
|
||||
|
||||
if o.Repository != "" || o.Directory != "" {
|
||||
opts = append(opts, "--")
|
||||
|
||||
if o.Repository != "" {
|
||||
opts = append(opts, o.Repository)
|
||||
}
|
||||
|
||||
if o.Directory != "" {
|
||||
opts = append(opts, o.Directory)
|
||||
}
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
218
tools/pipeline/internal/pkg/git/commit.go
Normal file
218
tools/pipeline/internal/pkg/git/commit.go
Normal file
@ -0,0 +1,218 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// Cleanup are cleanup modes
|
||||
Cleanup string
|
||||
// FixupLog configures how to fix up a commit
|
||||
FixupLog string
|
||||
// UntrackedFiles are how to show untracked files
|
||||
UntrackedFiles string
|
||||
)
|
||||
|
||||
const (
|
||||
CommitCleanupModeString Cleanup = "strip"
|
||||
CommitCleanupModeWhitespace Cleanup = "whitespace"
|
||||
CommitCleanupModeVerbatim Cleanup = "verbatim"
|
||||
CommitCleanupModeScissors Cleanup = "scissors"
|
||||
CommitCleanupModeDefault Cleanup = "default"
|
||||
CommitFixupLogNone FixupLog = ""
|
||||
CommitFixupLogAmend FixupLog = "amend"
|
||||
CommitFixupLogReword FixupLog = "reword"
|
||||
UntrackedFilesNo UntrackedFiles = "no"
|
||||
UntrackedFilesNormal UntrackedFiles = "normal"
|
||||
UntrackedFilesAll UntrackedFiles = "all"
|
||||
)
|
||||
|
||||
// CommitFixup is how to fixup a commit
|
||||
type CommitFixup struct {
|
||||
FixupLog
|
||||
Commit string
|
||||
}
|
||||
|
||||
// CommitOpts are the git commit flags and arguments
|
||||
// See: https://git-scm.com/docs/git-commit
|
||||
type CommitOpts struct {
|
||||
// Options
|
||||
All bool // --all
|
||||
AllowEmpty bool // --allow-empty
|
||||
AllowEmptyMessage bool // --allow-empty-message
|
||||
Amend bool // --amend
|
||||
Author string // --author=<author>
|
||||
Branch bool // --branch
|
||||
Cleanup Cleanup // --cleanup=<mode>
|
||||
Date string // --date=<date>
|
||||
DryRun bool // --dry-run
|
||||
File string // --file=<file>
|
||||
Fixup *CommitFixup // --fixup=
|
||||
GPGSign bool // --gpgsign
|
||||
GPGSignKeyID string // --gpgsign=<key-id>
|
||||
Long bool // --long
|
||||
Patch bool // --patch
|
||||
Porcelain bool // --porcelain
|
||||
Message string // --message=<message>
|
||||
NoEdit bool // --no-edit
|
||||
NoPostRewrite bool // --no-post-rewrite
|
||||
NoVerify bool // --no-verify
|
||||
Null bool // --null
|
||||
Only bool // --only
|
||||
Quiet bool // --quiet
|
||||
ResetAuthor bool // --reset-author
|
||||
ReuseMessage string // --reuse-message=<commit>
|
||||
Short bool // --short
|
||||
Signoff bool // --signoff
|
||||
Status bool // --status
|
||||
Verbose bool // --verbose
|
||||
|
||||
// Target
|
||||
PathSpec []string // <pathspec>
|
||||
}
|
||||
|
||||
// Commit runs the git commit command
|
||||
func (c *Client) Commit(ctx context.Context, opts *CommitOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "commit", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *CommitOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *CommitOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
if o.All {
|
||||
opts = append(opts, "--all")
|
||||
}
|
||||
|
||||
if o.AllowEmpty {
|
||||
opts = append(opts, "--allow-empty")
|
||||
}
|
||||
|
||||
if o.AllowEmptyMessage {
|
||||
opts = append(opts, "--allow-empty-message")
|
||||
}
|
||||
|
||||
if o.Amend {
|
||||
opts = append(opts, "--amend")
|
||||
}
|
||||
|
||||
if o.Author != "" {
|
||||
opts = append(opts, fmt.Sprintf("--author=%s", o.Author))
|
||||
}
|
||||
|
||||
if o.Branch {
|
||||
opts = append(opts, "--branch")
|
||||
}
|
||||
|
||||
if o.Cleanup != "" {
|
||||
opts = append(opts, fmt.Sprintf("--cleanup=%s", string(o.Cleanup)))
|
||||
}
|
||||
|
||||
if o.Date != "" {
|
||||
opts = append(opts, fmt.Sprintf("--date=%s", o.Date))
|
||||
}
|
||||
|
||||
if o.DryRun {
|
||||
opts = append(opts, "--dry-run")
|
||||
}
|
||||
|
||||
if o.File != "" {
|
||||
opts = append(opts, fmt.Sprintf("--file=%s", o.File))
|
||||
}
|
||||
|
||||
if o.Fixup != nil {
|
||||
if o.Fixup.FixupLog == CommitFixupLogNone {
|
||||
opts = append(opts, fmt.Sprintf("--fixup=%s", string(o.Fixup.Commit)))
|
||||
} else {
|
||||
opts = append(opts, fmt.Sprintf("--fixup=%s:%s", string(o.Fixup.FixupLog), string(o.Fixup.Commit)))
|
||||
}
|
||||
}
|
||||
|
||||
if o.GPGSign {
|
||||
opts = append(opts, "--gpg-sign")
|
||||
}
|
||||
|
||||
if o.GPGSignKeyID != "" {
|
||||
opts = append(opts, fmt.Sprintf("--gpg-sign=%s", o.GPGSignKeyID))
|
||||
}
|
||||
|
||||
if o.Long {
|
||||
opts = append(opts, "--long")
|
||||
}
|
||||
|
||||
if o.Patch {
|
||||
opts = append(opts, "--patch")
|
||||
}
|
||||
|
||||
if o.Porcelain {
|
||||
opts = append(opts, "--porcelain")
|
||||
}
|
||||
|
||||
if o.Message != "" {
|
||||
opts = append(opts, fmt.Sprintf("--message=%s", o.Message))
|
||||
}
|
||||
|
||||
if o.NoEdit {
|
||||
opts = append(opts, "--no-edit")
|
||||
}
|
||||
|
||||
if o.NoPostRewrite {
|
||||
opts = append(opts, "--no-post-rewrite")
|
||||
}
|
||||
|
||||
if o.NoVerify {
|
||||
opts = append(opts, "--no-verify")
|
||||
}
|
||||
|
||||
if o.Null {
|
||||
opts = append(opts, "--null")
|
||||
}
|
||||
|
||||
if o.Only {
|
||||
opts = append(opts, "--only")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.ResetAuthor {
|
||||
opts = append(opts, "--reset-author")
|
||||
}
|
||||
|
||||
if o.ReuseMessage != "" {
|
||||
opts = append(opts, fmt.Sprintf("--reuse-message=%s", o.ReuseMessage))
|
||||
}
|
||||
|
||||
if o.Short {
|
||||
opts = append(opts, "--short")
|
||||
}
|
||||
|
||||
if o.Status {
|
||||
opts = append(opts, "--status")
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
opts = append(opts, "--verbose")
|
||||
}
|
||||
|
||||
// If there's a pathspec, append the paths at the very end
|
||||
if len(o.PathSpec) > 0 {
|
||||
opts = append(append(opts, "--"), o.PathSpec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
123
tools/pipeline/internal/pkg/git/fetch.go
Normal file
123
tools/pipeline/internal/pkg/git/fetch.go
Normal file
@ -0,0 +1,123 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FetchOpts are the git fetch flags and arguments
|
||||
// See: https://git-scm.com/docs/git-fetch
|
||||
type FetchOpts struct {
|
||||
// Options
|
||||
All bool // --all
|
||||
Atomic bool // --atomic
|
||||
Depth uint // --depth
|
||||
Deepen uint // --deepen
|
||||
Force bool // --force
|
||||
NoTags bool // --no-tags
|
||||
Porcelain bool // --porcelain
|
||||
Progress bool // --progress
|
||||
Prune bool // --prune
|
||||
PruneTags bool // --prune-tags
|
||||
Quiet bool // --quiet
|
||||
SetUpstream bool // --set-upstream
|
||||
Unshallow bool // --unshallow
|
||||
Verbose bool // --verbose
|
||||
|
||||
// Targets
|
||||
Repository string // <repository>
|
||||
Refspec []string // <refspec>
|
||||
}
|
||||
|
||||
// Fetch runs the git fetch command
|
||||
func (c *Client) Fetch(ctx context.Context, opts *FetchOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "fetch", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *FetchOpts) String() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *FetchOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if o.All {
|
||||
opts = append(opts, "--all")
|
||||
}
|
||||
|
||||
if o.Atomic {
|
||||
opts = append(opts, "--atomic")
|
||||
}
|
||||
|
||||
if o.Depth > 0 {
|
||||
opts = append(opts, "--depth", strconv.FormatUint(uint64(o.Depth), 10))
|
||||
}
|
||||
|
||||
if o.Deepen > 0 {
|
||||
opts = append(opts, "--deepen", strconv.FormatUint(uint64(o.Deepen), 10))
|
||||
}
|
||||
|
||||
if o.Force {
|
||||
opts = append(opts, "--force")
|
||||
}
|
||||
|
||||
if o.NoTags {
|
||||
opts = append(opts, "--no-tags")
|
||||
}
|
||||
|
||||
if o.Porcelain {
|
||||
opts = append(opts, "--porcelain")
|
||||
}
|
||||
|
||||
if o.Progress {
|
||||
opts = append(opts, "--progress")
|
||||
}
|
||||
|
||||
if o.Prune {
|
||||
opts = append(opts, "--prune")
|
||||
}
|
||||
|
||||
if o.PruneTags {
|
||||
opts = append(opts, "--prune-tags")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.SetUpstream {
|
||||
opts = append(opts, "--set-upstream")
|
||||
}
|
||||
|
||||
if o.Unshallow {
|
||||
opts = append(opts, "--unshallow")
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
opts = append(opts, "--verbose")
|
||||
}
|
||||
|
||||
if o.Repository != "" {
|
||||
opts = append(opts, o.Repository)
|
||||
}
|
||||
|
||||
if len(o.Refspec) > 0 {
|
||||
opts = append(opts, o.Refspec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
220
tools/pipeline/internal/pkg/git/merge.go
Normal file
220
tools/pipeline/internal/pkg/git/merge.go
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// MergeStrategy are the merge strategy to use
|
||||
MergeStrategy = string
|
||||
// MergeStrategy are merge strategy options
|
||||
MergeStrategyOption = string
|
||||
)
|
||||
|
||||
const (
|
||||
MergeStrategyORT MergeStrategy = "ort"
|
||||
MergeStrategyRecursive MergeStrategy = "recursive"
|
||||
MergeStrategyResolve MergeStrategy = "resolve"
|
||||
MergeStrategyOctopus MergeStrategy = "octopus"
|
||||
MergeStrategyOurs MergeStrategy = "ours"
|
||||
MergeStrategySubtree MergeStrategy = "subtree"
|
||||
|
||||
// Ort
|
||||
MergeStrategyOptionOurs MergeStrategy = "ours"
|
||||
MergeStrategyOptionTheirs MergeStrategy = "theirs"
|
||||
MergeStrategyOptionIgnoreSpaceChange MergeStrategy = "ignore-space-change"
|
||||
MergeStrategyOptionIgnoreAllSpace MergeStrategy = "ignore-all-space"
|
||||
MergeStrategyOptionIgnoreSpaceAtEOL MergeStrategy = "ignore-space-at-eol"
|
||||
MergeStrategyOptionIgnoreCRAtEOL MergeStrategy = "ignore-cr-at-eol"
|
||||
MergeStrategyOptionRenormalize MergeStrategy = "renormalize"
|
||||
MergeStrategyOptionNoRenormalize MergeStrategy = "no-renormalize"
|
||||
MergeStrategyOptionFindRenames MergeStrategy = "find-renames"
|
||||
|
||||
// Recursive
|
||||
MergeStrategyOptionDiffAlgorithmPatience MergeStrategy = "diff-algorithm=patience"
|
||||
MergeStrategyOptionDiffAlgorithmMinimal MergeStrategy = "diff-algorithm=minimal"
|
||||
MergeStrategyOptionDiffAlgorithmHistogram MergeStrategy = "diff-algorithm=histogram"
|
||||
MergeStrategyOptionDiffAlgorithmMyers MergeStrategy = "diff-algorithm=myers"
|
||||
)
|
||||
|
||||
// MergeOpts are the git merge flags and arguments
|
||||
// See: https://git-scm.com/docs/git-merge
|
||||
type MergeOpts struct {
|
||||
// Options
|
||||
Autostash bool // --autostash
|
||||
DoCommit bool // --commit
|
||||
FF bool // --ff
|
||||
FFOnly bool // --ff-onnly
|
||||
IntoName string // --into-name
|
||||
Log uint // --log=<n>
|
||||
Message string // -m
|
||||
NoAutostash bool // --no-autostash
|
||||
NoDoCommit bool // --no-commit
|
||||
NoFF bool // --no-ff
|
||||
NoLog bool // --no-log
|
||||
NoOverwrite bool // --no-overwrite
|
||||
NoProgress bool // --no-progress
|
||||
NoRebase bool // --no-rebase
|
||||
NoReReReAutoupdate bool // --no-rerere-autoupdate
|
||||
NoSquash bool // --no-squash
|
||||
NoStat bool // --no-stat
|
||||
NoVerify bool // --no-verify
|
||||
Progress bool // --progress
|
||||
Quiet bool // --quiet
|
||||
ReReReAutoupdate bool // --rerere-autoupdate
|
||||
Squash bool // --squash
|
||||
Stat bool // --stat
|
||||
Strategy MergeStrategy // --stategy=<strategy>
|
||||
StragegyOptions []MergeStrategyOption // --strategy-option=<option>
|
||||
Verbose bool // --verbose
|
||||
|
||||
// Targets
|
||||
Commit string // <commit>
|
||||
|
||||
// Sequences
|
||||
Continue bool // --continue
|
||||
Abort bool // --abort
|
||||
Quit bool // --quit
|
||||
}
|
||||
|
||||
// Merge runs the git merge command
|
||||
func (c *Client) Merge(ctx context.Context, opts *MergeOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "merge", opts)
|
||||
}
|
||||
|
||||
// MergeAbort aborts an in-progress merge
|
||||
func (c *Client) MergeAbort(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Merge(ctx, &MergeOpts{Abort: true})
|
||||
}
|
||||
|
||||
// MergeContinue continues an in-progress merge
|
||||
func (c *Client) MergeContinue(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Merge(ctx, &MergeOpts{Continue: true})
|
||||
}
|
||||
|
||||
// MergeQuit quits an in-progress merge
|
||||
func (c *Client) MergeQuit(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Merge(ctx, &MergeOpts{Quit: true})
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (m *MergeOpts) String() string {
|
||||
return strings.Join(m.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (m *MergeOpts) Strings() []string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case m.Abort:
|
||||
return []string{"--abort"}
|
||||
case m.Continue:
|
||||
return []string{"--continue"}
|
||||
case m.Quit:
|
||||
return []string{"--quit"}
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if m.Autostash {
|
||||
opts = append(opts, "--autostash")
|
||||
}
|
||||
|
||||
if m.DoCommit {
|
||||
opts = append(opts, "--commit")
|
||||
}
|
||||
|
||||
if m.FF {
|
||||
opts = append(opts, "--ff")
|
||||
}
|
||||
|
||||
if m.FFOnly {
|
||||
opts = append(opts, "--ff-only")
|
||||
}
|
||||
|
||||
if m.IntoName != "" {
|
||||
opts = append(opts, "--into-name", m.IntoName)
|
||||
}
|
||||
|
||||
if m.Log > 0 {
|
||||
opts = append(opts, fmt.Sprintf("--log=%d", m.Log))
|
||||
}
|
||||
|
||||
if m.ReReReAutoupdate {
|
||||
opts = append(opts, "--rerere-autoupdate")
|
||||
}
|
||||
|
||||
if m.Squash {
|
||||
opts = append(opts, "--squash")
|
||||
}
|
||||
|
||||
if m.Stat {
|
||||
opts = append(opts, "--stat")
|
||||
}
|
||||
|
||||
if m.Strategy != "" {
|
||||
opts = append(opts, fmt.Sprintf("--strategy=%s", string(m.Strategy)))
|
||||
}
|
||||
|
||||
for _, opt := range m.StragegyOptions {
|
||||
opts = append(opts, fmt.Sprintf("--strategy-option=%s", string(opt)))
|
||||
}
|
||||
|
||||
if m.NoAutostash {
|
||||
opts = append(opts, "--no-autostash")
|
||||
}
|
||||
|
||||
if m.NoDoCommit {
|
||||
opts = append(opts, "--no-commit")
|
||||
}
|
||||
|
||||
if m.NoFF {
|
||||
opts = append(opts, "--no-ff")
|
||||
}
|
||||
|
||||
if m.NoLog {
|
||||
opts = append(opts, "--no-log")
|
||||
}
|
||||
|
||||
if m.NoProgress {
|
||||
opts = append(opts, "--no-progress")
|
||||
}
|
||||
|
||||
if m.NoRebase {
|
||||
opts = append(opts, "--no-rebase")
|
||||
}
|
||||
|
||||
if m.NoReReReAutoupdate {
|
||||
opts = append(opts, "--no-rerere-autoupdate")
|
||||
}
|
||||
|
||||
if m.NoSquash {
|
||||
opts = append(opts, "--no-squash")
|
||||
}
|
||||
|
||||
if m.NoStat {
|
||||
opts = append(opts, "--no-stat")
|
||||
}
|
||||
|
||||
if m.NoStat {
|
||||
opts = append(opts, "--no-stat")
|
||||
}
|
||||
|
||||
if m.NoVerify {
|
||||
opts = append(opts, "--no-verify")
|
||||
}
|
||||
|
||||
if m.Commit != "" {
|
||||
opts = append(opts, m.Commit)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
632
tools/pipeline/internal/pkg/git/opts_test.go
Normal file
632
tools/pipeline/internal/pkg/git/opts_test.go
Normal file
@ -0,0 +1,632 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test our opts structs ability to render the correct flags in the correct order
|
||||
// NOTE: Many of thests use incompatible options but that's not what we care about,
|
||||
// we're simply asserting that the rendered string matches what ought to be there
|
||||
// give the config.
|
||||
// We have chosen not to try and very flag combinations. Instead we render it
|
||||
// and execute it and rely on git to handle validation of options.
|
||||
func TestOptsStringers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, expect := range map[string]struct {
|
||||
opts OptStringer
|
||||
expected string
|
||||
}{
|
||||
"apply": {
|
||||
&ApplyOpts{
|
||||
AllowEmpty: true,
|
||||
Cached: true,
|
||||
Check: true,
|
||||
Index: true,
|
||||
Ours: true,
|
||||
Recount: true,
|
||||
Stat: true,
|
||||
Summary: true,
|
||||
Theirs: true,
|
||||
ThreeWayMerge: true,
|
||||
Union: true,
|
||||
Whitespace: ApplyWhitespaceActionFix,
|
||||
Patch: []string{"/path/to/my.diff"},
|
||||
},
|
||||
"--allow-empty --cached --check --index --ours --recount --stat --summary --theirs --3way --union --whitespace=fix /path/to/my.diff",
|
||||
},
|
||||
"branch copy": {
|
||||
&BranchOpts{
|
||||
Copy: true,
|
||||
Force: true,
|
||||
OldBranch: "my-old-branch",
|
||||
NewBranch: "my-new-branch",
|
||||
},
|
||||
"--copy --force my-old-branch my-new-branch",
|
||||
},
|
||||
"branch delete": {
|
||||
&BranchOpts{
|
||||
Delete: true,
|
||||
Remotes: true,
|
||||
BranchName: "my-branch",
|
||||
},
|
||||
"--delete --remotes my-branch",
|
||||
},
|
||||
"branch move": {
|
||||
&BranchOpts{
|
||||
Move: true,
|
||||
OldBranch: "my-old-branch",
|
||||
NewBranch: "my-new-branch",
|
||||
},
|
||||
"--move my-old-branch my-new-branch",
|
||||
},
|
||||
"branch upstream set": {
|
||||
&BranchOpts{
|
||||
SetUpstream: true,
|
||||
SetUpstreamTo: "my-upstream",
|
||||
BranchName: "my-branch",
|
||||
},
|
||||
"--set-upstream --set-upstream-to=my-upstream my-branch",
|
||||
},
|
||||
"branch upstream unset": {
|
||||
&BranchOpts{
|
||||
UnsetUpstream: true,
|
||||
BranchName: "my-branch",
|
||||
},
|
||||
"--unset-upstream my-branch",
|
||||
},
|
||||
"branch track": {
|
||||
&BranchOpts{
|
||||
Track: BranchTrackInherit,
|
||||
NoTrack: true,
|
||||
Force: true,
|
||||
BranchName: "my-branch",
|
||||
StartPoint: "HEAD~2",
|
||||
},
|
||||
"--force --no-track --track=inherit my-branch HEAD~2",
|
||||
},
|
||||
"branch with pattern": {
|
||||
// Everything else in branch..
|
||||
&BranchOpts{
|
||||
Abbrev: 7,
|
||||
All: true,
|
||||
Contains: "abcd1234",
|
||||
Format: "%%",
|
||||
List: true,
|
||||
Merged: "1234abcd",
|
||||
NoColor: true,
|
||||
NoColumn: true,
|
||||
PointsAt: "12ab34cd",
|
||||
Remotes: true,
|
||||
ShowCurrent: true,
|
||||
Sort: "key",
|
||||
Pattern: []string{"my/dir", "another/dir"},
|
||||
},
|
||||
"--abbrev=7 --all --contains=abcd1234 --format=%% --list --merged=1234abcd --no-color --no-column --points-at=12ab34cd --remotes --show-current --sort=key my/dir another/dir",
|
||||
},
|
||||
"checkout 1/2 opts": {
|
||||
&CheckoutOpts{
|
||||
Branch: "source",
|
||||
NewBranchForceCheckout: "new",
|
||||
Force: true,
|
||||
NoTrack: true,
|
||||
Ours: true,
|
||||
Quiet: true,
|
||||
},
|
||||
"-B new --force --no-track --ours --quiet source",
|
||||
},
|
||||
"checkout 2/2 opts": {
|
||||
&CheckoutOpts{
|
||||
Branch: "source",
|
||||
NewBranch: "new",
|
||||
Guess: true,
|
||||
Orphan: "bar",
|
||||
Progress: true,
|
||||
Theirs: true,
|
||||
Track: BranchTrackDirect,
|
||||
StartPoint: "HEAD~1",
|
||||
},
|
||||
"-b new --guess --orphan bar --progress --theirs --track=direct source HEAD~1",
|
||||
},
|
||||
"checkout path spec": {
|
||||
&CheckoutOpts{
|
||||
Branch: "main",
|
||||
StartPoint: "HEAD~1",
|
||||
PathSpec: []string{"go.mod", "go.sum"},
|
||||
},
|
||||
"main HEAD~1 -- go.mod go.sum",
|
||||
},
|
||||
"cherry-pick 1/2 opts": {
|
||||
&CherryPickOpts{
|
||||
AllowEmpty: true,
|
||||
AllowEmptyMessage: true,
|
||||
Empty: EmptyCommitKeep,
|
||||
FF: true,
|
||||
GPGSign: true,
|
||||
Mainline: "ABCDEFGH",
|
||||
Record: true,
|
||||
Signoff: true,
|
||||
Commit: "1234ABCD",
|
||||
},
|
||||
"--allow-empty --allow-empty-message --empty=keep --ff --gpg-sign --mainline=ABCDEFGH -x --signoff 1234ABCD",
|
||||
},
|
||||
"cherry-pick: 2/2 opts": {
|
||||
&CherryPickOpts{
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
ReReReAutoupdate: true,
|
||||
Strategy: MergeStrategyResolve,
|
||||
StrategyOptions: []MergeStrategyOption{
|
||||
MergeStrategyOptionDiffAlgorithmHistogram,
|
||||
MergeStrategyOptionIgnoreSpaceChange,
|
||||
},
|
||||
Commit: "1234ABCD",
|
||||
},
|
||||
"--gpg-sign=4321DCBA --rerere-autoupdate --strategy=resolve --strategy-option=diff-algorithm=histogram --strategy-option=ignore-space-change 1234ABCD",
|
||||
},
|
||||
"cherry-pick --continue": {
|
||||
&CherryPickOpts{
|
||||
Continue: true,
|
||||
// Options are ignored
|
||||
Commit: "1234ABCD",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--continue",
|
||||
},
|
||||
"cherry-pick --abort": {
|
||||
&CherryPickOpts{
|
||||
Abort: true,
|
||||
// Options are ignored
|
||||
Commit: "1234ABCD",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--abort",
|
||||
},
|
||||
"cherry-pick --quit": {
|
||||
&CherryPickOpts{
|
||||
Quit: true,
|
||||
// Options are ignored
|
||||
Commit: "1234ABCD",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--quit",
|
||||
},
|
||||
"clone 1/2 opts": {
|
||||
&CloneOpts{
|
||||
Branch: "my-branch",
|
||||
Depth: 3,
|
||||
NoCheckout: true,
|
||||
NoTags: true,
|
||||
Origin: "my-fork",
|
||||
Quiet: true,
|
||||
Directory: "some-dir",
|
||||
},
|
||||
"--branch my-branch --depth 3 --no-checkout --no-tags --origin my-fork --quiet -- some-dir",
|
||||
},
|
||||
"clone 2/2 opts": {
|
||||
&CloneOpts{
|
||||
Branch: "my-branch",
|
||||
Progress: true,
|
||||
Sparse: true,
|
||||
SingleBranch: true,
|
||||
Repository: "my-repo",
|
||||
Directory: "some-dir",
|
||||
},
|
||||
"--branch my-branch --progress --single-branch --sparse -- my-repo some-dir",
|
||||
},
|
||||
"commit 1/2 opts": {
|
||||
&CommitOpts{
|
||||
All: true,
|
||||
AllowEmpty: true,
|
||||
AllowEmptyMessage: true,
|
||||
Amend: true,
|
||||
Author: "example@hashicorp.com",
|
||||
Branch: true,
|
||||
Cleanup: CommitCleanupModeWhitespace,
|
||||
Date: "1 day ago",
|
||||
DryRun: true,
|
||||
File: "path/to/message/file",
|
||||
Fixup: &CommitFixup{
|
||||
FixupLog: CommitFixupLogReword,
|
||||
Commit: "1234ABCD",
|
||||
},
|
||||
GPGSign: true,
|
||||
Long: true,
|
||||
NoEdit: true,
|
||||
PathSpec: []string{
|
||||
"file/a",
|
||||
"another/b",
|
||||
},
|
||||
},
|
||||
"--all --allow-empty --allow-empty-message --amend --author=example@hashicorp.com --branch --cleanup=whitespace --date=1 day ago --dry-run --file=path/to/message/file --fixup=reword:1234ABCD --gpg-sign --long --no-edit -- file/a another/b",
|
||||
},
|
||||
"commit 2/2 opts": {
|
||||
&CommitOpts{
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
Patch: true,
|
||||
Porcelain: true,
|
||||
Message: "my commit message",
|
||||
NoPostRewrite: true,
|
||||
NoVerify: true,
|
||||
Null: true,
|
||||
Only: true,
|
||||
ResetAuthor: true,
|
||||
ReuseMessage: "1234ABCD",
|
||||
Short: true,
|
||||
Signoff: true,
|
||||
Status: true,
|
||||
Verbose: true,
|
||||
PathSpec: []string{
|
||||
"file/a",
|
||||
"another/b",
|
||||
},
|
||||
},
|
||||
"--gpg-sign=4321DCBA --patch --porcelain --message=my commit message --no-post-rewrite --no-verify --null --only --reset-author --reuse-message=1234ABCD --short --status --verbose -- file/a another/b",
|
||||
},
|
||||
"fetch": {
|
||||
&FetchOpts{
|
||||
All: true,
|
||||
Atomic: true,
|
||||
Depth: 5,
|
||||
Deepen: 6,
|
||||
Force: true,
|
||||
NoTags: true,
|
||||
Porcelain: true,
|
||||
Progress: true,
|
||||
Prune: true,
|
||||
Quiet: true,
|
||||
SetUpstream: true,
|
||||
Unshallow: true,
|
||||
Verbose: true,
|
||||
Repository: "my-repo",
|
||||
Refspec: []string{"my-branch"},
|
||||
},
|
||||
"--all --atomic --depth 5 --deepen 6 --force --no-tags --porcelain --progress --prune --quiet --set-upstream --unshallow --verbose my-repo my-branch",
|
||||
},
|
||||
"merge 1/2 opts": {
|
||||
&MergeOpts{
|
||||
Autostash: true,
|
||||
DoCommit: true,
|
||||
Commit: "1234ABCD",
|
||||
FF: true,
|
||||
FFOnly: true,
|
||||
IntoName: "my-other-branch",
|
||||
Log: 2,
|
||||
Message: "merging my branch",
|
||||
Progress: true,
|
||||
ReReReAutoupdate: true,
|
||||
Squash: true,
|
||||
Stat: true,
|
||||
Strategy: MergeStrategyORT,
|
||||
StragegyOptions: []MergeStrategyOption{
|
||||
MergeStrategyOptionDiffAlgorithmMyers,
|
||||
MergeStrategyOptionFindRenames,
|
||||
},
|
||||
Verbose: true,
|
||||
},
|
||||
"--autostash --commit --ff --ff-only --into-name my-other-branch --log=2 --rerere-autoupdate --squash --stat --strategy=ort --strategy-option=diff-algorithm=myers --strategy-option=find-renames 1234ABCD",
|
||||
},
|
||||
"merge 2/2 opts": {
|
||||
&MergeOpts{
|
||||
NoAutostash: true,
|
||||
NoDoCommit: true,
|
||||
NoFF: true,
|
||||
NoLog: true,
|
||||
NoProgress: true,
|
||||
NoRebase: true,
|
||||
NoReReReAutoupdate: true,
|
||||
NoSquash: true,
|
||||
NoStat: true,
|
||||
NoVerify: true,
|
||||
},
|
||||
"--no-autostash --no-commit --no-ff --no-log --no-progress --no-rebase --no-rerere-autoupdate --no-squash --no-stat --no-stat --no-verify",
|
||||
},
|
||||
"merge --continue": {
|
||||
&MergeOpts{
|
||||
Continue: true,
|
||||
// Options are ignored
|
||||
Commit: "1234ABCD",
|
||||
Message: "merging my branch",
|
||||
},
|
||||
"--continue",
|
||||
},
|
||||
"merge --abort": {
|
||||
&MergeOpts{
|
||||
Abort: true,
|
||||
// Options are ignored
|
||||
Commit: "1234ABCD",
|
||||
Message: "merging my branch",
|
||||
},
|
||||
"--abort",
|
||||
},
|
||||
"merge --quit": {
|
||||
&MergeOpts{
|
||||
Quit: true,
|
||||
// Options are ignored
|
||||
Commit: "1234ABCD",
|
||||
Message: "merging my branch",
|
||||
},
|
||||
"--quit",
|
||||
},
|
||||
"pull 1/3 opts": {
|
||||
&PullOpts{
|
||||
Atomic: true,
|
||||
Autostash: true,
|
||||
Depth: 4,
|
||||
DoCommit: true,
|
||||
FF: true,
|
||||
GPGSign: true,
|
||||
NoLog: true,
|
||||
NoStat: true,
|
||||
Quiet: true,
|
||||
Prune: true,
|
||||
SetUpstream: true,
|
||||
Squash: true,
|
||||
Rebase: RebaseStrategyTrue,
|
||||
Refspec: []string{"my-branch"},
|
||||
Repository: "my-repo",
|
||||
UpdateShallow: true,
|
||||
}, "--atomic --autostash --commit --depth 4 --ff --gpg-sign --squash --no-log --no-stat --no-stat --prune --quiet --rebase=true --set-upstream my-repo my-branch",
|
||||
},
|
||||
"pull 2/3 opts": {
|
||||
&PullOpts{
|
||||
AllowUnrelatedHistories: true,
|
||||
Append: true,
|
||||
Deepen: 3,
|
||||
FFOnly: true,
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
Log: 5,
|
||||
NoRebase: true,
|
||||
NoRecurseSubmodules: true,
|
||||
Porcelain: true,
|
||||
Progress: true,
|
||||
PruneTags: true,
|
||||
Refspec: []string{"my-branch"},
|
||||
Repository: "my-repo",
|
||||
Stat: true,
|
||||
Strategy: MergeStrategyOctopus,
|
||||
StragegyOptions: []MergeStrategyOption{
|
||||
MergeStrategyOptionDiffAlgorithmMinimal,
|
||||
MergeStrategyOptionIgnoreCRAtEOL,
|
||||
},
|
||||
Verbose: true,
|
||||
Verify: true,
|
||||
}, "--deepen 3 --ff-only --gpg-sign=4321DCBA --log=5 --stat -X diff-algorithm=minimal -X ignore-cr-at-eol --no-rebase --no-recurse-submodules --porcelain --progress --prune-tags --verbose my-repo my-branch",
|
||||
},
|
||||
"pull 3/3 opts": {
|
||||
&PullOpts{
|
||||
All: true,
|
||||
Cleanup: CommitCleanupModeDefault,
|
||||
Force: true,
|
||||
NoAutostash: true,
|
||||
NoDoCommit: true,
|
||||
NoFF: true,
|
||||
NoSquash: true,
|
||||
NoTags: true,
|
||||
NoVerify: true,
|
||||
RecurseSubmodules: RecurseSubmodulesOnDemand,
|
||||
Refspec: []string{"my-branch"},
|
||||
Repository: "my-repo",
|
||||
Stat: true,
|
||||
Unshallow: true,
|
||||
}, "--all --force --stat --no-autostash --no-commit --no-ff --no-squash --no-tags --no-verify --unshallow my-repo my-branch",
|
||||
},
|
||||
"push 1/2 opts": {
|
||||
&PushOpts{
|
||||
All: true,
|
||||
Branches: true,
|
||||
DryRun: true,
|
||||
FollowTags: true,
|
||||
ForceIfIncludes: true,
|
||||
Mirror: true,
|
||||
NoForceWithLease: true,
|
||||
NoRecurseSubmodules: true,
|
||||
NoThin: true,
|
||||
Porcelain: true,
|
||||
Prune: true,
|
||||
Quiet: true,
|
||||
Refspec: []string{"my-branch"},
|
||||
Repository: "my-repo",
|
||||
SetUpstream: true,
|
||||
Tags: true,
|
||||
Verify: true,
|
||||
}, "--all --branches --dry-run --follow-tags --force-if-includes --mirror --no-force-with-lease --no-recurse-submodules --no-thin --porcelain --prune --quiet --set-upstream --tags --verify my-repo my-branch",
|
||||
},
|
||||
"push 2/2 opts": {
|
||||
&PushOpts{
|
||||
Atomic: true,
|
||||
Delete: true,
|
||||
Exec: "/path/to/git-receive-pack",
|
||||
Force: true,
|
||||
ForceWithLease: "another-branch",
|
||||
NoAtomic: true,
|
||||
NoForceIfIncludes: true,
|
||||
NoSigned: true,
|
||||
NoVerify: true,
|
||||
Progress: true,
|
||||
PushOption: "a",
|
||||
RecurseSubmodules: PushRecurseSubmodulesCheck,
|
||||
Refspec: []string{"my-branch"},
|
||||
Repository: "my-repo",
|
||||
Signed: PushSignedTrue,
|
||||
Verbose: true,
|
||||
}, "--atomic --delete --exec=/path/to/git-receive-pack --force --force-with-lease=another-branch --no-atomic --no-force-if-includes --no-signed --no-verify --progress --push-option=a --recurse-submodules=check --signed=true --verbose my-repo my-branch",
|
||||
},
|
||||
"rebase 1/3 opts": {
|
||||
&RebaseOpts{
|
||||
AllowEmptyMessage: true,
|
||||
Autosquash: true,
|
||||
Branch: "my-branch",
|
||||
Empty: EmptyCommitDrop,
|
||||
ForkPoint: true,
|
||||
IgnoreDate: true,
|
||||
Merge: true,
|
||||
NoAutostash: true,
|
||||
NoRebaseMerges: true,
|
||||
NoReReReAutoupdate: true,
|
||||
NoVerify: true,
|
||||
RescheduleFailedExec: true,
|
||||
Root: true,
|
||||
UpdateRefs: true,
|
||||
}, "--allow-empty-message --autosquash --empty=drop --fork-point --ignore-date --merge --no-autostash --no-rebase-merges --no-rerere-autoupdate --no-verify --reschedule-failed-exec --root --update-refs my-branch",
|
||||
},
|
||||
"rebase 2/3 opts": {
|
||||
&RebaseOpts{
|
||||
Apply: true,
|
||||
Branch: "my-branch",
|
||||
CommitterDateIsAuthorDate: true,
|
||||
Exec: "/path/to/git-receive-pack",
|
||||
GPGSign: true,
|
||||
IgnoreWhitespace: true,
|
||||
KeepEmpty: true,
|
||||
NoReapplyCherryPicks: true,
|
||||
NoStat: true,
|
||||
Onto: "new-base",
|
||||
Quiet: true,
|
||||
ResetAuthorDate: true,
|
||||
Stat: true,
|
||||
Whitespace: WhitespaceActionFix,
|
||||
}, "--apply --committer-date-is-author-date --exec=/path/to/git-receive-pack --gpg-sign --ignore-whitespace --keep-empty --no-reapply-cherry-picks --no-stat --onto=new-base --quiet --reset-author-date --stat --whitespace=fix my-branch",
|
||||
},
|
||||
"rebase 3/3 opts": {
|
||||
&RebaseOpts{
|
||||
Autostash: true,
|
||||
Branch: "my-branch",
|
||||
Context: 3,
|
||||
ForceRebase: true,
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
KeepBase: "another-upstream",
|
||||
NoAutosquash: true,
|
||||
NoKeepEmpty: true,
|
||||
NoRescheduleFailedExec: true,
|
||||
NoUpdateRefs: true,
|
||||
ReapplyCherryPicks: true,
|
||||
RebaseMerges: RebaseMergesCousins,
|
||||
ReReReAutoupdate: true,
|
||||
Strategy: MergeStrategySubtree,
|
||||
StragegyOptions: []MergeStrategyOption{
|
||||
MergeStrategyOptionDiffAlgorithmPatience,
|
||||
MergeStrategyOptionNoRenormalize,
|
||||
},
|
||||
Verbose: true,
|
||||
}, "--autostash -C 3 --force-rebase --gpg-sign=4321DCBA --keep-base=another-upstream --no-autosquash --no-keep-empty --no-reschedule-failed-exec --no-update-refs --reapply-cherry-picks --rebase-merges=rebase-cousins --rerere-autoupdate --strategy=subtree --strategy-option=diff-algorithm=patience --strategy-option=no-renormalize --verbose my-branch",
|
||||
},
|
||||
"rebase --continue": {
|
||||
&RebaseOpts{
|
||||
Continue: true,
|
||||
// Options are ignored
|
||||
Branch: "my-branch",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--continue",
|
||||
},
|
||||
"rebase --abort": {
|
||||
&RebaseOpts{
|
||||
Abort: true,
|
||||
// Options are ignored
|
||||
Branch: "my-branch",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--abort",
|
||||
},
|
||||
"rebase --quit": {
|
||||
&RebaseOpts{
|
||||
Quit: true,
|
||||
// Options are ignored
|
||||
Branch: "my-branch",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--quit",
|
||||
},
|
||||
"rebase --skip": {
|
||||
&RebaseOpts{
|
||||
Skip: true,
|
||||
// Options are ignored
|
||||
Branch: "my-branch",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--skip",
|
||||
},
|
||||
"rebase --show-current-patch": {
|
||||
&RebaseOpts{
|
||||
ShowCurrentPatch: true,
|
||||
// Options are ignored
|
||||
Branch: "my-branch",
|
||||
GPGSignKeyID: "4321DCBA",
|
||||
},
|
||||
"--show-current-patch",
|
||||
},
|
||||
"reset": {
|
||||
&ResetOpts{
|
||||
Mode: ResetModeHard,
|
||||
NoRefresh: true,
|
||||
Patch: true,
|
||||
Quiet: true,
|
||||
Refresh: true,
|
||||
Commit: "abcd1234",
|
||||
Treeish: "HEAD~2",
|
||||
PathSpec: []string{"vault/something_ent.go", "vault/cli/another_ent.go"},
|
||||
},
|
||||
"--hard --no-refresh --quiet --refresh --patch abcd1234 HEAD~2 -- vault/something_ent.go vault/cli/another_ent.go",
|
||||
},
|
||||
"rm": {
|
||||
&RmOpts{
|
||||
Cached: true,
|
||||
DryRun: true,
|
||||
Force: true,
|
||||
IgnoreUnmatched: true,
|
||||
Quiet: true,
|
||||
Recursive: true,
|
||||
Sparse: true,
|
||||
PathSpec: []string{"vault/something_ent.go", "vault/cli/another_ent.go"},
|
||||
},
|
||||
"--cached --dry-run --force --ignore-unmatched --quiet -r --sparse -- vault/something_ent.go vault/cli/another_ent.go",
|
||||
},
|
||||
"show": {
|
||||
&ShowOpts{
|
||||
DiffAlgorithm: DiffAlgorithmHistogram,
|
||||
DiffMerges: DiffMergeFormatDenseCombined,
|
||||
Format: "medium",
|
||||
NoColor: true,
|
||||
NoPatch: true,
|
||||
Output: "/path/to/my.diff",
|
||||
Patch: true,
|
||||
Raw: true,
|
||||
Object: "HEAD",
|
||||
PathSpec: []string{"go.mod", "go.sum"},
|
||||
},
|
||||
"--diff-algorithm=histogram --diff-merges=dense-combined --format=medium --no-color --no-patch --output=/path/to/my.diff --patch --raw HEAD -- go.mod go.sum",
|
||||
},
|
||||
"status": {
|
||||
&StatusOpts{
|
||||
AheadBehind: true,
|
||||
Branch: true,
|
||||
Column: "always",
|
||||
FindRenames: 12,
|
||||
Ignored: IgnoredModeMatching,
|
||||
IgnoreSubmodules: IgnoreSubmodulesWhenDirty,
|
||||
Long: true,
|
||||
NoAheadBehind: true,
|
||||
NoColumn: true,
|
||||
NoRenames: true,
|
||||
Porcelain: true,
|
||||
Renames: true,
|
||||
Short: true,
|
||||
ShowStash: true,
|
||||
UntrackedFiles: UntrackedFilesAll,
|
||||
Verbose: true,
|
||||
PathSpec: []string{"go.mod", "go.sum"},
|
||||
},
|
||||
"--ahead-behind --branch --column=always --find-renames=12 --ignored=matching --ignore-submodules=dirty --long --no-ahead-behind --no-column --no-renames --porcelain --renames --short --show-stash --untracked-files=all --verbose -- go.mod go.sum",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, expect.expected, expect.opts.String())
|
||||
})
|
||||
}
|
||||
}
|
244
tools/pipeline/internal/pkg/git/pull.go
Normal file
244
tools/pipeline/internal/pkg/git/pull.go
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RecurseSubmodules is a sub-module recurse mode
|
||||
type RecurseSubmodules = string
|
||||
|
||||
const (
|
||||
RecurseSubmodulesYes RecurseSubmodules = "yes"
|
||||
RecurseSubmodulesOnDemand RecurseSubmodules = "on-demand"
|
||||
RecurseSubmodulesNo RecurseSubmodules = "no"
|
||||
)
|
||||
|
||||
// PullOpts are the git pull flags and arguments
|
||||
// See: https://git-scm.com/docs/git-pull
|
||||
type PullOpts struct {
|
||||
// Options
|
||||
Quiet bool // --quiet
|
||||
Verbose bool // --verbose
|
||||
RecurseSubmodules RecurseSubmodules // --recurse-submodules=
|
||||
NoRecurseSubmodules bool // --no-recurse-submodules
|
||||
|
||||
// Merge options
|
||||
Autostash bool // --autostash
|
||||
AllowUnrelatedHistories bool // --allow-unrelated-histories
|
||||
DoCommit bool // --commit
|
||||
NoDoCommit bool // --no-commit
|
||||
Cleanup Cleanup // --cleanup=
|
||||
FF bool // --ff
|
||||
FFOnly bool // --ff-onnly
|
||||
NoFF bool // --no-ff
|
||||
GPGSign bool // --gpgsign
|
||||
GPGSignKeyID string // --gpgsign=<key-id>
|
||||
Log uint // --log=
|
||||
NoAutostash bool // --no-autostash
|
||||
NoLog bool // --no-log
|
||||
NoRebase bool // --no-rebase
|
||||
NoStat bool // --no-stat
|
||||
NoSquash bool // --no-squash
|
||||
NoVerify bool // --no-verify
|
||||
Stat bool // --stat
|
||||
Squash bool // --squash
|
||||
Strategy MergeStrategy // --stategy=
|
||||
StragegyOptions []MergeStrategyOption // --strategy-option=
|
||||
Rebase RebaseStrategy // --rebase=
|
||||
Verify bool // --verify
|
||||
|
||||
// Fetch options
|
||||
All bool // --all
|
||||
Append bool // --append
|
||||
Atomic bool // --atomic
|
||||
Depth uint // --depth
|
||||
Deepen uint // --deepen
|
||||
Force bool // --force
|
||||
NoTags bool // --no-tags
|
||||
Porcelain bool // --porcelain
|
||||
Progress bool // --progress
|
||||
Prune bool // --prune
|
||||
PruneTags bool // --prune-tags
|
||||
SetUpstream bool // --set-upstream
|
||||
Unshallow bool // --unshallow
|
||||
UpdateShallow bool // --update-shallow
|
||||
|
||||
// Targets
|
||||
Repository string // <repository>
|
||||
Refspec []string // <refspec>
|
||||
}
|
||||
|
||||
// Pull runs the git pull command
|
||||
func (c *Client) Pull(ctx context.Context, opts *PullOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "pull", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *PullOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *PullOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if o.All {
|
||||
opts = append(opts, "--all")
|
||||
}
|
||||
|
||||
if o.Atomic {
|
||||
opts = append(opts, "--atomic")
|
||||
}
|
||||
|
||||
if o.Autostash {
|
||||
opts = append(opts, "--autostash")
|
||||
}
|
||||
|
||||
if o.DoCommit {
|
||||
opts = append(opts, "--commit")
|
||||
}
|
||||
|
||||
if o.Depth > 0 {
|
||||
opts = append(opts, "--depth", strconv.FormatUint(uint64(o.Depth), 10))
|
||||
}
|
||||
|
||||
if o.Deepen > 0 {
|
||||
opts = append(opts, "--deepen", strconv.FormatUint(uint64(o.Deepen), 10))
|
||||
}
|
||||
|
||||
if o.FF {
|
||||
opts = append(opts, "--ff")
|
||||
}
|
||||
|
||||
if o.FFOnly {
|
||||
opts = append(opts, "--ff-only")
|
||||
}
|
||||
|
||||
if o.Force {
|
||||
opts = append(opts, "--force")
|
||||
}
|
||||
|
||||
if o.GPGSign {
|
||||
opts = append(opts, "--gpg-sign")
|
||||
}
|
||||
|
||||
if o.GPGSignKeyID != "" {
|
||||
opts = append(opts, fmt.Sprintf("--gpg-sign=%s", o.GPGSignKeyID))
|
||||
}
|
||||
|
||||
if o.Log > 0 {
|
||||
opts = append(opts, fmt.Sprintf("--log=%d", o.Log))
|
||||
}
|
||||
|
||||
if o.Squash {
|
||||
opts = append(opts, "--squash")
|
||||
}
|
||||
|
||||
if o.Stat {
|
||||
opts = append(opts, "--stat")
|
||||
}
|
||||
|
||||
for _, opt := range o.StragegyOptions {
|
||||
opts = append(opts, "-X", string(opt))
|
||||
}
|
||||
|
||||
if o.NoAutostash {
|
||||
opts = append(opts, "--no-autostash")
|
||||
}
|
||||
|
||||
if o.NoDoCommit {
|
||||
opts = append(opts, "--no-commit")
|
||||
}
|
||||
|
||||
if o.NoFF {
|
||||
opts = append(opts, "--no-ff")
|
||||
}
|
||||
|
||||
if o.NoLog {
|
||||
opts = append(opts, "--no-log")
|
||||
}
|
||||
|
||||
if o.NoRebase {
|
||||
opts = append(opts, "--no-rebase")
|
||||
}
|
||||
|
||||
if o.NoRecurseSubmodules {
|
||||
opts = append(opts, "--no-recurse-submodules")
|
||||
}
|
||||
|
||||
if o.NoSquash {
|
||||
opts = append(opts, "--no-squash")
|
||||
}
|
||||
|
||||
if o.NoStat {
|
||||
opts = append(opts, "--no-stat")
|
||||
}
|
||||
|
||||
if o.NoStat {
|
||||
opts = append(opts, "--no-stat")
|
||||
}
|
||||
|
||||
if o.NoTags {
|
||||
opts = append(opts, "--no-tags")
|
||||
}
|
||||
|
||||
if o.NoVerify {
|
||||
opts = append(opts, "--no-verify")
|
||||
}
|
||||
|
||||
if o.Porcelain {
|
||||
opts = append(opts, "--porcelain")
|
||||
}
|
||||
|
||||
if o.Progress {
|
||||
opts = append(opts, "--progress")
|
||||
}
|
||||
|
||||
if o.Prune {
|
||||
opts = append(opts, "--prune")
|
||||
}
|
||||
|
||||
if o.PruneTags {
|
||||
opts = append(opts, "--prune-tags")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.Rebase != "" {
|
||||
opts = append(opts, fmt.Sprintf("--rebase=%s", string(o.Rebase)))
|
||||
}
|
||||
|
||||
if o.SetUpstream {
|
||||
opts = append(opts, "--set-upstream")
|
||||
}
|
||||
|
||||
if o.Unshallow {
|
||||
opts = append(opts, "--unshallow")
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
opts = append(opts, "--verbose")
|
||||
}
|
||||
|
||||
if o.Repository != "" {
|
||||
opts = append(opts, o.Repository)
|
||||
}
|
||||
|
||||
if len(o.Refspec) > 0 {
|
||||
opts = append(opts, o.Refspec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
217
tools/pipeline/internal/pkg/git/push.go
Normal file
217
tools/pipeline/internal/pkg/git/push.go
Normal file
@ -0,0 +1,217 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// PushRecurseSubmodules is how to handle recursive sub-modules
|
||||
PushRecurseSubmodules = string
|
||||
// PushSigned sets gpg sign mode
|
||||
PushSigned = string
|
||||
)
|
||||
|
||||
const (
|
||||
PushRecurseSubmodulesCheck PushRecurseSubmodules = "check"
|
||||
PushRecurseSubmodulesOnDemand PushRecurseSubmodules = "on-demand"
|
||||
PushRecurseSubmodulesOnly PushRecurseSubmodules = "only"
|
||||
PushRecurseSubmodulesNo PushRecurseSubmodules = "no"
|
||||
|
||||
PushSignedTrue PushSigned = "true"
|
||||
PushSignedFalse PushSigned = "false"
|
||||
PushSignedIfAsked PushSigned = "if-asked"
|
||||
)
|
||||
|
||||
// PushOpts are the git push flags and arguments
|
||||
// See: https://git-scm.com/docs/git-push
|
||||
type PushOpts struct {
|
||||
// Options
|
||||
All bool // --all
|
||||
Atomic bool // --atomic
|
||||
Branches bool // --branches
|
||||
Delete bool // --delete
|
||||
DryRun bool // --dry-run
|
||||
Exec string // --exec=<git-receive-pack>
|
||||
FollowTags bool // --follow-tags
|
||||
Force bool // --force
|
||||
ForceIfIncludes bool // --force-if-includes
|
||||
ForceWithLease string // --force-with-lease=<refname>
|
||||
Mirror bool // --mirror
|
||||
NoAtomic bool // --no-atomic
|
||||
NoForceIfIncludes bool // --no-force-if-includes
|
||||
NoForceWithLease bool // --no-force-with-lease
|
||||
NoRecurseSubmodules bool // --no-recurse-submodules
|
||||
NoSigned bool // --no-signed
|
||||
NoThin bool // --no-thin
|
||||
NoVerify bool // --no-verify
|
||||
Porcelain bool // --porcelain
|
||||
Progress bool // --progress
|
||||
Prune bool // --prune
|
||||
PushOption string // --push-option
|
||||
Quiet bool // --quiet
|
||||
RecurseSubmodules PushRecurseSubmodules // --recurse-submodules=<mode>
|
||||
SetUpstream bool // --set-upstream
|
||||
Signed PushSigned // --signed=<mode>
|
||||
Tags bool // --tags
|
||||
Thin bool // --thin
|
||||
Verbose bool // --verbose
|
||||
Verify bool // --verify
|
||||
|
||||
// Targets
|
||||
Repository string // <repository>
|
||||
Refspec []string // <refspec>
|
||||
}
|
||||
|
||||
// Push runs the git push command
|
||||
func (c *Client) Push(ctx context.Context, opts *PushOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "push", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *PushOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *PushOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if o.All {
|
||||
opts = append(opts, "--all")
|
||||
}
|
||||
|
||||
if o.Atomic {
|
||||
opts = append(opts, "--atomic")
|
||||
}
|
||||
|
||||
if o.Branches {
|
||||
opts = append(opts, "--branches")
|
||||
}
|
||||
|
||||
if o.Delete {
|
||||
opts = append(opts, "--delete")
|
||||
}
|
||||
|
||||
if o.DryRun {
|
||||
opts = append(opts, "--dry-run")
|
||||
}
|
||||
|
||||
if o.Exec != "" {
|
||||
opts = append(opts, fmt.Sprintf("--exec=%s", o.Exec))
|
||||
}
|
||||
|
||||
if o.FollowTags {
|
||||
opts = append(opts, "--follow-tags")
|
||||
}
|
||||
|
||||
if o.Force {
|
||||
opts = append(opts, "--force")
|
||||
}
|
||||
|
||||
if o.ForceIfIncludes {
|
||||
opts = append(opts, "--force-if-includes")
|
||||
}
|
||||
|
||||
if o.ForceWithLease != "" {
|
||||
opts = append(opts, fmt.Sprintf("--force-with-lease=%s", o.ForceWithLease))
|
||||
}
|
||||
|
||||
if o.Mirror {
|
||||
opts = append(opts, "--mirror")
|
||||
}
|
||||
|
||||
if o.NoAtomic {
|
||||
opts = append(opts, "--no-atomic")
|
||||
}
|
||||
|
||||
if o.NoForceIfIncludes {
|
||||
opts = append(opts, "--no-force-if-includes")
|
||||
}
|
||||
|
||||
if o.NoForceWithLease {
|
||||
opts = append(opts, "--no-force-with-lease")
|
||||
}
|
||||
|
||||
if o.NoRecurseSubmodules {
|
||||
opts = append(opts, "--no-recurse-submodules")
|
||||
}
|
||||
|
||||
if o.NoSigned {
|
||||
opts = append(opts, "--no-signed")
|
||||
}
|
||||
|
||||
if o.NoThin {
|
||||
opts = append(opts, "--no-thin")
|
||||
}
|
||||
|
||||
if o.NoVerify {
|
||||
opts = append(opts, "--no-verify")
|
||||
}
|
||||
|
||||
if o.Porcelain {
|
||||
opts = append(opts, "--porcelain")
|
||||
}
|
||||
|
||||
if o.Progress {
|
||||
opts = append(opts, "--progress")
|
||||
}
|
||||
|
||||
if o.Prune {
|
||||
opts = append(opts, "--prune")
|
||||
}
|
||||
|
||||
if o.PushOption != "" {
|
||||
opts = append(opts, fmt.Sprintf("--push-option=%s", o.PushOption))
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.RecurseSubmodules != "" {
|
||||
opts = append(opts, fmt.Sprintf("--recurse-submodules=%s", string(o.RecurseSubmodules)))
|
||||
}
|
||||
|
||||
if o.SetUpstream {
|
||||
opts = append(opts, "--set-upstream")
|
||||
}
|
||||
|
||||
if o.Signed != "" {
|
||||
opts = append(opts, fmt.Sprintf("--signed=%s", string(o.Signed)))
|
||||
}
|
||||
|
||||
if o.Tags {
|
||||
opts = append(opts, "--tags")
|
||||
}
|
||||
|
||||
if o.Thin {
|
||||
opts = append(opts, "--thin")
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
opts = append(opts, "--verbose")
|
||||
}
|
||||
|
||||
if o.Verify {
|
||||
opts = append(opts, "--verify")
|
||||
}
|
||||
|
||||
if o.Repository != "" {
|
||||
opts = append(opts, o.Repository)
|
||||
}
|
||||
|
||||
if len(o.Refspec) > 0 {
|
||||
opts = append(opts, o.Refspec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
324
tools/pipeline/internal/pkg/git/rebase.go
Normal file
324
tools/pipeline/internal/pkg/git/rebase.go
Normal file
@ -0,0 +1,324 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// RebaseMerges is the strategy for handling merge commits in rebases
|
||||
RebaseMerges = string
|
||||
// RebaseMerges is the strategy for rebasing
|
||||
RebaseStrategy = string
|
||||
// RebaseMerges is the strategy for handling whitespace during rebasing
|
||||
WhitespaceAction = string
|
||||
)
|
||||
|
||||
const (
|
||||
RebaseMergesCousins RebaseMerges = "rebase-cousins"
|
||||
RebaseMergesNoCousins RebaseMerges = "no-rebase-cousins"
|
||||
|
||||
RebaseStrategyTrue RebaseStrategy = "true"
|
||||
RebaseStrategyFalse RebaseStrategy = "false"
|
||||
RebaseStrategyMerges RebaseStrategy = "merges"
|
||||
RebaseStrategyInteractive RebaseStrategy = "interactive"
|
||||
|
||||
WhitespaceActionNoWarn WhitespaceAction = "nowarn"
|
||||
WhitespaceActionWarn WhitespaceAction = "warn"
|
||||
WhitespaceActionFix WhitespaceAction = "fix"
|
||||
WhitespaceActionError WhitespaceAction = "error"
|
||||
WhitespaceActionErrorAll WhitespaceAction = "error-all"
|
||||
)
|
||||
|
||||
// RebaseOpts are the git rebase flags and arguments
|
||||
// See: https://git-scm.com/docs/git-rebase
|
||||
type RebaseOpts struct {
|
||||
// Options
|
||||
AllowEmptyMessage bool // --allow-empty-message
|
||||
Apply bool // --apply
|
||||
Autosquash bool // --autosquash
|
||||
Autostash bool // --autostash
|
||||
CommitterDateIsAuthorDate bool // --committer-date-is-author-date
|
||||
Context uint // -C
|
||||
Empty EmptyCommit // --empty=
|
||||
Exec string // --exec=
|
||||
ForceRebase bool // --force-rebase
|
||||
ForkPoint bool // --fork-point
|
||||
GPGSign bool // --gpgsign
|
||||
GPGSignKeyID string // --gpgsign=<key-id>
|
||||
IgnoreDate bool // --ignore-date
|
||||
IgnoreWhitespace bool // --ignore-whitespace
|
||||
KeepBase string // --keep-base <upstream|branch>
|
||||
KeepEmpty bool // --keep-empty
|
||||
Merge bool // --merge
|
||||
NoAutosquash bool // --no-autosquash
|
||||
NoAutostash bool // --no-autostash
|
||||
NoKeepEmpty bool // --no-keep-empty
|
||||
NoReapplyCherryPicks bool // --no-reapply-cherry-picks
|
||||
NoRebaseMerges bool // --no-rebase-merges
|
||||
NoRescheduleFailedExec bool // --no-reschedule-failed-exec
|
||||
NoReReReAutoupdate bool // --no-rerere-autoupdate
|
||||
NoStat bool // --no-stat
|
||||
NoUpdateRefs bool // --no-update-refs
|
||||
NoVerify bool // --no-verify
|
||||
Onto string // --onto
|
||||
Quiet bool // --quiet
|
||||
ReapplyCherryPicks bool // --reapply-cherry-picks
|
||||
RebaseMerges RebaseMerges // --rebase-merges=<strategy>
|
||||
RescheduleFailedExec bool // --reschedule-failed-exec
|
||||
ResetAuthorDate bool // --reset-author-date
|
||||
ReReReAutoupdate bool // --rerere-autoupdate
|
||||
Root bool // --root
|
||||
Stat bool // --stat
|
||||
Strategy MergeStrategy // --strategy
|
||||
StragegyOptions []MergeStrategyOption // --strategy-option=<option>
|
||||
UpdateRefs bool // --update-refs
|
||||
Verbose bool // --verbose
|
||||
Verify bool // --verify
|
||||
Whitespace WhitespaceAction //--whitespace=<handler>
|
||||
|
||||
// Args
|
||||
Branch string // <branch>
|
||||
|
||||
// Mode Options
|
||||
Continue bool // --continue
|
||||
Skip bool // --skip
|
||||
Abort bool // --abort
|
||||
Quit bool // --quit
|
||||
ShowCurrentPatch bool // --show-current-patch
|
||||
}
|
||||
|
||||
// Rebase runs the git rebase command
|
||||
func (c *Client) Rebase(ctx context.Context, opts *RebaseOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "rebase", opts)
|
||||
}
|
||||
|
||||
// RebaseAbort aborts an in-progress rebase
|
||||
func (c *Client) RebaseAbort(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Rebase(ctx, &RebaseOpts{Abort: true})
|
||||
}
|
||||
|
||||
// RebaseContinue continues an in-progress rebase
|
||||
func (c *Client) RebaseContinue(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Rebase(ctx, &RebaseOpts{Continue: true})
|
||||
}
|
||||
|
||||
// RebaseQuit quits an in-progress rebase
|
||||
func (c *Client) RebaseQuit(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Rebase(ctx, &RebaseOpts{Quit: true})
|
||||
}
|
||||
|
||||
// RebaseSkip skips an in-progress rebase
|
||||
func (c *Client) RebaseSkip(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Rebase(ctx, &RebaseOpts{Skip: true})
|
||||
}
|
||||
|
||||
// RebaseShowCurrentPatch shows the current patch an in-progress rebase
|
||||
func (c *Client) RebaseShowCurrentPatch(ctx context.Context) (*ExecResponse, error) {
|
||||
return c.Rebase(ctx, &RebaseOpts{ShowCurrentPatch: true})
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *RebaseOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the set options as a string slice
|
||||
func (o *RebaseOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.Abort:
|
||||
return []string{"--abort"}
|
||||
case o.Continue:
|
||||
return []string{"--continue"}
|
||||
case o.Quit:
|
||||
return []string{"--quit"}
|
||||
case o.Skip:
|
||||
return []string{"--skip"}
|
||||
case o.ShowCurrentPatch:
|
||||
return []string{"--show-current-patch"}
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if o.AllowEmptyMessage {
|
||||
opts = append(opts, "--allow-empty-message")
|
||||
}
|
||||
if o.Apply {
|
||||
opts = append(opts, "--apply")
|
||||
}
|
||||
|
||||
if o.Autosquash {
|
||||
opts = append(opts, "--autosquash")
|
||||
}
|
||||
|
||||
if o.Autostash {
|
||||
opts = append(opts, "--autostash")
|
||||
}
|
||||
|
||||
if o.CommitterDateIsAuthorDate {
|
||||
opts = append(opts, "--committer-date-is-author-date")
|
||||
}
|
||||
|
||||
if o.Context > 0 {
|
||||
opts = append(opts, "-C", strconv.FormatUint(uint64(o.Context), 10))
|
||||
}
|
||||
|
||||
if o.Empty != "" {
|
||||
opts = append(opts, fmt.Sprintf("--empty=%s", string(o.Empty)))
|
||||
}
|
||||
|
||||
if o.Exec != "" {
|
||||
opts = append(opts, fmt.Sprintf("--exec=%s", o.Exec))
|
||||
}
|
||||
|
||||
if o.ForceRebase {
|
||||
opts = append(opts, "--force-rebase")
|
||||
}
|
||||
|
||||
if o.ForkPoint {
|
||||
opts = append(opts, "--fork-point")
|
||||
}
|
||||
|
||||
if o.GPGSign {
|
||||
opts = append(opts, "--gpg-sign")
|
||||
}
|
||||
|
||||
if o.GPGSignKeyID != "" {
|
||||
opts = append(opts, fmt.Sprintf("--gpg-sign=%s", o.GPGSignKeyID))
|
||||
}
|
||||
|
||||
if o.IgnoreDate {
|
||||
opts = append(opts, "--ignore-date")
|
||||
}
|
||||
|
||||
if o.IgnoreWhitespace {
|
||||
opts = append(opts, "--ignore-whitespace")
|
||||
}
|
||||
|
||||
if o.KeepBase != "" {
|
||||
opts = append(opts, fmt.Sprintf("--keep-base=%s", o.KeepBase))
|
||||
}
|
||||
|
||||
if o.KeepEmpty {
|
||||
opts = append(opts, "--keep-empty")
|
||||
}
|
||||
|
||||
if o.Merge {
|
||||
opts = append(opts, "--merge")
|
||||
}
|
||||
|
||||
if o.NoAutosquash {
|
||||
opts = append(opts, "--no-autosquash")
|
||||
}
|
||||
|
||||
if o.NoAutostash {
|
||||
opts = append(opts, "--no-autostash")
|
||||
}
|
||||
|
||||
if o.NoKeepEmpty {
|
||||
opts = append(opts, "--no-keep-empty")
|
||||
}
|
||||
|
||||
if o.NoReapplyCherryPicks {
|
||||
opts = append(opts, "--no-reapply-cherry-picks")
|
||||
}
|
||||
|
||||
if o.NoRebaseMerges {
|
||||
opts = append(opts, "--no-rebase-merges")
|
||||
}
|
||||
|
||||
if o.NoRescheduleFailedExec {
|
||||
opts = append(opts, "--no-reschedule-failed-exec")
|
||||
}
|
||||
|
||||
if o.NoReReReAutoupdate {
|
||||
opts = append(opts, "--no-rerere-autoupdate")
|
||||
}
|
||||
|
||||
if o.NoStat {
|
||||
opts = append(opts, "--no-stat")
|
||||
}
|
||||
|
||||
if o.NoUpdateRefs {
|
||||
opts = append(opts, "--no-update-refs")
|
||||
}
|
||||
|
||||
if o.NoVerify {
|
||||
opts = append(opts, "--no-verify")
|
||||
}
|
||||
|
||||
if o.Onto != "" {
|
||||
opts = append(opts, fmt.Sprintf("--onto=%s", o.Onto))
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.ReapplyCherryPicks {
|
||||
opts = append(opts, "--reapply-cherry-picks")
|
||||
}
|
||||
|
||||
if o.RebaseMerges != "" {
|
||||
opts = append(opts, fmt.Sprintf("--rebase-merges=%s", string(o.RebaseMerges)))
|
||||
}
|
||||
|
||||
if o.RescheduleFailedExec {
|
||||
opts = append(opts, "--reschedule-failed-exec")
|
||||
}
|
||||
|
||||
if o.ResetAuthorDate {
|
||||
opts = append(opts, "--reset-author-date")
|
||||
}
|
||||
|
||||
if o.ReReReAutoupdate {
|
||||
opts = append(opts, "--rerere-autoupdate")
|
||||
}
|
||||
|
||||
if o.Root {
|
||||
opts = append(opts, "--root")
|
||||
}
|
||||
|
||||
if o.Stat {
|
||||
opts = append(opts, "--stat")
|
||||
}
|
||||
|
||||
if o.Strategy != "" {
|
||||
opts = append(opts, fmt.Sprintf("--strategy=%s", string(o.Strategy)))
|
||||
}
|
||||
|
||||
for _, opt := range o.StragegyOptions {
|
||||
opts = append(opts, fmt.Sprintf("--strategy-option=%s", string(opt)))
|
||||
}
|
||||
|
||||
if o.UpdateRefs {
|
||||
opts = append(opts, "--update-refs")
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
opts = append(opts, "--verbose")
|
||||
}
|
||||
|
||||
if o.Verify {
|
||||
opts = append(opts, "--verify")
|
||||
}
|
||||
|
||||
if o.Whitespace != "" {
|
||||
opts = append(opts, fmt.Sprintf("--whitespace=%s", string(o.Whitespace)))
|
||||
}
|
||||
|
||||
if o.Branch != "" {
|
||||
opts = append(opts, o.Branch)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
94
tools/pipeline/internal/pkg/git/reset.go
Normal file
94
tools/pipeline/internal/pkg/git/reset.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ResetMode is is mode to use when resetting the repository
|
||||
type ResetMode string
|
||||
|
||||
const (
|
||||
ResetModeSoft ResetMode = "soft"
|
||||
ResetModeMixed ResetMode = "mixed"
|
||||
ResetModeHard ResetMode = "hard"
|
||||
ResetModeMerge ResetMode = "merge"
|
||||
ResetModeKeep ResetMode = "keep"
|
||||
)
|
||||
|
||||
// ResetOpts are the git reset flags and arguments
|
||||
// See: https://git-scm.com/docs/git-reset
|
||||
type ResetOpts struct {
|
||||
// Options
|
||||
Mode ResetMode // [--soft, --hard, etc..]
|
||||
NoRefresh bool // --no-refresh
|
||||
Patch bool // --patch
|
||||
Quiet bool // --quiet
|
||||
Refresh bool // --refresh
|
||||
|
||||
// Targets
|
||||
Commit string // <commit>
|
||||
Treeish string // <tree-ish>
|
||||
PathSpec []string // <pathspec>
|
||||
}
|
||||
|
||||
// Reset runs the git reset command
|
||||
func (c *Client) Reset(ctx context.Context, opts *ResetOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "reset", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *ResetOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *ResetOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
// Do mode before flags if it set
|
||||
if o.Mode != "" {
|
||||
opts = append(opts, "--"+string(o.Mode))
|
||||
}
|
||||
|
||||
// Flags
|
||||
if o.NoRefresh {
|
||||
opts = append(opts, "--no-refresh")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.Refresh {
|
||||
opts = append(opts, "--refresh")
|
||||
}
|
||||
|
||||
// Do Patch after flags but before our targets
|
||||
if o.Patch {
|
||||
opts = append(opts, "--patch")
|
||||
}
|
||||
|
||||
// Do our targets
|
||||
if o.Commit != "" {
|
||||
opts = append(opts, o.Commit)
|
||||
}
|
||||
|
||||
if o.Treeish != "" {
|
||||
opts = append(opts, o.Treeish)
|
||||
}
|
||||
|
||||
// If there's a pathspec, append the paths at the very end
|
||||
if len(o.PathSpec) > 0 {
|
||||
opts = append(append(opts, "--"), o.PathSpec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
75
tools/pipeline/internal/pkg/git/rm.go
Normal file
75
tools/pipeline/internal/pkg/git/rm.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RmOpts are the git rm flags and arguments
|
||||
type RmOpts struct {
|
||||
Cached bool // --cached
|
||||
DryRun bool // --dry-run
|
||||
Force bool // --force
|
||||
IgnoreUnmatched bool // --ignore-unmatched
|
||||
Quiet bool // --quiet
|
||||
Recursive bool // -r
|
||||
Sparse bool // --sparse
|
||||
|
||||
PathSpec []string // <pathspec>
|
||||
}
|
||||
|
||||
// Rm runs the git rm command
|
||||
func (c *Client) Rm(ctx context.Context, opts *RmOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "rm", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *RmOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *RmOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
if o.Cached {
|
||||
opts = append(opts, "--cached")
|
||||
}
|
||||
|
||||
if o.DryRun {
|
||||
opts = append(opts, "--dry-run")
|
||||
}
|
||||
|
||||
if o.Force {
|
||||
opts = append(opts, "--force")
|
||||
}
|
||||
|
||||
if o.IgnoreUnmatched {
|
||||
opts = append(opts, "--ignore-unmatched")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.Recursive {
|
||||
opts = append(opts, "-r")
|
||||
}
|
||||
|
||||
if o.Sparse {
|
||||
opts = append(opts, "--sparse")
|
||||
}
|
||||
|
||||
// If there's a pathspec, append the paths at the very end
|
||||
if len(o.PathSpec) > 0 {
|
||||
opts = append(append(opts, "--"), o.PathSpec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
108
tools/pipeline/internal/pkg/git/show.go
Normal file
108
tools/pipeline/internal/pkg/git/show.go
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
DiffAlgorithm = string
|
||||
DiffMergeFormat = string
|
||||
)
|
||||
|
||||
const (
|
||||
DiffAlgorithmPatience DiffAlgorithm = "patience"
|
||||
DiffAlgorithmMinimal DiffAlgorithm = "minimal"
|
||||
DiffAlgorithmHistogram DiffAlgorithm = "histogram"
|
||||
DiffAlgorithmMyers DiffAlgorithm = "myers"
|
||||
|
||||
DiffMergeFormatOff DiffMergeFormat = "off"
|
||||
DiffMergeFormatNone DiffMergeFormat = "none"
|
||||
DiffMergeFormatFirstParent DiffMergeFormat = "first-parent"
|
||||
DiffMergeFormatSeparate DiffMergeFormat = "separate"
|
||||
DiffMergeFormatCombined DiffMergeFormat = "combined"
|
||||
DiffMergeFormatDenseCombined DiffMergeFormat = "dense-combined"
|
||||
DiffMergeFormatRemerge DiffMergeFormat = "remerge"
|
||||
)
|
||||
|
||||
// ShowOpts are the git show flags and arguments
|
||||
// See: https://git-scm.com/docs/git-show
|
||||
type ShowOpts struct {
|
||||
// Options
|
||||
DiffAlgorithm DiffAlgorithm // --diff-algorithm=<algo>
|
||||
DiffMerges DiffMergeFormat // --diff-merges=<format>
|
||||
Format string // --format <format>
|
||||
NoColor bool // --no-color
|
||||
NoPatch bool // --no-patch
|
||||
Patch bool // --patch
|
||||
Output string // --output=<file>
|
||||
Raw bool // --raw
|
||||
|
||||
// Targets
|
||||
Object string // <object>
|
||||
PathSpec []string // <pathspec>
|
||||
}
|
||||
|
||||
// Show runs the git show command
|
||||
func (c *Client) Show(ctx context.Context, opts *ShowOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "show", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *ShowOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *ShowOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
if o.DiffAlgorithm != "" {
|
||||
opts = append(opts, fmt.Sprintf("--diff-algorithm=%s", string(o.DiffAlgorithm)))
|
||||
}
|
||||
|
||||
if o.DiffMerges != "" {
|
||||
opts = append(opts, fmt.Sprintf("--diff-merges=%s", string(o.DiffMerges)))
|
||||
}
|
||||
|
||||
if o.Format != "" {
|
||||
opts = append(opts, fmt.Sprintf("--format=%s", string(o.Format)))
|
||||
}
|
||||
|
||||
if o.NoColor {
|
||||
opts = append(opts, "--no-color")
|
||||
}
|
||||
|
||||
if o.NoPatch {
|
||||
opts = append(opts, "--no-patch")
|
||||
}
|
||||
|
||||
if o.Output != "" {
|
||||
opts = append(opts, fmt.Sprintf("--output=%s", o.Output))
|
||||
}
|
||||
|
||||
if o.Patch {
|
||||
opts = append(opts, "--patch")
|
||||
}
|
||||
|
||||
if o.Raw {
|
||||
opts = append(opts, "--raw")
|
||||
}
|
||||
|
||||
opts = append(opts, o.Object)
|
||||
|
||||
// If there's a pathspec, append the paths at the very end
|
||||
if len(o.PathSpec) > 0 {
|
||||
opts = append(append(opts, "--"), o.PathSpec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
142
tools/pipeline/internal/pkg/git/status.go
Normal file
142
tools/pipeline/internal/pkg/git/status.go
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// IgnoredMode determines how to handle ignored files
|
||||
IgnoredMode = string
|
||||
// IgnoredMode determines how to handle changes to submodules
|
||||
IgnoreSubmodulesWhen = string
|
||||
)
|
||||
|
||||
const (
|
||||
IgnoredModeTraditional IgnoredMode = "traditional"
|
||||
IgnoredModeNo IgnoredMode = "no"
|
||||
IgnoredModeMatching IgnoredMode = "matching"
|
||||
|
||||
IgnoreSubmodulesWhenNone IgnoreSubmodulesWhen = "none"
|
||||
IgnoreSubmodulesWhenUntracked IgnoreSubmodulesWhen = "untracked"
|
||||
IgnoreSubmodulesWhenDirty IgnoreSubmodulesWhen = "dirty"
|
||||
IgnoreSubmodulesWhenAll IgnoreSubmodulesWhen = "all"
|
||||
)
|
||||
|
||||
// StatusOpts are the git status flags and arguments
|
||||
// See: https://git-scm.com/docs/git-status
|
||||
type StatusOpts struct {
|
||||
// Options
|
||||
AheadBehind bool // --ahead-behind
|
||||
Branch bool // --branch
|
||||
Column string // --column=
|
||||
FindRenames uint // --find-renames=
|
||||
Ignored IgnoredMode // --ignored=
|
||||
IgnoreSubmodules IgnoreSubmodulesWhen // --ignore-submodules=<when>
|
||||
Long bool // --long
|
||||
NoAheadBehind bool // --no-ahead-behind
|
||||
NoColumn bool // --no-column
|
||||
NoRenames bool // --no-renames
|
||||
Porcelain bool // --porcelain
|
||||
Renames bool // --renames
|
||||
Short bool // --short
|
||||
ShowStash bool // --show-stash
|
||||
UntrackedFiles UntrackedFiles // --untracked-files=<mode>
|
||||
Verbose bool // --verbose
|
||||
|
||||
// Targets
|
||||
PathSpec []string // <pathspec>
|
||||
}
|
||||
|
||||
// Status runs the git status command
|
||||
func (c *Client) Status(ctx context.Context, opts *StatusOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "status", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *StatusOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *StatusOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
if o.AheadBehind {
|
||||
opts = append(opts, "--ahead-behind")
|
||||
}
|
||||
|
||||
if o.Branch {
|
||||
opts = append(opts, "--branch")
|
||||
}
|
||||
|
||||
if o.Column != "" {
|
||||
opts = append(opts, fmt.Sprintf("--column=%s", o.Column))
|
||||
}
|
||||
|
||||
if o.FindRenames > 0 {
|
||||
opts = append(opts, fmt.Sprintf("--find-renames=%d", o.FindRenames))
|
||||
}
|
||||
|
||||
if o.Ignored != "" {
|
||||
opts = append(opts, fmt.Sprintf("--ignored=%s", string(o.Ignored)))
|
||||
}
|
||||
|
||||
if o.IgnoreSubmodules != "" {
|
||||
opts = append(opts, fmt.Sprintf("--ignore-submodules=%s", string(o.IgnoreSubmodules)))
|
||||
}
|
||||
|
||||
if o.Long {
|
||||
opts = append(opts, "--long")
|
||||
}
|
||||
|
||||
if o.NoAheadBehind {
|
||||
opts = append(opts, "--no-ahead-behind")
|
||||
}
|
||||
|
||||
if o.NoColumn {
|
||||
opts = append(opts, "--no-column")
|
||||
}
|
||||
|
||||
if o.NoRenames {
|
||||
opts = append(opts, "--no-renames")
|
||||
}
|
||||
|
||||
if o.Porcelain {
|
||||
opts = append(opts, "--porcelain")
|
||||
}
|
||||
|
||||
if o.Renames {
|
||||
opts = append(opts, "--renames")
|
||||
}
|
||||
|
||||
if o.Short {
|
||||
opts = append(opts, "--short")
|
||||
}
|
||||
|
||||
if o.ShowStash {
|
||||
opts = append(opts, "--show-stash")
|
||||
}
|
||||
|
||||
if o.UntrackedFiles != "" {
|
||||
opts = append(opts, fmt.Sprintf("--untracked-files=%s", string(o.UntrackedFiles)))
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
opts = append(opts, "--verbose")
|
||||
}
|
||||
|
||||
// If there's a pathspec, append the paths at the very end
|
||||
if len(o.PathSpec) > 0 {
|
||||
opts = append(append(opts, "--"), o.PathSpec...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
Loading…
Reference in New Issue
Block a user