Skip to content

Commit

Permalink
Initial implementation of go-git client
Browse files Browse the repository at this point in the history
Signed-off-by: Azeem Shaikh <azeemshaikh38@gmail.com>
  • Loading branch information
azeemshaikh38 committed Mar 9, 2023
1 parent 170af75 commit 6617f93
Show file tree
Hide file tree
Showing 6 changed files with 475 additions and 9 deletions.
208 changes: 208 additions & 0 deletions clients/git/client.go
@@ -0,0 +1,208 @@
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package git defines helper functions for clients.RepoClient interface.
package git

import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"sync"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
cp "github.com/otiai10/copy"

"github.com/ossf/scorecard/v4/clients"
)

const repoDir = "repo*"

var (
errNilCommitFound = errors.New("nil commit found")
errEmptyQuery = errors.New("Query is empty")
)

type Client struct {
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 {
// cleanup previous state, if any.
c.Close()
c.listCommits = new(sync.Once)
c.commits = nil

// init
c.commitDepth = commitDepth
tempDir, err := os.MkdirTemp("", repoDir)
if err != nil {
return fmt.Errorf("os.MkdirTemp: %w", err)
}

// git clone
const filePrefix = "file://"
if strings.HasPrefix(uri, filePrefix) {
if err := cp.Copy(strings.TrimPrefix(uri, filePrefix), tempDir); err != nil {
return fmt.Errorf("cp.Copy: %w", err)
}
c.gitRepo, err = git.PlainOpen(tempDir)
if err != nil {
return fmt.Errorf("git.PlainOpen: %w", err)
}
} else {
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)
}
}
c.tempDir = tempDir
c.worktree, err = c.gitRepo.Worktree()
if err != nil {
return fmt.Errorf("git.Worktree: %w", err)
}

// git checkout
if commitSHA != clients.HeadSHA {
if err := c.worktree.Checkout(&git.CheckoutOptions{
Hash: plumbing.NewHash(commitSHA),
Force: true, // throw away any unsaved changes.
}); err != nil {
return fmt.Errorf("git.Worktree: %w", err)
}
}

return nil
}

func (c *Client) ListCommits() ([]clients.Commit, error) {
c.listCommits.Do(func() {
commitIter, err := c.gitRepo.Log(&git.LogOptions{
Order: git.LogOrderCommitterTime,
})
if err != nil {
c.errListCommits = fmt.Errorf("git.CommitObjects: %w", err)
return
}
for i := 0; i < c.commitDepth; i++ {
commit, err := commitIter.Next()
if err != nil && !errors.Is(err, io.EOF) {
c.errListCommits = fmt.Errorf("commitIter.Next: %w", err)
return
}
// No more commits.
if errors.Is(err, io.EOF) {
break
}

if commit == nil {
// Not sure in what case a nil commit is returned. Fail explicitly.
c.errListCommits = fmt.Errorf("%w", errNilCommitFound)
return
}

c.commits = append(c.commits, clients.Commit{
SHA: commit.Hash.String(),
Message: commit.Message,
CommittedDate: commit.Committer.When,
Committer: clients.User{
Login: commit.Committer.Email,
},
})
}
})
return c.commits, c.errListCommits
}

func (c *Client) Search(request clients.SearchRequest) (clients.SearchResponse, error) {
// Pattern
if request.Query == "" {
return clients.SearchResponse{}, fmt.Errorf("%w", errEmptyQuery)
}
queryRegexp, err := regexp.Compile(request.Query)
if err != nil {
return clients.SearchResponse{}, fmt.Errorf("regexp.Compile: %w", err)
}
grepOpts := &git.GrepOptions{
Patterns: []*regexp.Regexp{queryRegexp},
}

// path/filename
var pathExpr string
switch {
case request.Path != "" && request.Filename != "":
pathExpr = filepath.Join(fmt.Sprintf("^%s", request.Path),
fmt.Sprintf(".*%s", request.Filename))
case request.Path != "":
pathExpr = fmt.Sprintf("^%s", request.Path)
case request.Filename != "":
pathExpr = filepath.Join(".*", request.Filename)
}
if pathExpr != "" {
pathRegexp, err := regexp.Compile(pathExpr)
if err != nil {
return clients.SearchResponse{}, fmt.Errorf("regexp.Compile: %w", err)
}
grepOpts.PathSpecs = append(grepOpts.PathSpecs, pathRegexp)
}

// Grep
grepResults, err := c.worktree.Grep(grepOpts)
if err != nil {
return clients.SearchResponse{}, fmt.Errorf("git.Grep: %w", err)
}

ret := clients.SearchResponse{}
for _, grepResult := range grepResults {
ret.Results = append(ret.Results, clients.SearchResult{
Path: grepResult.FileName,
})
}
ret.Hits = len(grepResults)
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
}

func (c *Client) GetFileContent(filename string) ([]byte, error) {
return nil, nil
}

func (c *Client) Close() error {
if err := os.RemoveAll(c.tempDir); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("os.RemoveAll: %w", err)
}
return nil
}
51 changes: 51 additions & 0 deletions clients/git/coverage.out
@@ -0,0 +1,51 @@
mode: set
github.com/ossf/scorecard/v4/clients/git/client.go:52.73,61.16 6 1
github.com/ossf/scorecard/v4/clients/git/client.go:61.16,63.3 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:66.2,67.40 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:67.40,68.79 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:68.79,70.4 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:71.3,72.17 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:72.17,74.4 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:75.8,80.17 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:80.17,82.4 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:84.2,86.16 3 1
github.com/ossf/scorecard/v4/clients/git/client.go:86.16,88.3 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:91.2,91.34 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:91.34,95.18 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:95.18,97.4 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:100.2,100.12 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:103.58,104.26 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:104.26,108.17 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:108.17,111.4 2 0
github.com/ossf/scorecard/v4/clients/git/client.go:112.3,112.38 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:112.38,114.45 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:114.45,117.5 2 0
github.com/ossf/scorecard/v4/clients/git/client.go:119.4,119.30 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:119.30,120.10 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:123.4,123.21 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:123.21,127.5 2 0
github.com/ossf/scorecard/v4/clients/git/client.go:129.4,136.6 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:139.2,139.36 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:142.88,144.25 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:144.25,146.3 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:147.2,148.16 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:148.16,150.3 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:151.2,157.9 3 1
github.com/ossf/scorecard/v4/clients/git/client.go:158.52,160.42 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:161.26,162.46 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:163.30,164.51 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:166.2,166.20 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:166.20,168.17 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:168.17,170.4 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:171.3,171.62 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:175.2,176.16 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:176.16,178.3 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:180.2,181.41 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:181.41,185.3 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:186.2,187.17 2 1
github.com/ossf/scorecard/v4/clients/git/client.go:191.96,193.2 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:195.84,197.2 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:199.66,201.2 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:203.32,204.71 1 1
github.com/ossf/scorecard/v4/clients/git/client.go:204.71,206.3 1 0
github.com/ossf/scorecard/v4/clients/git/client.go:207.2,207.12 1 1

0 comments on commit 6617f93

Please sign in to comment.