Skip to content

Commit

Permalink
backend: Extending GitHub service to have the ability to get files ba…
Browse files Browse the repository at this point in the history
…sed on a path (#3012)
  • Loading branch information
jdslaugh authored May 20, 2024
1 parent 9a47d9a commit 5fd778a
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
4 changes: 4 additions & 0 deletions backend/mock/service/githubmock/githubmock.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func (s svc) GetFile(ctx context.Context, ref *github.RemoteRef, path string) (*
panic("implement me")
}

func (s svc) GetDirectory(ctx context.Context, ref *github.RemoteRef, path string) (*github.Directory, error) {
panic("implement me")
}

func (s svc) CreateBranch(ctx context.Context, req *github.CreateBranchRequest) error {
panic("implement me")
}
Expand Down
54 changes: 54 additions & 0 deletions backend/service/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,22 @@ type File struct {
LastModifiedSHA string
}

type Entry struct {
Name string
Type string
}

type Directory struct {
Path string
LastModifiedTime time.Time
LastModifiedSHA string
Entries []*Entry
}

// Client allows various interactions with remote repositories on GitHub.
type Client interface {
GetFile(ctx context.Context, ref *RemoteRef, path string) (*File, error)
GetDirectory(ctx context.Context, ref *RemoteRef, path string) (*Directory, error)
CreateBranch(ctx context.Context, req *CreateBranchRequest) error
CreatePullRequest(ctx context.Context, ref *RemoteRef, base, title, body string) (*PullRequestInfo, error)
CreateRepository(ctx context.Context, req *sourcecontrolv1.CreateRepositoryRequest) (*sourcecontrolv1.CreateRepositoryResponse, error)
Expand Down Expand Up @@ -469,6 +482,47 @@ func (s *svc) GetFile(ctx context.Context, ref *RemoteRef, path string) (*File,
return f, nil
}

func (s *svc) GetDirectory(ctx context.Context, ref *RemoteRef, path string) (*Directory, error) {
q := &getDirectoryQuery{}

params := map[string]interface{}{
"owner": githubv4.String(ref.RepoOwner),
"name": githubv4.String(ref.RepoName),
"path": githubv4.String(path),
"ref": githubv4.String(ref.Ref),
"refPath": githubv4.String(fmt.Sprintf("%s:%s", ref.Ref, path)),
}

err := s.graphQL.Query(ctx, q, params)
if err != nil {
return nil, err
}

switch {
case q.Repository.Ref.Commit.ID == nil:
return nil, errors.New("ref not found")
case len(q.Repository.Object.Tree.Entries) == 0:
return nil, errors.New("directory not found")
}

var entries []*Entry
for _, obj := range q.Repository.Object.Tree.Entries {
entries = append(entries, &Entry{
Name: string(obj.Name),
Type: string(obj.Type),
})
}

d := &Directory{
Path: path,
Entries: entries,
LastModifiedTime: q.Repository.Ref.Commit.History.Nodes[0].CommittedDate.Time,
LastModifiedSHA: string(q.Repository.Ref.Commit.History.Nodes[0].OID),
}

return d, nil
}

/*
* Rather than calling GetCommit() multiple times, we can use CompareCommits to get a range of commits
*/
Expand Down
105 changes: 105 additions & 0 deletions backend/service/github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ type getfileMock struct {
truncated, binary bool
}

type getdirectoryMock struct {
v4client

queryError bool
refID, objID string
entries []*Entry
}

func (g *getfileMock) Query(ctx context.Context, query interface{}, variables map[string]interface{}) error {
q, ok := query.(*getFileQuery)
if !ok {
Expand Down Expand Up @@ -76,6 +84,40 @@ func (g *getfileMock) Query(ctx context.Context, query interface{}, variables ma
return nil
}

func (g *getdirectoryMock) Query(ctx context.Context, query interface{}, variables map[string]interface{}) error {
q, ok := query.(*getDirectoryQuery)
if !ok {
panic("not a query")
}

if g.queryError {
return errors.New(problem)
}

if g.refID != "" {
q.Repository.Ref.Commit.ID = g.refID
q.Repository.Ref.Commit.OID = githubv4.GitObjectID(g.refID)
}
if g.objID != "" {
q.Repository.Object.Tree.Entries = append(
q.Repository.Object.Tree.Entries,
struct {
Name githubv4.String
Type githubv4.String
}{Name: githubv4.String(g.entries[0].Name), Type: githubv4.String(g.entries[0].Type)})
}

q.Repository.Ref.Commit.History.Nodes = append(
q.Repository.Ref.Commit.History.Nodes,
struct {
CommittedDate githubv4.DateTime
OID githubv4.GitObjectID
}{githubv4.DateTime{Time: timestamp}, "otherSHA"},
)

return nil
}

type mockRoundTripper struct {
http.RoundTripper
resp *http.Response
Expand Down Expand Up @@ -235,6 +277,69 @@ func TestGetFile(t *testing.T) {
}
}

var directoryEntries = []*Entry{{Name: "foo", Type: "blob"}}

var getDirectoryTests = []struct {
name string
v4 getdirectoryMock
errText string
}{
{
name: "queryError",
v4: getdirectoryMock{queryError: true},
errText: problem,
},
{
name: "noRef",
v4: getdirectoryMock{},
errText: "ref not found",
},
{
name: "noObject",
v4: getdirectoryMock{refID: "abcdef12345"},
errText: "directory not found",
},
{
name: "happyPath",
v4: getdirectoryMock{refID: "abcdef12345", objID: "abcdef12345", entries: directoryEntries},
},
}

func TestGetDirectory(t *testing.T) {
for _, tt := range getDirectoryTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
a := assert.New(t)

s := &svc{graphQL: &tt.v4}
f, err := s.GetDirectory(context.Background(),
&RemoteRef{
RepoOwner: "owner",
RepoName: "myRepo",
Ref: "master",
},
"data/foo",
)

if tt.errText != "" {
a.Error(err)
a.Contains(err.Error(), tt.errText)
return
}
if err != nil {
a.FailNow("unexpected error")
return
}

a.Equal("data/foo", f.Path)
a.Equal(directoryEntries, f.Entries)
a.Equal("otherSHA", f.LastModifiedSHA)
a.Equal(timestamp, f.LastModifiedTime)
})
}
}

type mockRepositories struct {
actualOrg string
actualRepo *githubv3.Repository
Expand Down
28 changes: 28 additions & 0 deletions backend/service/github/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,34 @@ type getFileQuery struct {
} `graphql:"repository(owner:$owner,name:$name)"`
}

type getDirectoryQuery struct {
Repository struct {
// Get more information about desired ref and last modified ref for the file.
Ref struct {
Commit struct {
ID githubv4.ID
OID githubv4.GitObjectID
History struct {
Nodes []struct {
CommittedDate githubv4.DateTime
OID githubv4.GitObjectID
}
} `graphql:"history(path:$path,first:1)"`
} `graphql:"... on Commit"`
} `graphql:"ref: object(expression:$ref)"`

// Fetch requested tree and its entries.
Object struct {
Tree struct {
Entries []struct {
Name githubv4.String
Type githubv4.String
} `graphql:"entries"`
} `graphql:"... on Tree"`
} `graphql:"object(expression:$refPath)"`
} `graphql:"repository(owner:$owner,name:$name)"`
}

type getRepositoryQuery struct {
Repository struct {
DefaultBranchRef struct {
Expand Down

0 comments on commit 5fd778a

Please sign in to comment.