Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add git branch support, non-linear tag support and simplify git repo logic #1404

Merged
merged 16 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions examples/git-data/zarf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,38 @@ metadata:
description: "Demo Zarf loading resources into a gitops service"

components:
- name: full-repo
- name: flux-demo
required: true
images:
- ghcr.io/stefanprodan/podinfo:6.0.0
repos:
# Do a full Git Repo Mirror
# Do a full Git Repo Mirror
Racer159 marked this conversation as resolved.
Show resolved Hide resolved
- https://github.com/stefanprodan/podinfo.git
# Clone an azure repo that breaks in go-git and has to fall back to the host git
- https://[email protected]/me0515/zarf-public-test/_git/zarf-public-test

- name: full-repo
required: true
repos:
# Do a full Git Repo Mirror
- https://github.com/kelseyhightower/nocode.git

- name: specific-tag
required: true
repos:
# Do a tag-provided Git Repo mirror
# Do a tag-provided Git Repo mirror
- https://github.com/defenseunicorns/[email protected]
# Use the git refspec pattern to get a tag
- https://github.com/defenseunicorns/zarf.git@refs/tags/v0.16.0

- name: specific-branch
required: true
repos:
# Do a branch-provided Git Repo mirror
- "https://repo1.dso.mil/big-bang/bigbang.git@refs/heads/release-1.53.x"

