cb080a2066
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
179 lines
5.3 KiB
Go
179 lines
5.3 KiB
Go
/*
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package repo
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/go-git/go-billy/v5"
|
|
"github.com/go-git/go-billy/v5/osfs"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/go-git/go-git/v5/plumbing/cache"
|
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
|
"github.com/go-git/go-git/v5/storage"
|
|
"github.com/go-git/go-git/v5/storage/filesystem"
|
|
|
|
"opendev.org/airship/airshipctl/pkg/log"
|
|
"opendev.org/airship/airshipctl/pkg/util"
|
|
)
|
|
|
|
// OptionsBuilder interface provides specification for a repository implementation
|
|
type OptionsBuilder interface {
|
|
ToAuth() (transport.AuthMethod, error)
|
|
ToCloneOptions(auth transport.AuthMethod) *git.CloneOptions
|
|
ToCheckoutOptions() *git.CheckoutOptions
|
|
ToFetchOptions(auth transport.AuthMethod) *git.FetchOptions
|
|
URL() string
|
|
}
|
|
|
|
// Repository container holds Filesystem, spec and open repository object
|
|
// Abstracts git repository and allows for easy cloning, checkout and update of git repos
|
|
type Repository struct {
|
|
Driver Adapter
|
|
OptionsBuilder
|
|
Name string
|
|
}
|
|
|
|
// NewRepository create repository object, with real filesystem on disk
|
|
// basePath is used to calculate final path where to clone/open the repository
|
|
func NewRepository(basePath string, builder OptionsBuilder) (*Repository, error) {
|
|
dirName := util.GitDirNameFromURL(builder.URL())
|
|
if dirName == "" {
|
|
return nil, ErrParseURL{URL: builder.URL()}
|
|
}
|
|
fs := osfs.New(filepath.Join(basePath, dirName))
|
|
|
|
s, err := storerFromFs(fs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// This can create
|
|
return &Repository{
|
|
Name: dirName,
|
|
Driver: NewGitDriver(fs, s),
|
|
OptionsBuilder: builder,
|
|
}, nil
|
|
}
|
|
|
|
func storerFromFs(fs billy.Filesystem) (storage.Storer, error) {
|
|
dot, err := fs.Chroot(".git")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return filesystem.NewStorage(dot, cache.NewObjectLRUDefault()), nil
|
|
}
|
|
|
|
// Update fetches new refs, and checkout according to checkout options
|
|
func (repo *Repository) Update() error {
|
|
log.Debugf("Updating repository %s", repo.Name)
|
|
if !repo.Driver.IsOpen() {
|
|
return ErrNoOpenRepo{}
|
|
}
|
|
auth, err := repo.ToAuth()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = repo.Driver.Fetch(repo.ToFetchOptions(auth))
|
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
|
return fmt.Errorf("failed to fetch refs for repository %v: %w", repo.Name, err)
|
|
}
|
|
return repo.Checkout()
|
|
}
|
|
|
|
// Checkout git repository, ToCheckoutOptions method will be used go get CheckoutOptions
|
|
func (repo *Repository) Checkout() error {
|
|
if !repo.Driver.IsOpen() {
|
|
return ErrNoOpenRepo{}
|
|
}
|
|
co := repo.ToCheckoutOptions()
|
|
var branchHash string
|
|
if co.Hash == plumbing.ZeroHash {
|
|
branchHash = fmt.Sprintf("branch %s", co.Branch.String())
|
|
} else {
|
|
branchHash = fmt.Sprintf("commit hash %s", co.Hash.String())
|
|
}
|
|
log.Debugf("Attempting to checkout the repository %s from %s", repo.Name, branchHash)
|
|
tree, err := repo.Driver.Worktree()
|
|
if err != nil {
|
|
return fmt.Errorf("could not get worktree from the repo, %w", err)
|
|
}
|
|
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)
|
|
return repo.Driver.Open()
|
|
}
|
|
|
|
// Clone given repository
|
|
func (repo *Repository) Clone() error {
|
|
log.Debugf("Attempting to clone the repository %s from %s", repo.Name, repo.URL())
|
|
auth, err := repo.ToAuth()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build auth options for repository %v: %w", repo.Name, err)
|
|
}
|
|
|
|
return repo.Driver.Clone(repo.ToCloneOptions(auth))
|
|
}
|
|
|
|
// Download will clone and checkout repository based on auth and checkout fields of the Repository object
|
|
// If repository is already cloned, it will be opened and checked out to configured hash,branch,tag etc...
|
|
// no remotes will be modified in this case, also no refs will be updated.
|
|
// enforce parameter is used to simulate git reset --hard option.
|
|
// If you want to enforce state of the repository, please delete current git repository before downloading.
|
|
func (repo *Repository) Download(noCheckout bool) error {
|
|
log.Debugf("Attempting to download the repository %s", repo.Name)
|
|
|
|
if !repo.Driver.IsOpen() {
|
|
err := repo.Clone()
|
|
if err == git.ErrRepositoryAlreadyExists {
|
|
openErr := repo.Open()
|
|
if openErr != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|