cmd/hub/git/git.go (120 lines of code) (raw):
// Copyright (c) 2022 EPAM Systems, Inc.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package git
import (
"fmt"
"log"
"github.com/epam/hubctl/cmd/hub/util"
goGit "github.com/go-git/go-git/v5"
goGitConfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
)
func cloneErrMsgFormat(remoteUrl, ref, dir string, err error) error {
return fmt.Errorf("unable to clone git repo %s at `%s` into `%s`: %v", remoteUrl, ref, dir, err)
}
func Clone(remoteUrl, ref, dir string) error {
reference, err := findRemoteReference(remoteUrl, ref)
if err != nil {
return cloneErrMsgFormat(remoteUrl, ref, dir, err)
}
_, err = goGit.PlainClone(dir, false, &goGit.CloneOptions{
URL: remoteUrl,
ReferenceName: reference.Name(),
SingleBranch: true,
Progress: log.Default().Writer(),
Tags: goGit.NoTags,
RecurseSubmodules: goGit.NoRecurseSubmodules,
})
if err != nil {
return cloneErrMsgFormat(remoteUrl, ref, dir, err)
}
return nil
}
const remoteName = "origin"
func pullErrMsgFormat(dir string, err error) error {
return fmt.Errorf("unable to pull git repo in `%s` directory: %v", dir, err)
}
func Pull(targetRef, dir string) error {
repo, err := goGit.PlainOpen(dir)
if err != nil {
return pullErrMsgFormat(dir, err)
}
reference, err := repo.Head()
if err != nil {
return pullErrMsgFormat(dir, err)
}
if targetRef == "" {
refs, err := repo.References()
if err != nil {
return pullErrMsgFormat(dir, err)
}
defer refs.Close()
for ref, err := refs.Next(); err == nil; {
if ref.Hash() == reference.Hash() {
reference = ref
break
}
}
} else {
remote, err := repo.Remote(remoteName)
if err != nil {
return pullErrMsgFormat(dir, err)
}
remoteUrl := remote.Config().URLs[0]
reference, err = findRemoteReference(remoteUrl, targetRef)
if err != nil {
return pullErrMsgFormat(dir, err)
}
}
worktree, err := repo.Worktree()
if err != nil {
return pullErrMsgFormat(dir, err)
}
err = worktree.Pull(&goGit.PullOptions{
RemoteName: remoteName,
ReferenceName: reference.Name(),
SingleBranch: true,
Progress: log.Default().Writer(),
RecurseSubmodules: goGit.NoRecurseSubmodules,
})
if err != nil {
if err == goGit.NoErrAlreadyUpToDate {
log.Print(err)
return nil
} else {
return pullErrMsgFormat(dir, err)
}
}
return nil
}
const (
refPrefix = "refs/"
refHeadPrefix = refPrefix + "heads/"
refTagPrefix = refPrefix + "tags/"
)
var refPrefixes = []string{refHeadPrefix, refTagPrefix}
var refFindOrder = []string{
refHeadPrefix + "%s",
refTagPrefix + "%s",
}
func findRemoteReference(remoteUrl, targetRef string) (*plumbing.Reference, error) {
remote := goGit.NewRemote(memory.NewStorage(), &goGitConfig.RemoteConfig{
Name: remoteName,
URLs: []string{remoteUrl},
})
refs, err := remote.List(&goGit.ListOptions{})
if err != nil {
return nil, fmt.Errorf("unable to get ref list of `%s` remote: %v", remoteUrl, err)
}
for _, refNameFormat := range refFindOrder {
for _, ref := range refs {
name := ref.Name().String()
if util.ContainsPrefix(refPrefixes, name) {
targetName := fmt.Sprintf(refNameFormat, targetRef)
if (util.ContainsPrefix(refPrefixes, targetRef) && targetRef == name) || targetName == name {
return ref, nil
}
}
}
}
return nil, fmt.Errorf("unable to find git ref `%s` in `%s` remote", targetRef, remoteUrl)
}