Implement fetching remote refs for document pull
This commit adds the `fetch` and `fetch.remoteRefSpec` fields to the configuration for repositories, allowing the capability to fetch references from a remote. This is useful for pulling specific gerrit patchsets. This also changes `checkout.remoteRefSpec` to `checkout.refSpec`, and implements the logic required to checkout to an arbitrary ref. Closes: #616 Change-Id: Ie21a6c2a7a7ac92ed3c05fef7e5683203cd62e45
This commit is contained in:
parent
7974e041c5
commit
cb080a2066
@ -67,7 +67,7 @@ type ErrMutuallyExclusiveCheckout struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrMutuallyExclusiveCheckout) Error() string {
|
func (e ErrMutuallyExclusiveCheckout) Error() string {
|
||||||
return "Checkout mutually exclusive, use either: commit-hash, branch or tag."
|
return "Checkout mutually exclusive, use either: commit-hash, branch, tag, or ref."
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrRepositoryNotFound is returned if repository is empty
|
// ErrRepositoryNotFound is returned if repository is empty
|
||||||
|
@ -22,8 +22,6 @@ import (
|
|||||||
|
|
||||||
"k8s.io/cli-runtime/pkg/printers"
|
"k8s.io/cli-runtime/pkg/printers"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContextOptions holds all configurable options for context
|
// ContextOptions holds all configurable options for context
|
||||||
@ -44,7 +42,6 @@ type ManifestOptions struct {
|
|||||||
Branch string
|
Branch string
|
||||||
CommitHash string
|
CommitHash string
|
||||||
Tag string
|
Tag string
|
||||||
RemoteRef string
|
|
||||||
Force bool
|
Force bool
|
||||||
IsPhase bool
|
IsPhase bool
|
||||||
TargetPath string
|
TargetPath string
|
||||||
@ -152,9 +149,6 @@ func (o *ManifestOptions) Validate() error {
|
|||||||
if o.Name == "" {
|
if o.Name == "" {
|
||||||
return ErrMissingManifestName{}
|
return ErrMissingManifestName{}
|
||||||
}
|
}
|
||||||
if o.RemoteRef != "" {
|
|
||||||
return errors.ErrNotImplemented{What: "repository checkout by RemoteRef"}
|
|
||||||
}
|
|
||||||
if o.IsPhase && o.RepoName == "" {
|
if o.IsPhase && o.RepoName == "" {
|
||||||
return ErrMissingRepositoryName{}
|
return ErrMissingRepositoryName{}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
|
gitconfig "github.com/go-git/go-git/v5/config"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||||
@ -49,6 +50,8 @@ type Repository struct {
|
|||||||
Auth *RepoAuth `json:"auth,omitempty"`
|
Auth *RepoAuth `json:"auth,omitempty"`
|
||||||
// CheckoutOptions holds options to checkout repository
|
// CheckoutOptions holds options to checkout repository
|
||||||
CheckoutOptions *RepoCheckout `json:"checkout,omitempty"`
|
CheckoutOptions *RepoCheckout `json:"checkout,omitempty"`
|
||||||
|
// FetchOptions holds options for fetching remote refs
|
||||||
|
FetchOptions *RepoFetch `json:"fetch,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoAuth struct describes method of authentication against given repository
|
// RepoAuth struct describes method of authentication against given repository
|
||||||
@ -77,17 +80,27 @@ type RepoCheckout struct {
|
|||||||
Branch string `json:"branch"`
|
Branch string `json:"branch"`
|
||||||
// Tag is the tag name to checkout
|
// Tag is the tag name to checkout
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
// RemoteRef is not supported currently TODO
|
// Ref is the ref to checkout
|
||||||
// RemoteRef is used for remote checkouts such as gerrit change requests/github pull request
|
|
||||||
// for example refs/changes/04/691202/5
|
// for example refs/changes/04/691202/5
|
||||||
// TODO Add support for fetching remote refs
|
Ref string `json:"ref,omitempty"`
|
||||||
RemoteRef string `json:"remoteRef,omitempty"`
|
|
||||||
// ForceCheckout is a boolean to indicate whether to use the `--force` option when checking out
|
// ForceCheckout is a boolean to indicate whether to use the `--force` option when checking out
|
||||||
ForceCheckout bool `json:"force"`
|
ForceCheckout bool `json:"force"`
|
||||||
// LocalBranch is a boolean to indicate whether the Branch is local one. False by default
|
// LocalBranch is a boolean to indicate whether the Branch is local one. False by default
|
||||||
LocalBranch bool `json:"localBranch"`
|
LocalBranch bool `json:"localBranch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RepoFetch holds information on which remote ref to fetch
|
||||||
|
type RepoFetch struct {
|
||||||
|
// RemoteRefSpec is used for remote fetches such as gerrit change
|
||||||
|
// requests and github pull requests. The format of the refspec is an
|
||||||
|
// optional +, followed by <src>:<dst>, where <src> is the pattern for
|
||||||
|
// references on the remote side and <dst> is where those references
|
||||||
|
// will be written locally. The + tells Git to update the reference
|
||||||
|
// even if it isn't a fast-forward.
|
||||||
|
// eg.: refs/changes/04/691202/5:refs/changes/04/691202/5
|
||||||
|
RemoteRefSpec string `json:"remoteRefSpec,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// RepoCheckout methods
|
// RepoCheckout methods
|
||||||
|
|
||||||
func (c *RepoCheckout) String() string {
|
func (c *RepoCheckout) String() string {
|
||||||
@ -102,7 +115,7 @@ func (c *RepoCheckout) String() string {
|
|||||||
// repository checkout and returns Error for incorrect values
|
// repository checkout and returns Error for incorrect values
|
||||||
// returns nil when there are no errors
|
// returns nil when there are no errors
|
||||||
func (c *RepoCheckout) Validate() error {
|
func (c *RepoCheckout) Validate() error {
|
||||||
possibleValues := []string{c.CommitHash, c.Branch, c.Tag, c.RemoteRef}
|
possibleValues := []string{c.CommitHash, c.Branch, c.Tag, c.Ref}
|
||||||
var count int
|
var count int
|
||||||
for _, val := range possibleValues {
|
for _, val := range possibleValues {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
@ -112,8 +125,14 @@ func (c *RepoCheckout) Validate() error {
|
|||||||
if count > 1 {
|
if count > 1 {
|
||||||
return ErrMutuallyExclusiveCheckout{}
|
return ErrMutuallyExclusiveCheckout{}
|
||||||
}
|
}
|
||||||
if c.RemoteRef != "" {
|
return nil
|
||||||
return errors.ErrNotImplemented{What: "repository checkout by RemoteRef"}
|
}
|
||||||
|
|
||||||
|
// Validate verifies that the remote refspec is valid. If a remote refspec was
|
||||||
|
// not supplied, Validate does nothing.
|
||||||
|
func (rf *RepoFetch) Validate() error {
|
||||||
|
if rf.RemoteRefSpec != "" {
|
||||||
|
return gitconfig.RefSpec(rf.RemoteRefSpec).Validate()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -238,6 +257,8 @@ func (repo *Repository) ToCheckoutOptions() *git.CheckoutOptions {
|
|||||||
co.Branch = plumbing.NewTagReferenceName(repo.CheckoutOptions.Tag)
|
co.Branch = plumbing.NewTagReferenceName(repo.CheckoutOptions.Tag)
|
||||||
case repo.CheckoutOptions.CommitHash != "":
|
case repo.CheckoutOptions.CommitHash != "":
|
||||||
co.Hash = plumbing.NewHash(repo.CheckoutOptions.CommitHash)
|
co.Hash = plumbing.NewHash(repo.CheckoutOptions.CommitHash)
|
||||||
|
case repo.CheckoutOptions.Ref != "":
|
||||||
|
co.Branch = plumbing.ReferenceName(repo.CheckoutOptions.Ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return co
|
return co
|
||||||
@ -257,7 +278,14 @@ func (repo *Repository) ToCloneOptions(auth transport.AuthMethod) *git.CloneOpti
|
|||||||
// ToFetchOptions returns an instance of git.FetchOptions for given authentication
|
// ToFetchOptions returns an instance of git.FetchOptions for given authentication
|
||||||
// FetchOptions describes how a fetch should be performed
|
// FetchOptions describes how a fetch should be performed
|
||||||
func (repo *Repository) ToFetchOptions(auth transport.AuthMethod) *git.FetchOptions {
|
func (repo *Repository) ToFetchOptions(auth transport.AuthMethod) *git.FetchOptions {
|
||||||
return &git.FetchOptions{Auth: auth}
|
var refSpecs []gitconfig.RefSpec
|
||||||
|
if repo.FetchOptions != nil && repo.FetchOptions.RemoteRefSpec != "" {
|
||||||
|
refSpecs = []gitconfig.RefSpec{gitconfig.RefSpec(repo.FetchOptions.RemoteRefSpec)}
|
||||||
|
}
|
||||||
|
return &git.FetchOptions{
|
||||||
|
Auth: auth,
|
||||||
|
RefSpecs: refSpecs,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL returns the repository URL in a string format
|
// URL returns the repository URL in a string format
|
||||||
|
@ -31,7 +31,6 @@ func Pull(cfgFactory config.Factory, noCheckout bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cloneRepositories(cfg *config.Config, noCheckout bool) error {
|
func cloneRepositories(cfg *config.Config, noCheckout bool) error {
|
||||||
// Clone main repository
|
|
||||||
currentManifest, err := cfg.CurrentContextManifest()
|
currentManifest, err := cfg.CurrentContextManifest()
|
||||||
log.Debugf("Reading current context manifest information from %s", cfg.LoadedConfigPath())
|
log.Debugf("Reading current context manifest information from %s", cfg.LoadedConfigPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -39,17 +38,17 @@ func cloneRepositories(cfg *config.Config, noCheckout bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clone repositories
|
// Clone repositories
|
||||||
for repoName, extraRepoConfig := range currentManifest.Repositories {
|
for repoName, repoConfig := range currentManifest.Repositories {
|
||||||
err := extraRepoConfig.Validate()
|
err := repoConfig.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
repository, err := repo.NewRepository(currentManifest.GetTargetPath(), extraRepoConfig)
|
repository, err := repo.NewRepository(currentManifest.GetTargetPath(), repoConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Downloading %s repository %s from %s into %s",
|
log.Printf("Downloading %s repository %s from %s into %s",
|
||||||
repoName, repository.Name, extraRepoConfig.URL(), currentManifest.GetTargetPath())
|
repoName, repository.Name, repoConfig.URL(), currentManifest.GetTargetPath())
|
||||||
err = repository.Download(noCheckout)
|
err = repository.Download(noCheckout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -115,6 +115,19 @@ func (repo *Repository) Checkout() error {
|
|||||||
return tree.Checkout(co)
|
return tree.Checkout(co)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch fetches remote refs
|
||||||
|
func (repo *Repository) Fetch() error {
|
||||||
|
if !repo.Driver.IsOpen() {
|
||||||
|
return ErrNoOpenRepo{}
|
||||||
|
}
|
||||||
|
auth, err := repo.ToAuth()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build auth options for repository %v: %w", repo.Name, err)
|
||||||
|
}
|
||||||
|
fo := repo.ToFetchOptions(auth)
|
||||||
|
return repo.Driver.Fetch(fo)
|
||||||
|
}
|
||||||
|
|
||||||
// Open the repository
|
// Open the repository
|
||||||
func (repo *Repository) Open() error {
|
func (repo *Repository) Open() error {
|
||||||
log.Debugf("Attempting to open repository %s", repo.Name)
|
log.Debugf("Attempting to open repository %s", repo.Name)
|
||||||
@ -155,5 +168,11 @@ func (repo *Repository) Download(noCheckout bool) error {
|
|||||||
if noCheckout {
|
if noCheckout {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := repo.Fetch()
|
||||||
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||||
|
return fmt.Errorf("failed to fetch refs for repository %v: %w", repo.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
return repo.Checkout()
|
return repo.Checkout()
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,8 @@ func TestDownload(t *testing.T) {
|
|||||||
CloneOptions: &git.CloneOptions{
|
CloneOptions: &git.CloneOptions{
|
||||||
URL: fx.DotGit().Root(),
|
URL: fx.DotGit().Root(),
|
||||||
},
|
},
|
||||||
URLString: fx.DotGit().Root(),
|
FetchOptions: &git.FetchOptions{Auth: nil},
|
||||||
|
URLString: fx.DotGit().Root(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := memfs.New()
|
fs := memfs.New()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user