mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
6676da1f9d
* feat(gitopsUpdateDeployment) forcePush fix(gitopsUpdateDeployment) include registry The push operation in this step can be forced to bypass branch-protection Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> * add unit test Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
455 lines
12 KiB
Go
455 lines
12 KiB
Go
package git
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/go-git/go-billy/v5"
|
|
"github.com/go-git/go-billy/v5/memfs"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
"github.com/go-git/go-git/v5/storage/memory"
|
|
"github.com/stretchr/testify/assert"
|
|
"testing"
|
|
)
|
|
|
|
func TestCommit(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("successful run", func(t *testing.T) {
|
|
t.Parallel()
|
|
worktreeMock := WorktreeMock{}
|
|
hash, err := commitSingleFile(".", "message", "user", &worktreeMock)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, plumbing.Hash([20]byte{4, 5, 6}), hash)
|
|
assert.Equal(t, "user", worktreeMock.author)
|
|
assert.True(t, worktreeMock.commitAll)
|
|
})
|
|
|
|
t.Run("error adding file", func(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := commitSingleFile(".", "message", "user", WorktreeMockFailing{
|
|
failingAdd: true,
|
|
})
|
|
assert.EqualError(t, err, "failed to add file to git: failed to add file")
|
|
})
|
|
|
|
t.Run("error committing file", func(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := commitSingleFile(".", "message", "user", WorktreeMockFailing{
|
|
failingCommit: true,
|
|
})
|
|
assert.EqualError(t, err, "failed to commit file: failed to commit file")
|
|
})
|
|
}
|
|
|
|
func TestPushChangesToRepository(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("successful push", func(t *testing.T) {
|
|
t.Parallel()
|
|
err := pushChangesToRepository("user", "password", nil, RepositoryMock{
|
|
test: t,
|
|
})
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("error pushing", func(t *testing.T) {
|
|
t.Parallel()
|
|
err := pushChangesToRepository("user", "password", nil, RepositoryMockError{})
|
|
assert.EqualError(t, err, "failed to push commit: error on push commits")
|
|
})
|
|
}
|
|
|
|
func TestPlainClone(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("successful clone", func(t *testing.T) {
|
|
t.Parallel()
|
|
abstractedGit := &UtilsGitMock{}
|
|
_, err := plainClone("user", "password", "URL", "directory", abstractedGit)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "directory", abstractedGit.path)
|
|
assert.False(t, abstractedGit.isBare)
|
|
assert.Equal(t, "http-basic-auth - user:*******", abstractedGit.authString)
|
|
assert.Equal(t, "URL", abstractedGit.URL)
|
|
})
|
|
|
|
t.Run("error on cloning", func(t *testing.T) {
|
|
t.Parallel()
|
|
abstractedGit := UtilsGitMockError{}
|
|
_, err := plainClone("user", "password", "URL", "directory", abstractedGit)
|
|
assert.EqualError(t, err, "failed to clone git: error during clone")
|
|
})
|
|
}
|
|
|
|
func TestPlainOpenMock(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("successful clone", func(t *testing.T) {
|
|
t.Parallel()
|
|
abstractedGit := &UtilsGitMock{}
|
|
_, err := plainOpen("directory", abstractedGit)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "directory", abstractedGit.path)
|
|
})
|
|
|
|
t.Run("error on cloning", func(t *testing.T) {
|
|
t.Parallel()
|
|
abstractedGit := UtilsGitMockError{}
|
|
_, err := plainOpen("directory", abstractedGit)
|
|
assert.EqualError(t, err, "Unable to open git repository at 'directory': error during git plain open")
|
|
})
|
|
}
|
|
|
|
func TestChangeBranch(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("checkout existing branch", func(t *testing.T) {
|
|
t.Parallel()
|
|
worktreeMock := &WorktreeMock{}
|
|
err := changeBranch("otherBranch", worktreeMock)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, string(plumbing.NewBranchReferenceName("otherBranch")), worktreeMock.checkedOutBranch)
|
|
assert.False(t, worktreeMock.create)
|
|
})
|
|
|
|
t.Run("empty branch raises error", func(t *testing.T) {
|
|
t.Parallel()
|
|
worktreeMock := &WorktreeMock{}
|
|
err := changeBranch("", worktreeMock)
|
|
assert.EqualError(t, err, "no branch name provided")
|
|
})
|
|
|
|
t.Run("create new branch", func(t *testing.T) {
|
|
t.Parallel()
|
|
err := changeBranch("otherBranch", WorktreeUtilsNewBranch{})
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("error on new branch", func(t *testing.T) {
|
|
t.Parallel()
|
|
err := changeBranch("otherBranch", WorktreeMockFailing{
|
|
failingCheckout: true,
|
|
})
|
|
assert.EqualError(t, err, "failed to checkout branch: failed to checkout branch")
|
|
})
|
|
}
|
|
|
|
func TestLogRange(t *testing.T) {
|
|
|
|
against := func(t *testing.T, r *git.Repository, from, to string, expected []plumbing.Hash) {
|
|
seen := []plumbing.Hash{}
|
|
cIter, err := LogRange(r, from, to)
|
|
if assert.NoError(t, err) {
|
|
err = cIter.ForEach(func(c *object.Commit) error {
|
|
seen = append(seen, c.ID())
|
|
return nil
|
|
})
|
|
if assert.NoError(t, err) {
|
|
if assert.Len(t, seen, len(expected)) {
|
|
assert.Subset(t, seen, expected)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
prepareRepo := func() (r *git.Repository, hashes map[string]plumbing.Hash, err error) {
|
|
|
|
hashes = map[string]plumbing.Hash{}
|
|
|
|
// Creates a commit
|
|
c := func(r *git.Repository, fs billy.Filesystem, name string) (hash plumbing.Hash, err error) {
|
|
|
|
if val, ok := hashes[name]; ok {
|
|
err = fmt.Errorf("Cannot create commit for name '%s'. There is already a commit available (%s) for that name", name, val)
|
|
return
|
|
}
|
|
w, err := r.Worktree()
|
|
if err != nil {
|
|
return
|
|
}
|
|
f, err := fs.Create(fmt.Sprintf("commit%s.txt", name))
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = f.Write([]byte(fmt.Sprintf("Commit %s", name)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = w.Add(fmt.Sprintf("commit%s.txt", name))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
hash, err = w.Commit(fmt.Sprintf("Commit %s", name), &git.CommitOptions{})
|
|
if err != nil {
|
|
return
|
|
}
|
|
hashes[name] = hash
|
|
return
|
|
}
|
|
|
|
// Creates a branch on the currently checked out commit
|
|
b := func(r *git.Repository, name string) (err error) {
|
|
w, err := r.Worktree()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
b := plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", name))
|
|
|
|
err = w.Checkout(&git.CheckoutOptions{Create: true, Force: false, Branch: b})
|
|
|
|
return
|
|
}
|
|
|
|
fs := memfs.New()
|
|
|
|
// create new git repo
|
|
r, err = git.Init(memory.NewStorage(), fs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
config, err := r.Config()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
config.User.Name = "me"
|
|
config.User.Email = "me@example.org"
|
|
|
|
err = r.SetConfig(config)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
w, err := r.Worktree()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// add a commit to the repo -- A --
|
|
hashA, err := c(r, fs, "A")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = r.CreateTag("initial", hashA,
|
|
&git.CreateTagOptions{
|
|
Tagger: &object.Signature{
|
|
Name: config.User.Name,
|
|
Email: config.User.Email,
|
|
},
|
|
Message: "initial",
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// another commit -- B --
|
|
_, err = c(r, fs, "B")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// checkout the first commit again
|
|
err = w.Checkout(&git.CheckoutOptions{Hash: hashA})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// add another file as sucessor of the first commit -- C --
|
|
_, err = c(r, fs, "C")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// and yet another commit -- D --
|
|
_, err = c(r, fs, "D")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = b(r, "branch1")
|
|
|
|
return
|
|
}
|
|
|
|
r, hashes, err := prepareRepo()
|
|
if err != nil {
|
|
assert.FailNow(t, fmt.Sprintf("%v", err), err)
|
|
}
|
|
|
|
// Our repo contains these commits and branches:
|
|
//
|
|
// / C - D <-- HEAD <-- branch1
|
|
// A - B <-- master
|
|
//
|
|
// Tag 'initial' sits on commit A
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("B against D", func(t *testing.T) {
|
|
against(t, r, hashes["B"].String(), hashes["D"].String(), []plumbing.Hash{hashes["C"], hashes["D"]})
|
|
})
|
|
t.Run("A against B", func(t *testing.T) {
|
|
against(t, r, hashes["A"].String(), hashes["B"].String(), []plumbing.Hash{hashes["B"]})
|
|
})
|
|
t.Run("B against HEAD", func(t *testing.T) {
|
|
against(t, r, hashes["B"].String(), "HEAD", []plumbing.Hash{hashes["C"], hashes["D"]})
|
|
})
|
|
t.Run("B against HEAD~1", func(t *testing.T) {
|
|
against(t, r, hashes["B"].String(), "HEAD~1", []plumbing.Hash{hashes["C"]})
|
|
})
|
|
t.Run("A against master", func(t *testing.T) {
|
|
against(t, r, hashes["A"].String(), "master", []plumbing.Hash{hashes["B"]})
|
|
})
|
|
t.Run("master against a branch pointing to D", func(t *testing.T) {
|
|
against(t, r, "master", "branch1", []plumbing.Hash{hashes["C"], hashes["D"]})
|
|
})
|
|
t.Run("A against master~1", func(t *testing.T) {
|
|
against(t, r, hashes["A"].String(), "master~1", []plumbing.Hash{})
|
|
})
|
|
t.Run("Tag against C", func(t *testing.T) {
|
|
against(t, r, "initial", hashes["C"].String(), []plumbing.Hash{hashes["C"]})
|
|
})
|
|
|
|
t.Run("Same ref results in empty result", func(t *testing.T) {
|
|
against(t, r, hashes["A"].String(), hashes["A"].String(), []plumbing.Hash{})
|
|
})
|
|
t.Run("Invalid ref", func(t *testing.T) {
|
|
// it is unlikely as hell, but at some time we might get a test failure here
|
|
// when a commit with this hash has been created during preparation of the repo.
|
|
// Maybe we should check first if a commit with that hash exists and if so try
|
|
// another hash. paranoia :-)
|
|
_, err := LogRange(r, "0123456789012345678901234567890123456789", "HEAD")
|
|
assert.EqualError(t, err, "Cannot provide log range (from: '0123456789012345678901234567890123456789' not found): Trouble resolving '0123456789012345678901234567890123456789': reference not found")
|
|
})
|
|
t.Run("Empty string as ref", func(t *testing.T) {
|
|
_, err := LogRange(r, "", "HEAD")
|
|
assert.EqualError(t, err, "Cannot provide log range (from: '' not found): Cannot get a commit for an empty ref")
|
|
})
|
|
}
|
|
|
|
type RepositoryMock struct {
|
|
worktree *git.Worktree
|
|
test *testing.T
|
|
}
|
|
|
|
func (r RepositoryMock) Worktree() (*git.Worktree, error) {
|
|
if r.worktree != nil {
|
|
return r.worktree, nil
|
|
}
|
|
return &git.Worktree{}, nil
|
|
}
|
|
|
|
func (r RepositoryMock) Push(o *git.PushOptions) error {
|
|
assert.Equal(r.test, "http-basic-auth - user:*******", o.Auth.String())
|
|
return nil
|
|
}
|
|
|
|
type RepositoryMockError struct{}
|
|
|
|
func (RepositoryMockError) Worktree() (*git.Worktree, error) {
|
|
return nil, errors.New("error getting worktree")
|
|
}
|
|
|
|
func (RepositoryMockError) Push(*git.PushOptions) error {
|
|
return errors.New("error on push commits")
|
|
}
|
|
|
|
type WorktreeMockFailing struct {
|
|
failingAdd bool
|
|
failingCommit bool
|
|
failingCheckout bool
|
|
}
|
|
|
|
func (w WorktreeMockFailing) Add(string) (plumbing.Hash, error) {
|
|
if w.failingAdd {
|
|
return [20]byte{}, errors.New("failed to add file")
|
|
}
|
|
return [20]byte{}, nil
|
|
}
|
|
|
|
func (w WorktreeMockFailing) Commit(string, *git.CommitOptions) (plumbing.Hash, error) {
|
|
if w.failingCommit {
|
|
return [20]byte{}, errors.New("failed to commit file")
|
|
}
|
|
return [20]byte{}, nil
|
|
}
|
|
|
|
func (w WorktreeMockFailing) Checkout(*git.CheckoutOptions) error {
|
|
if w.failingCheckout {
|
|
return errors.New("failed to checkout branch")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type WorktreeMock struct {
|
|
expectedBranchName string
|
|
checkedOutBranch string
|
|
create bool
|
|
author string
|
|
commitAll bool
|
|
}
|
|
|
|
func (WorktreeMock) Add(string) (plumbing.Hash, error) {
|
|
return [20]byte{1, 2, 3}, nil
|
|
}
|
|
|
|
func (w *WorktreeMock) Commit(_ string, options *git.CommitOptions) (plumbing.Hash, error) {
|
|
w.author = options.Author.Name
|
|
w.commitAll = options.All
|
|
return [20]byte{4, 5, 6}, nil
|
|
}
|
|
|
|
func (w *WorktreeMock) Checkout(opts *git.CheckoutOptions) error {
|
|
w.checkedOutBranch = string(opts.Branch)
|
|
w.create = opts.Create
|
|
return nil
|
|
}
|
|
|
|
type WorktreeUtilsNewBranch struct{}
|
|
|
|
func (WorktreeUtilsNewBranch) Add(string) (plumbing.Hash, error) {
|
|
panic("implement me")
|
|
}
|
|
|
|
func (WorktreeUtilsNewBranch) Commit(string, *git.CommitOptions) (plumbing.Hash, error) {
|
|
panic("implement me")
|
|
}
|
|
|
|
func (WorktreeUtilsNewBranch) Checkout(opts *git.CheckoutOptions) error {
|
|
if opts.Create {
|
|
return nil
|
|
}
|
|
return errors.New("branch already exists")
|
|
}
|
|
|
|
type UtilsGitMock struct {
|
|
path string
|
|
isBare bool
|
|
authString string
|
|
URL string
|
|
}
|
|
|
|
func (u *UtilsGitMock) plainClone(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) {
|
|
u.path = path
|
|
u.isBare = isBare
|
|
u.authString = o.Auth.String()
|
|
u.URL = o.URL
|
|
return nil, nil
|
|
}
|
|
|
|
func (u *UtilsGitMock) plainOpen(path string) (*git.Repository, error) {
|
|
u.path = path
|
|
return nil, nil
|
|
}
|
|
|
|
type UtilsGitMockError struct{}
|
|
|
|
func (UtilsGitMockError) plainClone(string, bool, *git.CloneOptions) (*git.Repository, error) {
|
|
return nil, errors.New("error during clone")
|
|
}
|
|
|
|
func (UtilsGitMockError) plainOpen(path string) (*git.Repository, error) {
|
|
return nil, errors.New("error during git plain open")
|
|
}
|