Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions clients/azuredevopsrepo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,19 @@
)

type Client struct {
azdoClient git.Client
ctx context.Context
repourl *Repo
repo *git.GitRepository
audit *auditHandler
branches *branchesHandler
commits *commitsHandler
languages *languagesHandler
search *searchHandler
workItems *workItemsHandler
zip *zipHandler
commitDepth int
azdoClient git.Client
ctx context.Context
repourl *Repo
repo *git.GitRepository
audit *auditHandler
branches *branchesHandler
commits *commitsHandler
contributors *contributorsHandler
languages *languagesHandler
search *searchHandler
workItems *workItemsHandler
zip *zipHandler
commitDepth int
}

func (c *Client) InitRepo(inputRepo clients.Repo, commitSHA string, commitDepth int) error {
Expand Down Expand Up @@ -95,6 +96,8 @@

c.commits.init(c.ctx, c.repourl, c.commitDepth)

c.contributors.init(c.ctx, c.repourl)

Check warning on line 100 in clients/azuredevopsrepo/client.go

View check run for this annotation

Codecov / codecov/patch

clients/azuredevopsrepo/client.go#L99-L100

Added lines #L99 - L100 were not covered by tests
c.languages.init(c.ctx, c.repourl)

c.search.init(c.ctx, c.repourl)
Expand Down Expand Up @@ -176,7 +179,7 @@
}

func (c *Client) ListContributors() ([]clients.User, error) {
return nil, clients.ErrUnsupportedFeature
return c.contributors.listContributors()

Check warning on line 182 in clients/azuredevopsrepo/client.go

View check run for this annotation

Codecov / codecov/patch

clients/azuredevopsrepo/client.go#L182

Added line #L182 was not covered by tests
}

func (c *Client) ListSuccessfulWorkflowRuns(filename string) ([]clients.WorkflowRun, error) {
Expand Down Expand Up @@ -260,6 +263,9 @@
commits: &commitsHandler{
gitClient: gitClient,
},
contributors: &contributorsHandler{
gitClient: gitClient,
},

Check warning on line 268 in clients/azuredevopsrepo/client.go

View check run for this annotation

Codecov / codecov/patch

clients/azuredevopsrepo/client.go#L266-L268

Added lines #L266 - L268 were not covered by tests
languages: &languagesHandler{
projectAnalysisClient: projectAnalysisClient,
},
Expand Down
100 changes: 100 additions & 0 deletions clients/azuredevopsrepo/contributors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2024 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 azuredevopsrepo

import (
"context"
"sync"

"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"

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

type contributorsHandler struct {
ctx context.Context
once *sync.Once
repourl *Repo
gitClient git.Client
errSetup error
getCommits fnGetCommits
contributors []clients.User
}

func (c *contributorsHandler) init(ctx context.Context, repourl *Repo) {
c.ctx = ctx
c.once = new(sync.Once)
c.repourl = repourl
c.errSetup = nil
c.getCommits = c.gitClient.GetCommits
c.contributors = nil

Check warning on line 42 in clients/azuredevopsrepo/contributors.go

View check run for this annotation

Codecov / codecov/patch

clients/azuredevopsrepo/contributors.go#L36-L42

Added lines #L36 - L42 were not covered by tests
}

func (c *contributorsHandler) setup() error {
c.once.Do(func() {
contributors := make(map[string]clients.User)
commitsPageSize := 1000
skip := 0
for {
args := git.GetCommitsArgs{
RepositoryId: &c.repourl.id,
SearchCriteria: &git.GitQueryCommitsCriteria{
Top: &commitsPageSize,
Skip: &skip,
},
}
commits, err := c.getCommits(c.ctx, args)
if err != nil {
c.errSetup = err
return
}

Check warning on line 62 in clients/azuredevopsrepo/contributors.go

View check run for this annotation

Codecov / codecov/patch

clients/azuredevopsrepo/contributors.go#L60-L62

Added lines #L60 - L62 were not covered by tests

if commits == nil || len(*commits) == 0 {
break
}

for i := range *commits {
commit := (*commits)[i]
email := *commit.Author.Email
if _, ok := contributors[email]; ok {
user := contributors[email]
user.NumContributions++
contributors[email] = user
} else {
contributors[email] = clients.User{
Login: email,
NumContributions: 1,
Companies: []string{c.repourl.organization},
}
}
}

skip += commitsPageSize
}

for _, contributor := range contributors {
c.contributors = append(c.contributors, contributor)
}
})
return c.errSetup
}

func (c *contributorsHandler) listContributors() ([]clients.User, error) {
if err := c.setup(); err != nil {
return nil, err
}

Check warning on line 97 in clients/azuredevopsrepo/contributors.go

View check run for this annotation

Codecov / codecov/patch

clients/azuredevopsrepo/contributors.go#L94-L97

Added lines #L94 - L97 were not covered by tests

return c.contributors, nil

Check warning on line 99 in clients/azuredevopsrepo/contributors.go

View check run for this annotation

Codecov / codecov/patch

clients/azuredevopsrepo/contributors.go#L99

Added line #L99 was not covered by tests
}
130 changes: 130 additions & 0 deletions clients/azuredevopsrepo/contributors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2024 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 azuredevopsrepo

import (
"context"
"sync"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"

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

func Test_listContributors(t *testing.T) {
t.Parallel()
tests := []struct {
name string
getCommits fnGetCommits
wantContribs []clients.User
wantErr bool
}{
{
name: "no commits",
getCommits: func(ctx context.Context, args git.GetCommitsArgs) (*[]git.GitCommitRef, error) {
return &[]git.GitCommitRef{}, nil
},
wantContribs: nil,
wantErr: false,
},
{
name: "single contributor",
getCommits: func(ctx context.Context, args git.GetCommitsArgs) (*[]git.GitCommitRef, error) {
if *args.SearchCriteria.Skip == 0 {
return &[]git.GitCommitRef{
{
Author: &git.GitUserDate{
Email: toPtr("[email protected]"),
},
},
}, nil
} else {
return &[]git.GitCommitRef{}, nil
}
},
wantContribs: []clients.User{
{
Login: "[email protected]",
Companies: []string{"testOrg"},
NumContributions: 1,
},
},
wantErr: false,
},
{
name: "multiple contributors",
getCommits: func(ctx context.Context, args git.GetCommitsArgs) (*[]git.GitCommitRef, error) {
if *args.SearchCriteria.Skip == 0 {
return &[]git.GitCommitRef{
{
Author: &git.GitUserDate{
Email: toPtr("[email protected]"),
},
},
{
Author: &git.GitUserDate{
Email: toPtr("[email protected]"),
},
},
{
Author: &git.GitUserDate{
Email: toPtr("[email protected]"),
},
},
}, nil
} else {
return &[]git.GitCommitRef{}, nil
}
},
wantContribs: []clients.User{
{
Login: "[email protected]",
Companies: []string{"testOrg"},
NumContributions: 1,
},
{
Login: "[email protected]",
Companies: []string{"testOrg"},
NumContributions: 2,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := contributorsHandler{
ctx: context.Background(),
once: new(sync.Once),
repourl: &Repo{
organization: "testOrg",
},
getCommits: tt.getCommits,
}
err := c.setup()
if (err != nil) != tt.wantErr {
t.Errorf("contributorsHandler.setup() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := cmp.Diff(tt.wantContribs, c.contributors, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
t.Errorf("contributorsHandler.setup() mismatch (-want +got):\n%s", diff)
}
})
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/google/uuid v1.6.0
github.com/google/wire v0.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
Expand Down
Loading