tools/version-tracker/pkg/git/git.go (136 lines of code) (raw):
package git
import (
"fmt"
"io"
"os"
"strings"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"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/transport/http"
"github.com/aws/eks-anywhere-build-tooling/tools/version-tracker/pkg/constants"
"github.com/aws/eks-anywhere-build-tooling/tools/version-tracker/pkg/util/logger"
)
// CloneRepo clones the remote repository to a destination folder and creates a Git remote.
func CloneRepo(cloneURL, destination, headRepoOwner, branch string) (*git.Repository, string, error) {
logger.V(6).Info(fmt.Sprintf("Cloning repository [%s] to %s directory", cloneURL, destination))
progress := io.Discard
if logger.Verbosity >= 6 {
progress = os.Stdout
}
repo, err := git.PlainClone(destination, false, &git.CloneOptions{
URL: cloneURL,
Progress: progress,
})
if err != nil {
if err == git.ErrRepositoryAlreadyExists {
logger.V(6).Info(fmt.Sprintf("Repo already exists at %s", destination))
repo, err = git.PlainOpen(destination)
if err != nil {
return nil, "", fmt.Errorf("opening repo from %s directory: %v", destination, err)
}
} else {
return nil, "", fmt.Errorf("cloning repo %s to %s directory: %v", cloneURL, destination, err)
}
}
repoHeadCommit, err := repo.ResolveRevision(plumbing.Revision(fmt.Sprintf(constants.BaseRepoHeadRevisionPattern, branch)))
if err != nil {
return nil, "", fmt.Errorf("resolving revision [%s] to commit hash: %v", fmt.Sprintf(constants.BaseRepoHeadRevisionPattern, branch), err)
}
repoHeadCommitHash := strings.Split(repoHeadCommit.String(), " ")[0]
if headRepoOwner != "" {
_, err = repo.CreateRemote(&config.RemoteConfig{
Name: headRepoOwner,
URLs: []string{fmt.Sprintf("https://github.com/%s/%s.git", headRepoOwner, constants.BuildToolingRepoName)},
})
if err != nil {
if err == git.ErrRemoteExists {
logger.V(6).Info(fmt.Sprintf("Remote %s already exists", headRepoOwner))
} else {
return nil, "", fmt.Errorf("creating remote %s: %v", headRepoOwner, err)
}
}
}
return repo, repoHeadCommitHash, nil
}
// ResetToHEAD hard-resets the current working tree to point to the HEAD commit of the base repository.
func ResetToHEAD(worktree *git.Worktree, baseRepoHeadCommit string) error {
err := worktree.Reset(&git.ResetOptions{
Commit: plumbing.NewHash(baseRepoHeadCommit),
Mode: git.HardReset,
})
if err != nil {
return fmt.Errorf("resetting to origin HEAD commit %s: %v", baseRepoHeadCommit, err)
}
return nil
}
// Checkout checks out the working tree at the given branch, creating a new branch if necessary.
func Checkout(worktree *git.Worktree, branch string, create bool) error {
logger.V(6).Info(fmt.Sprintf("Checking out branch [%s] in local worktree", branch))
err := worktree.Checkout(&git.CheckoutOptions{
Branch: plumbing.NewBranchReferenceName(branch),
Keep: true,
Create: create,
})
if err != nil {
return fmt.Errorf("checking out branch [%s]: %v", branch, err)
}
return nil
}
// Add adds the given paths to the Git index.
func Add(worktree *git.Worktree, paths []string) error {
logger.V(6).Info("Adding updated files to index")
for _, path := range paths {
_, err := worktree.Add(path)
if err != nil {
return fmt.Errorf("adding file [%s] to the index: %v", path, err)
}
}
return nil
}
// Commit creates a new commit with the given commit message.
func Commit(worktree *git.Worktree, commitMessage string) error {
logger.V(6).Info("Committing file(s) in the index")
var commitAuthorName, commitAuthorEmail string
commitAuthorName, ok := os.LookupEnv(constants.CommitAuthorNameEnvvar)
if !ok {
commitAuthorName = constants.DefaultCommitAuthorName
}
commitAuthorEmail, ok = os.LookupEnv(constants.CommitAuthorEmailEnvvar)
if !ok {
commitAuthorEmail = constants.DefaultCommitAuthorEmail
}
_, err := worktree.Commit(commitMessage, &git.CommitOptions{
Author: &object.Signature{
Name: commitAuthorName,
Email: commitAuthorEmail,
When: time.Now(),
},
})
if err != nil {
return fmt.Errorf("committing file(s) in the index: %v", err)
}
return nil
}
// Push pushes changes to the given remote branch on GitHub.
func Push(repo *git.Repository, headRepoOwner, branch, githubToken string) error {
logger.V(6).Info(fmt.Sprintf("Pushing changes to remote [%s]", headRepoOwner))
progress := io.Discard
if logger.Verbosity >= 6 {
progress = os.Stdout
}
err := repo.Push(&git.PushOptions{
RemoteName: headRepoOwner,
RefSpecs: []config.RefSpec{config.RefSpec("+refs/*:refs/*")},
Auth: &http.BasicAuth{
Username: headRepoOwner,
Password: githubToken,
},
Progress: progress,
Force: true,
})
if err != nil {
if err == git.NoErrAlreadyUpToDate {
logger.V(6).Info(fmt.Sprintf("Destination branch [%s] on remote [%s] is already up-to-date", branch, headRepoOwner))
return nil
}
return fmt.Errorf("pushing changes to remote %s: %v", headRepoOwner, err)
}
return nil
}