- name: specific-hash
required: true
repos:
# Do a commit hash Git Repo mirror
# Do a commit hash Git Repo mirror
- https://github.com/defenseunicorns/zarf.git@c74e2e9626da0400e0a41e78319b3054c53a5d4e
# Clone an azure repo (w/SHA) that breaks in go-git and has to fall back to the host git
# Clone an azure repo (w/SHA) that breaks in go-git and has to fall back to the host git
- https://[email protected]/me0515/zarf-public-test/_git/zarf-public-test@524980951ff16e19dc25232e9aea8fd693989ba6
8 changes: 4 additions & 4 deletions src/internal/cluster/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,16 @@ func ServiceInfoFromServiceURL(serviceURL string) *ServiceInfo {

// Match hostname against local cluster service format.
pattern := regexp.MustCompile(serviceURLPattern)
matches := pattern.FindStringSubmatch(parsedURL.Hostname())
get, err := utils.MatchRegex(pattern, parsedURL.Hostname())

// If incomplete match, return an error.
if len(matches) != 3 {
if err != nil {
return nil
Racer159 marked this conversation as resolved.
Show resolved Hide resolved
}

return &ServiceInfo{
Namespace: matches[pattern.SubexpIndex("namespace")],
Name: matches[pattern.SubexpIndex("name")],
Namespace: get("namespace"),
Name: get("name"),
Port: remotePort,
}
}
Expand Down
20 changes: 6 additions & 14 deletions src/internal/packager/git/checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,19 @@ import (
)

// CheckoutTag performs a `git checkout` of the provided tag to a detached HEAD.
func (g *Git) CheckoutTag(tag string) {
message.Debugf("git.CheckoutTag(%s)", tag)

func (g *Git) CheckoutTag(tag string) error {
options := &git.CheckoutOptions{
Branch: plumbing.ReferenceName("refs/tags/" + tag),
Branch: g.parseRef(tag),
}
g.checkout(options)
return g.checkout(options)
}

func (g *Git) checkoutRefAsBranch(ref string, branch plumbing.ReferenceName) error {
var err error

if isHash(ref) {
err = g.checkoutHashAsBranch(plumbing.NewHash(ref), branch)
} else {
err = g.checkoutTagAsBranch(ref, branch)
if plumbing.IsHash(ref) {
return g.checkoutHashAsBranch(plumbing.NewHash(ref), branch)
}

return err
return g.checkoutTagAsBranch(ref, branch)
}

// checkoutTagAsBranch performs a `git checkout` of the provided tag but rather
Expand Down Expand Up @@ -60,8 +54,6 @@ func (g *Git) checkoutTagAsBranch(tag string, branch plumbing.ReferenceName) err
func (g *Git) checkoutHashAsBranch(hash plumbing.Hash, branch plumbing.ReferenceName) error {
message.Debugf("git.checkoutHasAsBranch(%s,%s)", hash.String(), branch.String())

_ = g.deleteBranchIfExists(branch)

repo, err := git.PlainOpen(g.GitPath)
if err != nil {
message.Fatal(err, "Not a valid git repo or unable to open")
Expand Down
90 changes: 57 additions & 33 deletions src/internal/packager/git/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,91 @@ package git

import (
"context"
"errors"

"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
"github.com/go-git/go-git/v5"
goConfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
)

// clone performs a `git clone` of a given repo.
func (g *Git) clone(gitDirectory string, gitURL string, onlyFetchRef bool) (*git.Repository, error) {
func (g *Git) clone(gitURL string, ref plumbing.ReferenceName) error {
cloneOptions := &git.CloneOptions{
URL: gitURL,
Progress: g.Spinner,
RemoteName: onlineRemoteName,
}

if onlyFetchRef {
// Don't clone all tags if we're cloning a specific tag.
if ref.IsTag() {
cloneOptions.Tags = git.NoTags
cloneOptions.ReferenceName = ref
}

gitCred := utils.FindAuthForHost(gitURL)
// Use a single branch if we're cloning a specific branch.
if ref.IsBranch() {
cloneOptions.SingleBranch = true
cloneOptions.ReferenceName = ref
}

// Gracefully handle no git creds on the system (like our CI/CD)
// Setup git credentials if we have them, ignore if we don't.
gitCred := utils.FindAuthForHost(gitURL)
if gitCred.Auth.Username != "" {
cloneOptions.Auth = &gitCred.Auth
}

// Clone the given repo
repo, err := git.PlainClone(gitDirectory, false, cloneOptions)

if errors.Is(err, git.ErrRepositoryAlreadyExists) {
repo, err = git.PlainOpen(gitDirectory)
// Clone the given repo.
repo, err := git.PlainClone(g.GitPath, false, cloneOptions)
if err != nil {
message.Debugf("Failed to clone repo %s: %s", gitURL, err.Error())
return g.gitCloneFallback(gitURL, ref)
}

if err != nil {
return nil, err
// If we're cloning the whole repo or a commit hash, we need to also fetch the other branches besides the default.
if ref == emptyRef {
fetchOpts := &git.FetchOptions{
RemoteName: onlineRemoteName,
Progress: g.Spinner,
RefSpecs: []goConfig.RefSpec{"refs/*:refs/*", "HEAD:refs/heads/HEAD"},
Tags: git.AllTags,
}
if err := repo.Fetch(fetchOpts); err != nil {
return err
}
}

return repo, git.ErrRepositoryAlreadyExists
} else if err != nil {
message.Debugf("Failed to clone repo %s: %s", gitURL, err.Error())
g.Spinner.Updatef("Falling back to host git for %s", gitURL)
return nil
}

// If we can't clone with go-git, fallback to the host clone
// Only support "all tags" due to the azure clone url format including a username
cmdArgs := []string{"clone", "--origin", onlineRemoteName, gitURL, gitDirectory}
// gitCloneFallback is a fallback if go-git fails to clone a repo.
func (g *Git) gitCloneFallback(gitURL string, ref plumbing.ReferenceName) error {
g.Spinner.Updatef("Falling back to host git for %s", gitURL)

if onlyFetchRef {
cmdArgs = append(cmdArgs, "--no-tags")
}
// If we can't clone with go-git, fallback to the host clone
// Only support "all tags" due to the azure clone url format including a username
cmdArgs := []string{"clone", "--origin", onlineRemoteName, gitURL, g.GitPath}

execConfig := exec.Config{
Stdout: g.Spinner,
Stderr: g.Spinner,
}
_, _, err := exec.CmdWithContext(context.TODO(), execConfig, "git", cmdArgs...)
if err != nil {
return nil, err
}
// Don't clone all tags if we're cloning a specific tag.
if ref.IsTag() {
cmdArgs = append(cmdArgs, "--no-tags")
}

return git.PlainOpen(gitDirectory)
} else {
return repo, nil
// Use a single branch if we're cloning a specific branch.
if ref.IsBranch() {
cmdArgs = append(cmdArgs, "-b", ref.String())
cmdArgs = append(cmdArgs, "--single-branch")
}

execConfig := exec.Config{
Stdout: g.Spinner,
Stderr: g.Spinner,
}
_, _, err := exec.CmdWithContext(context.TODO(), execConfig, "git", cmdArgs...)
if err != nil {
return err
}

return nil
}
27 changes: 18 additions & 9 deletions src/internal/packager/git/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,27 @@
package git

import (
"regexp"
"fmt"
"strings"

"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/types"
"github.com/go-git/go-git/v5/plumbing"
)

// Git is the main struct for managing git repositories.
type Git struct {
// Server is the git server configuration.
Server types.GitServerInfo

// Spinner is an optional spinner to use for long running operations.
Spinner *message.Spinner

// Target working directory for the git repository
// Target working directory for the git repository.
GitPath string
}

const onlineRemoteName = "online-upstream"
const offlineRemoteName = "offline-downstream"
const onlineRemoteRefPrefix = "refs/remotes/" + onlineRemoteName + "/"

// isHash checks if a string is a valid git hash.
// https://regex101.com/r/jm9bdk/1
var isHash = regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString
const emptyRef = ""

// New creates a new git instance with the provided server config.
func New(server types.GitServerInfo) *Git {
Expand All @@ -43,3 +41,14 @@ func NewWithSpinner(server types.GitServerInfo, spinner *message.Spinner) *Git {
Spinner: spinner,
}
}

// parseRef parses the provided ref into a ReferenceName if it's not a hash.
func (g *Git) parseRef(r string) plumbing.ReferenceName {
// If not a full ref, assume it's a tag at this point.
if !plumbing.IsHash(r) && !strings.HasPrefix(r, "refs/") {
r = fmt.Sprintf("refs/tags/%s", r)
}

// Set the reference name to the provided ref.
return plumbing.ReferenceName(r)
}
99 changes: 0 additions & 99 deletions src/internal/packager/git/fetch.go

This file was deleted.

Loading