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
111 changes: 91 additions & 20 deletions vcsclient/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -1069,9 +1069,10 @@ func (client *GitHubClient) CreateBranch(ctx context.Context, owner, repository,
return err
}

var sourceBranchRef *github.Branch
var sourceBranchRef *github.Reference
err = client.runWithRateLimitRetries(func() (*github.Response, error) {
sourceBranchRef, _, err = client.ghClient.Repositories.GetBranch(ctx, owner, repository, sourceBranch, 3)
sourceBranch = vcsutils.AddBranchPrefix(sourceBranch)
sourceBranchRef, _, err = client.ghClient.Git.GetRef(ctx, owner, repository, sourceBranch)
if err != nil {
return nil, err
}
Expand All @@ -1081,7 +1082,15 @@ func (client *GitHubClient) CreateBranch(ctx context.Context, owner, repository,
return err
}

latestCommitSHA := sourceBranchRef.Commit.SHA
if sourceBranchRef == nil {
return fmt.Errorf("failed to get reference for source branch %s", sourceBranch)
}
if sourceBranchRef.Object == nil {
return fmt.Errorf("source branch %s reference object is nil", sourceBranch)
}

latestCommitSHA := sourceBranchRef.Object.SHA
newBranch = vcsutils.AddBranchPrefix(newBranch)
ref := &github.Reference{
Ref: github.String("refs/heads/" + newBranch),
Object: &github.GitObject{SHA: latestCommitSHA},
Expand Down Expand Up @@ -1280,6 +1289,54 @@ func (client *GitHubClient) CommitAndPushFiles(
return errors.New("no files provided to commit")
}

if len(files) == 1 {
client.logger.Debug("Using Contents API for single file commit")
return client.commitSingleFile(ctx, owner, repo, sourceBranch, files[0], commitMessage, authorName, authorEmail)
}

client.logger.Debug("Using Git API for ", len(files), " file commit")
return client.commitMultipleFiles(ctx, owner, repo, sourceBranch, files, commitMessage, authorName, authorEmail)
}

func (client *GitHubClient) commitSingleFile(
ctx context.Context,
owner, repo, branch string,
file FileToCommit,
commitMessage, authorName, authorEmail string,
) error {
encodedContent := base64Utils.StdEncoding.EncodeToString([]byte(file.Content))

fileOptions := &github.RepositoryContentFileOptions{
Message: &commitMessage,
Content: []byte(encodedContent),
Branch: &branch,
Author: &github.CommitAuthor{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so both for author and committer we are using author?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Git, there are two distinct roles for a commit:

  1. Author - The person who originally wrote the code/content
  2. Committer - The person who actually committed the code to the repository

While these are often the same person (as in your code example where both use the same values), they serve different purposes:

  • The author represents who created the content
  • The committer represents who added it to the repository

You need both because:

  1. Git's data model tracks these separately
  2. In collaborative workflows, they might be different people (e.g., someone submits code that another person reviews and commits)
  3. Setting both ensures proper attribution in the commit history
  4. It maintains consistency with standard Git behavior

Even when they're the same person, explicitly setting both fields ensures predictable behavior when interacting with GitHub's API.

Name: &authorName,
Email: &authorEmail,
},
Committer: &github.CommitAuthor{
Name: &authorName,
Email: &authorEmail,
},
}

err := client.runWithRateLimitRetries(func() (*github.Response, error) {
_, ghResponse, err := client.ghClient.Repositories.CreateFile(ctx, owner, repo, file.Path, fileOptions)
return ghResponse, err
})

if err != nil {
return fmt.Errorf("failed to commit single file %s: %w", file.Path, err)
}
return nil
}

func (client *GitHubClient) commitMultipleFiles(
ctx context.Context,
owner, repo, sourceBranch string,
files []FileToCommit,
commitMessage, authorName, authorEmail string,
) error {
ref, _, err := client.ghClient.Git.GetRef(ctx, owner, repo, "refs/heads/"+sourceBranch)
if err != nil {
return fmt.Errorf("failed to get branch ref: %w", err)
Expand All @@ -1290,23 +1347,9 @@ func (client *GitHubClient) CommitAndPushFiles(
return fmt.Errorf("failed to get parent commit: %w", err)
}

var treeEntries []*github.TreeEntry
for _, file := range files {
blob, _, err := client.ghClient.Git.CreateBlob(ctx, owner, repo, &github.Blob{
Content: github.String(file.Content),
Encoding: github.String("utf-8"),
})
if err != nil {
return fmt.Errorf("failed to create blob for %s: %w", file.Path, err)
}

// Add each file to the treeEntries
treeEntries = append(treeEntries, &github.TreeEntry{
Path: github.String(file.Path),
Mode: github.String(regularFileCode),
Type: github.String("blob"),
SHA: blob.SHA,
})
treeEntries, err := client.createBlobs(ctx, owner, repo, files)
if err != nil {
return err
}

tree, _, err := client.ghClient.Git.CreateTree(ctx, owner, repo, *parentCommit.Tree.SHA, treeEntries)
Expand Down Expand Up @@ -1338,6 +1381,34 @@ func (client *GitHubClient) CommitAndPushFiles(
return nil
}

func (client *GitHubClient) createBlobs(ctx context.Context, owner, repo string, files []FileToCommit) ([]*github.TreeEntry, error) {
var treeEntries []*github.TreeEntry
for _, file := range files {
var blob *github.Blob
err := client.runWithRateLimitRetries(func() (*github.Response, error) {
var ghResponse *github.Response
var err error
blob, ghResponse, err = client.ghClient.Git.CreateBlob(ctx, owner, repo, &github.Blob{
Content: github.String(file.Content),
Encoding: github.String("utf-8"),
})
return ghResponse, err
})
if err != nil {
return nil, fmt.Errorf("failed to create blob for %s: %w", file.Path, err)
}

treeEntries = append(treeEntries, &github.TreeEntry{
Path: github.String(file.Path),
Mode: github.String(regularFileCode),
Type: github.String("blob"),
SHA: blob.SHA,
})
}

return treeEntries, nil
}

func (client *GitHubClient) MergePullRequest(ctx context.Context, owner, repo string, prNumber int, commitMessage string) error {
err := client.runWithRateLimitRetries(func() (*github.Response, error) {
_, resp, err := client.ghClient.PullRequests.Merge(ctx, owner, repo, prNumber, commitMessage, nil)
Expand Down
12 changes: 7 additions & 5 deletions vcsclient/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1033,12 +1033,13 @@ func TestGitHubClient_DeletePullRequestComment(t *testing.T) {

func TestGitHubClient_CreateBranch(t *testing.T) {
ctx := context.Background()
branchResponse := github.Branch{
Name: github.String("master"),
Commit: &github.RepositoryCommit{},
Protected: nil,
refResponse := github.Reference{
Ref: github.String("refs/heads/master"),
Object: &github.GitObject{
SHA: github.String("abc123abc123abc123abc123abc123abc123abcd"),
},
}
client, cleanUp := createServerAndClient(t, vcsutils.GitHub, false, branchResponse, "", createGitHubHandlerWithoutExpectedURI)
client, cleanUp := createServerAndClient(t, vcsutils.GitHub, false, refResponse, "", createGitHubHandlerWithoutExpectedURI)
defer cleanUp()
err := client.CreateBranch(ctx, owner, repo1, "master", "BranchForTest")
assert.NoError(t, err)
Expand Down Expand Up @@ -1093,6 +1094,7 @@ func TestGitHubClient_CommitAndPushFiles(t *testing.T) {
sourceBranch := "feature-branch"
filesToCommit := []FileToCommit{
{Path: "example.txt", Content: "example content"},
{Path: "example2.txt", Content: "example content 2"},
}
expectedResponses := map[string]mockGitHubResponse{
"/repos/jfrog/repo-1/git/ref/heads/feature-branch": {
Expand Down
Loading