🌱 Included additional method to git client (#3761)

* 🌱 Included additional method to git client

- Included additional methods to satisfy the local git client

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>

* Code review comments.

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>

* Fixed the incorrect gitlab test config.

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>

* Fixed code review comments.

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>

---------

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>
This commit is contained in:
Naveen 2024-01-07 11:53:58 -06:00 committed by GitHub
parent 7a4c1bdaff
commit a4148d9f17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 266 additions and 56 deletions

View File

@ -16,6 +16,7 @@
package git
import (
"context"
"errors"
"fmt"
"io"
@ -24,9 +25,12 @@ import (
"regexp"
"strings"
"sync"
"time"
"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/plumbing/storer"
cp "github.com/otiai10/copy"
"github.com/ossf/scorecard/v4/clients"
@ -37,19 +41,21 @@ const repoDir = "repo*"
var (
errNilCommitFound = errors.New("nil commit found")
errEmptyQuery = errors.New("query is empty")
errDefaultBranch = errors.New("default branch name could not be determined")
)
type Client struct {
repo clients.Repo
errListCommits error
gitRepo *git.Repository
worktree *git.Worktree
listCommits *sync.Once
tempDir string
errListCommits error
commits []clients.Commit
commitDepth int
}
func (c *Client) InitRepo(uri, commitSHA string, commitDepth int) error {
func (c *Client) InitRepo(repo clients.Repo, commitSHA string, commitDepth int) error {
// cleanup previous state, if any.
c.Close()
c.listCommits = new(sync.Once)
@ -61,10 +67,10 @@ func (c *Client) InitRepo(uri, commitSHA string, commitDepth int) error {
if err != nil {
return fmt.Errorf("os.MkdirTemp: %w", err)
}
// git clone
uri := repo.URI()
c.tempDir = tempDir
const filePrefix = "file://"
if strings.HasPrefix(uri, filePrefix) {
if strings.HasPrefix(uri, filePrefix) { //nolint:nestif
if err := cp.Copy(strings.TrimPrefix(uri, filePrefix), tempDir); err != nil {
return fmt.Errorf("cp.Copy: %w", err)
}
@ -73,13 +79,19 @@ func (c *Client) InitRepo(uri, commitSHA string, commitDepth int) error {
return fmt.Errorf("git.PlainOpen: %w", err)
}
} else {
if !strings.HasPrefix(uri, "https://") && !strings.HasPrefix(uri, "ssh://") {
uri = "https://" + uri
}
if !strings.HasSuffix(uri, ".git") {
uri = uri + ".git"
}
c.gitRepo, err = git.PlainClone(tempDir, false /*isBare*/, &git.CloneOptions{
URL: uri,
Progress: os.Stdout,
})
if err != nil {
return fmt.Errorf("git.PlainClone: %w", err)
}
}
if err != nil {
return fmt.Errorf("git.PlainClone: %w %s", err, uri)
}
c.tempDir = tempDir
c.worktree, err = c.gitRepo.Worktree()
@ -188,17 +200,60 @@ func (c *Client) Search(request clients.SearchRequest) (clients.SearchResponse,
return ret, nil
}
// TODO(#1709): Implement below fns using go-git.
func (c *Client) SearchCommits(request clients.SearchCommitsOptions) ([]clients.Commit, error) {
return nil, nil
}
func (c *Client) ListFiles(predicate func(string) (bool, error)) ([]string, error) {
return nil, nil
ref, err := c.gitRepo.Head()
if err != nil {
return nil, fmt.Errorf("git.Head: %w", err)
}
commit, err := c.gitRepo.CommitObject(ref.Hash())
if err != nil {
return nil, fmt.Errorf("git.CommitObject: %w", err)
}
tree, err := commit.Tree()
if err != nil {
return nil, fmt.Errorf("git.Commit.Tree: %w", err)
}
var files []string
err = tree.Files().ForEach(func(f *object.File) error {
shouldInclude, err := predicate(f.Name)
if err != nil {
return fmt.Errorf("error applying predicate to file %s: %w", f.Name, err)
}
if shouldInclude {
files = append(files, f.Name)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("git.Tree.Files: %w", err)
}
return files, nil
}
func (c *Client) GetFileContent(filename string) ([]byte, error) {
return nil, nil
// Create the full path of the file
fullPath := filepath.Join(c.tempDir, filename)
// Read the file
content, err := os.ReadFile(fullPath)
if err != nil {
return nil, fmt.Errorf("os.ReadFile: %w", err)
}
return content, nil
}
func (c *Client) IsArchived() (bool, error) {
return false, clients.ErrUnsupportedFeature
}
func (c *Client) URI() string {
return c.repo.URI()
}
func (c *Client) Close() error {
@ -207,3 +262,119 @@ func (c *Client) Close() error {
}
return nil
}
func (c *Client) GetBranch(branch string) (*clients.BranchRef, error) {
// Get the branch reference
ref, err := c.gitRepo.Branch(branch)
if err != nil {
return nil, fmt.Errorf("git.Branch: %w", err)
}
// Get the commit object
if err != nil {
return nil, fmt.Errorf("git.CommitObject: %w", err)
}
f := false
// Create the BranchRef object
branchRef := &clients.BranchRef{
Name: &ref.Name,
Protected: &f,
}
return branchRef, nil
}
func (c *Client) GetCreatedAt() (time.Time, error) {
// Retrieve the first commit of the repository
commitIter, err := c.gitRepo.Log(&git.LogOptions{Order: git.LogOrderCommitterTime})
if err != nil {
return time.Time{}, fmt.Errorf("git.Log: %w", err)
}
defer commitIter.Close()
// Iterate through the commits to find the first one
var firstCommit *object.Commit
err = commitIter.ForEach(func(c *object.Commit) error {
firstCommit = c
return storer.ErrStop
})
if err != nil && !errors.Is(err, storer.ErrStop) {
return time.Time{}, fmt.Errorf("commitIter.ForEach: %w", err)
}
if firstCommit == nil {
return time.Time{}, errNilCommitFound
}
// Return the commit time of the first commit
return firstCommit.Committer.When, nil
}
func (c *Client) GetDefaultBranchName() (string, error) {
headRef, err := c.gitRepo.Head()
if err != nil {
return "", fmt.Errorf("git.Head: %w", err)
}
// Extract the branch name from the Head reference
defaultBranch := headRef.Name()
if defaultBranch == "" {
return "", errDefaultBranch
}
return string(defaultBranch), nil
}
func (c *Client) GetDefaultBranch() (*clients.BranchRef, error) {
// TODO: Implement this
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) GetOrgRepoClient(ctx context.Context) (clients.RepoClient, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListIssues() ([]clients.Issue, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListLicenses() ([]clients.License, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListReleases() ([]clients.Release, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListContributors() ([]clients.User, error) {
// TODO: Implement this
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListSuccessfulWorkflowRuns(filename string) ([]clients.WorkflowRun, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListCheckRunsForRef(ref string) ([]clients.CheckRun, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListStatuses(ref string) ([]clients.Status, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListWebhooks() ([]clients.Webhook, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) ListProgrammingLanguages() ([]clients.Language, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) SearchCommits(request clients.SearchCommitsOptions) ([]clients.Commit, error) {
return nil, clients.ErrUnsupportedFeature
}
func (c *Client) LocalPath() (string, error) {
return c.tempDir, nil
}

View File

@ -15,7 +15,6 @@
package git
import (
"fmt"
"os"
"path/filepath"
"testing"
@ -27,6 +26,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/ossf/scorecard/v4/clients"
"github.com/ossf/scorecard/v4/clients/localdir"
)
func createTestRepo(t *testing.T) (path string) {
@ -79,29 +79,19 @@ func createTestRepo(t *testing.T) (path string) {
func TestInitRepo(t *testing.T) {
t.Parallel()
tests := []struct { //nolint:govet
tests := []struct {
name string
uri string
commitSHA string
commitDepth int
expectedErr string
commitDepth int
}{
{
name: "Success",
uri: "file://%s",
commitSHA: "HEAD",
commitDepth: 1,
},
{
name: "InvalidUri",
uri: ":",
commitSHA: "",
commitDepth: 1,
expectedErr: "repository does not exist",
},
{
name: "NegativeCommitDepth",
uri: "file://%s",
commitSHA: "HEAD",
commitDepth: -1,
},
@ -113,10 +103,14 @@ func TestInitRepo(t *testing.T) {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
uri := fmt.Sprintf(test.uri, repoPath)
uri := repoPath
client := &Client{}
err := client.InitRepo(uri, test.commitSHA, test.commitDepth)
repo, err := localdir.MakeLocalDirRepo(uri)
if err != nil {
t.Fatalf("MakeLocalDirRepo(%s) failed: %v", uri, err)
}
err = client.InitRepo(repo, test.commitSHA, test.commitDepth)
if (test.expectedErr != "") != (err != nil) {
t.Errorf("Unexpected error during InitRepo: %v", err)
}
@ -132,8 +126,12 @@ func TestListCommits(t *testing.T) {
commitDepth := 1
expectedLen := 1
commitSHA := "HEAD"
uri := fmt.Sprintf("file://%s", repoPath)
if err := client.InitRepo(uri, commitSHA, commitDepth); err != nil {
uri := repoPath
repo, err := localdir.MakeLocalDirRepo(uri)
if err != nil {
t.Fatalf("MakeLocalDirRepo(%s) failed: %v", uri, err)
}
if err := client.InitRepo(repo, commitSHA, commitDepth); err != nil {
t.Fatalf("InitRepo(%s) failed: %v", uri, err)
}
@ -221,8 +219,12 @@ func TestSearch(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
client := &Client{}
uri := fmt.Sprintf("file://%s", repoPath)
if err := client.InitRepo(uri, "HEAD", 1); err != nil {
uri := repoPath
repo, err := localdir.MakeLocalDirRepo(uri)
if err != nil {
t.Fatalf("MakeLocalDirRepo(%s) failed: %v", uri, err)
}
if err := client.InitRepo(repo, "HEAD", 1); err != nil {
t.Fatalf("InitRepo(%s) failed: %v", uri, err)
}

View File

@ -15,10 +15,15 @@
package git
import (
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/ossf/scorecard/v4/clients"
"github.com/ossf/scorecard/v4/clients/githubrepo"
"github.com/ossf/scorecard/v4/clients/gitlabrepo"
"github.com/ossf/scorecard/v4/clients/localdir"
)
var _ = DescribeTable("Test ListCommits commit-depth for HEAD",
@ -26,22 +31,37 @@ var _ = DescribeTable("Test ListCommits commit-depth for HEAD",
const commitSHA = clients.HeadSHA
const commitDepth = 1
client := &Client{}
Expect(client.InitRepo(uri, commitSHA, commitDepth)).To(BeNil())
GinkgoT().Logf("URI: %s\n", uri)
repo, err := getRepoClient(uri)
Expect(err).To(BeNil())
Expect(client.InitRepo(repo, commitSHA, commitDepth)).To(BeNil())
commits, err := client.ListCommits()
Expect(err).To(BeNil())
Expect(len(commits)).Should(BeEquivalentTo(commitDepth))
Expect(client.Close()).To(BeNil())
},
Entry("Local", "../../"),
Entry("GitHub", "https://github.com/ossf/scorecard"),
Entry("Local", "file://../../"),
Entry("GitLab", "https://gitlab.haskell.org/haskell/filepath"),
)
func getRepoClient(uri string) (clients.Repo, error) {
if strings.Contains(uri, "github.com") {
return githubrepo.MakeGithubRepo(uri)
} else if strings.Contains(uri, "gitlab") {
return gitlabrepo.MakeGitlabRepo(uri)
}
return localdir.MakeLocalDirRepo(uri)
}
var _ = DescribeTable("Test ListCommits commit-depth and latest commit at [0]",
func(uri, commitSHA string) {
const commitDepth = 10
client := &Client{}
Expect(client.InitRepo(uri, commitSHA, commitDepth)).To(BeNil())
repo, err := getRepoClient(uri)
GinkgoT().Logf("URI: %s\n", uri)
Expect(err).To(BeNil())
Expect(client.InitRepo(repo, commitSHA, commitDepth)).To(BeNil())
commits, err := client.ListCommits()
Expect(err).To(BeNil())
Expect(len(commits)).Should(BeEquivalentTo(commitDepth))
@ -49,7 +69,7 @@ var _ = DescribeTable("Test ListCommits commit-depth and latest commit at [0]",
Expect(client.Close()).To(BeNil())
},
Entry("GitHub", "https://github.com/ossf/scorecard", "c06ac740cc49fea404c54c036000731d5ea6ebe3"),
Entry("Local", "file://../../", "c06ac740cc49fea404c54c036000731d5ea6ebe3"),
Entry("Local", "../../", "c06ac740cc49fea404c54c036000731d5ea6ebe3"),
Entry("GitLab", "https://gitlab.haskell.org/haskell/filepath", "98f8bba9eac8c7183143d290d319be7df76c258b"),
)
@ -58,7 +78,9 @@ var _ = DescribeTable("Test ListCommits without enough commits",
const commitSHA = "dc1835b7ffe526969d65436b621e171e3386771e"
const commitDepth = 10
client := &Client{}
Expect(client.InitRepo(uri, commitSHA, commitDepth)).To(BeNil())
repo, err := getRepoClient(uri)
Expect(err).To(BeNil())
Expect(client.InitRepo(repo, commitSHA, commitDepth)).To(BeNil())
commits, err := client.ListCommits()
Expect(err).To(BeNil())
Expect(len(commits)).Should(BeEquivalentTo(3))
@ -66,8 +88,7 @@ var _ = DescribeTable("Test ListCommits without enough commits",
Expect(client.Close()).To(BeNil())
},
Entry("GitHub", "https://github.com/ossf/scorecard"),
Entry("Local", "file://../../"),
// TODO(#1709): Add equivalent test for GitLab.
Entry("Local", "../../"),
)
var _ = DescribeTable("Test Search across a repo",
@ -77,7 +98,9 @@ var _ = DescribeTable("Test Search across a repo",
commitDepth = 10
)
client := &Client{}
Expect(client.InitRepo(uri, commitSHA, commitDepth)).To(BeNil())
repo, err := getRepoClient(uri)
Expect(err).To(BeNil())
Expect(client.InitRepo(repo, commitSHA, commitDepth)).To(BeNil())
resp, err := client.Search(clients.SearchRequest{
Query: "github/codeql-action/analyze",
})
@ -86,8 +109,7 @@ var _ = DescribeTable("Test Search across a repo",
Expect(client.Close()).To(BeNil())
},
Entry("GitHub", "https://github.com/ossf/scorecard"),
Entry("Local", "file://../../"),
// TODO(#1709): Add equivalent test for GitLab.
Entry("Local", "../../"),
)
var _ = DescribeTable("Test Search within a path",
@ -97,7 +119,9 @@ var _ = DescribeTable("Test Search within a path",
commitDepth = 10
)
client := &Client{}
Expect(client.InitRepo(uri, commitSHA, commitDepth)).To(BeNil())
repo, err := getRepoClient(uri)
Expect(err).To(BeNil())
Expect(client.InitRepo(repo, commitSHA, commitDepth)).To(BeNil())
resp, err := client.Search(clients.SearchRequest{
Query: "github/codeql-action/analyze",
Path: ".github/workflows",
@ -107,8 +131,7 @@ var _ = DescribeTable("Test Search within a path",
Expect(client.Close()).To(BeNil())
},
Entry("GitHub", "https://github.com/ossf/scorecard"),
Entry("Local", "file://../../"),
// TODO(#1709): Add equivalent test for GitLab.
Entry("Local", "../../"),
)
var _ = DescribeTable("Test Search within a filename",
@ -118,7 +141,9 @@ var _ = DescribeTable("Test Search within a filename",
commitDepth = 10
)
client := &Client{}
Expect(client.InitRepo(uri, commitSHA, commitDepth)).To(BeNil())
repo, err := getRepoClient(uri)
Expect(err).To(BeNil())
Expect(client.InitRepo(repo, commitSHA, commitDepth)).To(BeNil())
resp, err := client.Search(clients.SearchRequest{
Query: "github/codeql-action/analyze",
Filename: "codeql-analysis.yml",
@ -128,8 +153,7 @@ var _ = DescribeTable("Test Search within a filename",
Expect(client.Close()).To(BeNil())
},
Entry("GitHub", "https://github.com/ossf/scorecard"),
Entry("Local", "file://../../"),
// TODO(#1709): Add equivalent test for GitLab.
Entry("Local", "../../"),
)
var _ = DescribeTable("Test Search within path and filename",
@ -139,7 +163,9 @@ var _ = DescribeTable("Test Search within path and filename",
commitDepth = 10
)
client := &Client{}
Expect(client.InitRepo(uri, commitSHA, commitDepth)).To(BeNil())
repo, err := getRepoClient(uri)
Expect(err).To(BeNil())
Expect(client.InitRepo(repo, commitSHA, commitDepth)).To(BeNil())
resp, err := client.Search(clients.SearchRequest{
Query: "github/codeql-action/analyze",
Path: ".github/workflows",
@ -149,7 +175,6 @@ var _ = DescribeTable("Test Search within path and filename",
Expect(resp.Hits).Should(BeEquivalentTo(1))
Expect(client.Close()).To(BeNil())
},
Entry("Local", "../../"),
Entry("GitHub", "https://github.com/ossf/scorecard"),
Entry("Local", "file://../../"),
// TODO(#1709): Add equivalent test for GitLab.
)

View File

@ -150,5 +150,6 @@ func MakeGitlabRepo(input string) (clients.Repo, error) {
if err := repo.IsValid(); err != nil {
return nil, fmt.Errorf("error in IsValid: %w", err)
}
return &repo, nil
}

View File

@ -64,14 +64,24 @@ func TestRepoURL_IsValid(t *testing.T) {
wantErr: false,
},
{
name: "valid https address with trailing slash",
name: "valid gitlab project",
expected: repoURL{
scheme: "https",
host: "https://gitlab.com",
owner: "ossf-test",
project: "scorecard-check-binary-artifacts-e2e",
},
inputURL: "https://gitlab.com/ossf-test/scorecard-check-binary-artifacts-e2e/",
inputURL: "https://gitlab.com/ossf-test/scorecard-check-binary-artifacts-e2e",
wantErr: false,
},
{
name: "valid https address with trailing slash",
expected: repoURL{
scheme: "https",
host: "https://gitlab.haskell.org",
owner: "haskell",
project: "filepath",
},
inputURL: "https://gitlab.haskell.org/haskell/filepath",
wantErr: false,
},
{
@ -105,6 +115,7 @@ func TestRepoURL_IsValid(t *testing.T) {
if err := r.IsValid(); (err != nil) != tt.wantErr {
t.Errorf("repoURL.IsValid() error = %v, wantErr %v", err, tt.wantErr)
}
t.Log(r.URI())
if !tt.wantErr && !cmp.Equal(tt.expected, r, cmpopts.IgnoreUnexported(repoURL{})) {
t.Logf("expected: %s GOT: %s", tt.expected.host, r.host)
t.Logf("expected: %s GOT: %s", tt.expected.owner, r.owner)