pkg/client/git/client.go (213 lines of code) (raw):
package git
import (
"crypto/sha1"
"fmt"
"net/url"
"os"
"os/exec"
"path"
"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/google/uuid"
"github.com/pkg/errors"
)
type Client struct {
username, password string
workingDir string
gerritBaseURL string
}
type User struct {
Name string
Email string
}
func New(gerritBaseURL, workingDir, username, password string) *Client {
return &Client{
workingDir: workingDir,
username: username,
password: password,
gerritBaseURL: gerritBaseURL,
}
}
func (c *Client) GerritBaseURL() string {
return c.gerritBaseURL
}
func (c *Client) projectPath(projectName string) string {
return path.Join(c.workingDir, projectName)
}
func (c *Client) SetFileContents(projectName, filePath, contents string) error {
projectPath := c.projectPath(projectName)
filePath = path.Join(projectPath, filePath)
fp, err := os.Create(filePath)
if err != nil {
return errors.Wrapf(err, "unable to create file: %s", filePath)
}
if _, err := fp.WriteString(contents); err != nil {
return errors.Wrapf(err, "unable to put file contents, file: %s", filePath)
}
if err := fp.Close(); err != nil {
return errors.Wrapf(err, "unable to close file: %s", filePath)
}
return nil
}
func (c *Client) RemoveFile(projectName, filePath string) (bool, error) {
projectPath := c.projectPath(projectName)
filePath = path.Join(projectPath, filePath)
err := os.Remove(filePath)
if err != nil {
if !os.IsNotExist(err) {
return false, errors.Wrapf(err, "unable to remove file: %s", filePath)
}
return false, nil
}
return true, nil
}
func (c *Client) Clone(projectName string) (projectPath string, err error) {
projectPath = c.projectPath(projectName)
_, err = git.PlainClone(
projectPath, false, &git.CloneOptions{
Auth: &http.BasicAuth{
Username: c.username,
Password: c.password,
},
URL: fmt.Sprintf("%s/%s", c.gerritBaseURL, projectName),
})
if err != nil {
return "", errors.Wrap(err, "unable to clone repository")
}
return
}
func (c *Client) Merge(projectName, sourceBranch, targetBranch string, options ...string) error {
projectDir := c.projectPath(projectName)
cmd := exec.Command("git", "checkout", targetBranch)
cmd.Dir = projectDir
bts, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, string(bts))
}
mergeOpts := []string{"merge", sourceBranch}
mergeOpts = append(mergeOpts, options...)
cmd = exec.Command("git", mergeOpts...)
cmd.Dir = projectDir
bts, err = cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, string(bts))
}
return nil
}
func (c *Client) Commit(projectName, message string, files []string, user *User) error {
projectPath := c.projectPath(projectName)
r, err := git.PlainOpen(projectPath)
if err != nil {
return errors.Wrap(err, "unable to open repository")
}
w, err := r.Worktree()
if err != nil {
return errors.Wrap(err, "unable to get repo worktree")
}
for _, f := range files {
if _, err := w.Add(f); err != nil {
return errors.Wrapf(err, "unable to add file: %s", f)
}
}
if _, err := w.Commit(message, &git.CommitOptions{
Author: &object.Signature{Name: user.Name, Email: user.Email, When: time.Now()},
}); err != nil {
return errors.Wrap(err, "unable to perform git commit")
}
return nil
}
func (c *Client) SetProjectUser(projectName string, user *User) error {
r, err := git.PlainOpen(c.projectPath(projectName))
if err != nil {
return errors.Wrap(err, "unable to open repository")
}
repoConf, err := r.Config()
if err != nil {
return errors.Wrap(err, "unable to get repo config")
}
repoConf.User = struct {
Name string
Email string
}{Name: user.Name, Email: user.Email}
if err := r.SetConfig(repoConf); err != nil {
return errors.Wrap(err, "unable to set project user")
}
return nil
}
func (c *Client) CheckoutBranch(projectName, branch string) error {
projectPath := c.projectPath(projectName)
r, err := git.PlainOpen(projectPath)
if err != nil {
return errors.Wrap(err, "unable to open repository")
}
w, err := r.Worktree()
if err != nil {
return errors.Wrap(err, "unable to get repo worktree")
}
if err := w.Checkout(&git.CheckoutOptions{Branch: plumbing.NewBranchReferenceName(branch)}); err != nil {
return errors.Wrapf(err, "unable to checkout to branch: %s", branch)
}
return nil
}
func (c *Client) Push(projectName, remote string, refSpecs ...string) (pushOutput string, retErr error) {
projectPath := c.projectPath(projectName)
r, err := git.PlainOpen(projectPath)
if err != nil {
return "", errors.Wrap(err, "")
}
unsecureRemoteName := fmt.Sprintf("unsecure_%s", remote)
_, err = c.createRemoteWithCredential(r, remote, unsecureRemoteName)
if err != nil {
return "", errors.Wrap(err, "unable to create new remote")
}
defer func() {
if err = r.DeleteRemote(unsecureRemoteName); err != nil {
retErr = errors.Wrap(err, "unable to delete tmp remote")
}
}()
pushArgs := []string{"push", unsecureRemoteName}
pushArgs = append(pushArgs, refSpecs...)
cmd := exec.Command("git", pushArgs...)
cmd.Dir = projectPath
bts, err := cmd.CombinedOutput()
if err != nil {
return "", errors.Wrap(err, string(bts))
}
return string(bts), nil
}
func (c *Client) createRemoteWithCredential(repo *git.Repository, baseRemoteName, newRemoteName string) (*git.Remote, error) {
origin, err := repo.Remote(baseRemoteName)
if err != nil {
return nil, errors.Wrap(err, "unable to get origin remote")
}
if len(origin.Config().URLs) == 0 {
return nil, errors.New("remote does not have valid urls")
}
originURL, err := url.Parse(origin.Config().URLs[0])
if err != nil {
return nil, errors.Wrap(err, "unable to parse origin url")
}
originURL.User = url.UserPassword(c.username, c.password)
newRemote, err := repo.CreateRemote(&config.RemoteConfig{
Name: newRemoteName, URLs: []string{originURL.String()},
})
if err != nil {
return nil, errors.Wrap(err, "unable to create remote")
}
return newRemote, nil
}
func (*Client) GenerateChangeID() (string, error) {
changeID, err := uuid.NewRandom()
if err != nil {
return "", fmt.Errorf("failed to generate uuid, %w", err)
}
h := sha1.New()
if _, err := h.Write([]byte(changeID.String())); err != nil {
return "", fmt.Errorf("failed to write to hash: %w", err)
}
return fmt.Sprintf("I%x", h.Sum(nil)), nil
}