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:
parent
05b51d3e82
commit
1f34b135da
1
go.mod
1
go.mod
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user