1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-02-21 19:48:53 +02:00

LogRange provide git log ref1..ref2 (#2440)

* LogRange provide git log ref1..ref2

This we need for checking inside the commit range
for transportRequestIds and changeDocumentIDs in the
body of the commit message.
This commit is contained in:
Marcus Holl 2021-01-29 12:36:15 +01:00 committed by GitHub
parent 05b51d3e82
commit 1f34b135da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 247 additions and 0 deletions

1
go.mod
View File

@ -20,6 +20,7 @@ require (
github.com/frankban/quicktest v1.10.0 // indirect
github.com/getsentry/sentry-go v0.7.0
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.1.0
github.com/go-openapi/runtime v0.19.22
github.com/go-openapi/spec v0.19.9 // indirect

View File

@ -112,6 +112,50 @@ func changeBranch(branchName string, worktree utilsWorkTree) error {
return nil
}
// LogRange Returns a CommitIterator providing all commits reachable from 'to', but
// not reachable by 'from'.
func LogRange(repo *git.Repository, from, to string) (object.CommitIter, error) {
cTo, err := getCommitObject(to, repo)
if err != nil {
return nil, errors.Wrapf(err, "Cannot provide log range (to: '%s' not found)", to)
}
cFrom, err := getCommitObject(from, repo)
if err != nil {
return nil, errors.Wrapf(err, "Cannot provide log range (from: '%s' not found)", from)
}
ignore := []plumbing.Hash{}
err = object.NewCommitPreorderIter(
cFrom,
map[plumbing.Hash]bool{},
[]plumbing.Hash{},
).ForEach(func(c *object.Commit) error {
ignore = append(ignore, c.ID())
return nil
})
if err != nil {
return nil, errors.Wrap(err, "Cannot provide log range")
}
return object.NewCommitPreorderIter(cTo, map[plumbing.Hash]bool{}, ignore), nil
}
func getCommitObject(ref string, repo *git.Repository) (*object.Commit, error) {
if len(ref) == 0 {
// with go-git v5.1.0 we panic otherwise inside ResolveRevision
return nil, errors.New("Cannot get a commit for an empty ref")
}
r, err := repo.ResolveRevision(plumbing.Revision(ref))
if err != nil {
return nil, errors.Wrapf(err, "Trouble resolving '%s'", ref)
}
c, err := repo.CommitObject(*r)
if err != nil {
return nil, errors.Wrapf(err, "Trouble resolving '%s'", ref)
}
return c, nil
}
type abstractionGit struct{}
func (abstractionGit) plainClone(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) {

View File

@ -2,8 +2,13 @@ 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"
)
@ -108,6 +113,203 @@ func TestChangeBranch(t *testing.T) {
})
}
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