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 {
|
||||
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
|
||||
|
@ -22,8 +22,6 @@ import (
|
||||
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/errors"
|
||||
)
|
||||
|
||||
// ContextOptions holds all configurable options for context
|
||||
@ -44,7 +42,6 @@ type ManifestOptions struct {
|
||||
Branch string
|
||||
CommitHash string
|
||||
Tag string
|
||||
RemoteRef string
|
||||
Force bool
|
||||
IsPhase bool
|
||||
TargetPath string
|
||||
@ -152,9 +149,6 @@ func (o *ManifestOptions) Validate() error {
|
||||
if o.Name == "" {
|
||||
return ErrMissingManifestName{}
|
||||
}
|
||||
if o.RemoteRef != "" {
|
||||
return errors.ErrNotImplemented{What: "repository checkout by RemoteRef"}
|
||||
}
|
||||
if o.IsPhase && o.RepoName == "" {
|
||||
return ErrMissingRepositoryName{}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"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/transport"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
@ -49,6 +50,8 @@ type Repository struct {
|
||||
Auth *RepoAuth `json:"auth,omitempty"`
|
||||
// CheckoutOptions holds options to checkout repository
|
||||
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
|
||||
@ -77,17 +80,27 @@ type RepoCheckout struct {
|
||||
Branch string `json:"branch"`
|
||||
// Tag is the tag name to checkout
|
||||
Tag string `json:"tag"`
|
||||
// RemoteRef is not supported currently TODO
|
||||
// RemoteRef is used for remote checkouts such as gerrit change requests/github pull request
|
||||
// Ref is the ref to checkout
|
||||
// for example refs/changes/04/691202/5
|
||||
// TODO Add support for fetching remote refs
|
||||
RemoteRef string `json:"remoteRef,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
// ForceCheckout is a boolean to indicate whether to use the `--force` option when checking out
|
||||
ForceCheckout bool `json:"force"`
|
||||
// LocalBranch is a boolean to indicate whether the Branch is local one. False by default
|
||||
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
|
||||
|
||||
func (c *RepoCheckout) String() string {
|
||||
@ -102,7 +115,7 @@ func (c *RepoCheckout) String() string {
|
||||
// repository checkout and returns Error for incorrect values
|
||||
// returns nil when there are no errors
|
||||
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
|
||||
for _, val := range possibleValues {
|
||||
if val != "" {
|
||||
@ -112,8 +125,14 @@ func (c *RepoCheckout) Validate() error {
|
||||
if count > 1 {
|
||||
return ErrMutuallyExclusiveCheckout{}
|
||||
}
|
||||
if c.RemoteRef != "" {
|
||||
return errors.ErrNotImplemented{What: "repository checkout by RemoteRef"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@ -238,6 +257,8 @@ func (repo *Repository) ToCheckoutOptions() *git.CheckoutOptions {
|
||||
co.Branch = plumbing.NewTagReferenceName(repo.CheckoutOptions.Tag)
|
||||
case repo.CheckoutOptions.CommitHash != "":
|
||||
co.Hash = plumbing.NewHash(repo.CheckoutOptions.CommitHash)
|
||||
case repo.CheckoutOptions.Ref != "":
|
||||
co.Branch = plumbing.ReferenceName(repo.CheckoutOptions.Ref)
|
||||
}
|
||||
}
|
||||
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
|
||||
// FetchOptions describes how a fetch should be performed
|
||||
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
|
||||
|
@ -31,7 +31,6 @@ func Pull(cfgFactory config.Factory, noCheckout bool) error {
|
||||
}
|
||||
|
||||
func cloneRepositories(cfg *config.Config, noCheckout bool) error {
|
||||
// Clone main repository
|
||||
currentManifest, err := cfg.CurrentContextManifest()
|
||||
log.Debugf("Reading current context manifest information from %s", cfg.LoadedConfigPath())
|
||||
if err != nil {
|
||||
@ -39,17 +38,17 @@ func cloneRepositories(cfg *config.Config, noCheckout bool) error {
|
||||
}
|
||||
|
||||
// Clone repositories
|
||||
for repoName, extraRepoConfig := range currentManifest.Repositories {
|
||||
err := extraRepoConfig.Validate()
|
||||
for repoName, repoConfig := range currentManifest.Repositories {
|
||||
err := repoConfig.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repository, err := repo.NewRepository(currentManifest.GetTargetPath(), extraRepoConfig)
|
||||
repository, err := repo.NewRepository(currentManifest.GetTargetPath(), repoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -115,6 +115,19 @@ func (repo *Repository) Checkout() error {
|
||||
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
|
||||
func (repo *Repository) Open() error {
|
||||
log.Debugf("Attempting to open repository %s", repo.Name)
|
||||
@ -155,5 +168,11 @@ func (repo *Repository) Download(noCheckout bool) error {
|
||||
if noCheckout {
|
||||
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()
|
||||
}
|
||||
|
@ -63,7 +63,8 @@ func TestDownload(t *testing.T) {
|
||||
CloneOptions: &git.CloneOptions{
|
||||
URL: fx.DotGit().Root(),
|
||||
},
|
||||
URLString: fx.DotGit().Root(),
|
||||
FetchOptions: &git.FetchOptions{Auth: nil},
|
||||
URLString: fx.DotGit().Root(),
|
||||
}
|
||||
|
||||
fs := memfs.New()
|
||||
|
Loading…
Reference in New Issue
Block a user