diff --git a/cmd/git-init/main.go b/cmd/git-init/main.go
index 5fa50a85c62..8f428f99ed6 100644
--- a/cmd/git-init/main.go
+++ b/cmd/git-init/main.go
@@ -28,16 +28,16 @@ import (
var (
fetchSpec git.FetchSpec
- submodules bool
terminationMessagePath string
)
func init() {
flag.StringVar(&fetchSpec.URL, "url", "", "Git origin URL to fetch")
flag.StringVar(&fetchSpec.Revision, "revision", "", "The Git revision to make the repository HEAD")
+ flag.StringVar(&fetchSpec.Refspec, "refspec", "", "The Git refspec to fetch the revision from (optional)")
flag.StringVar(&fetchSpec.Path, "path", "", "Path of directory under which Git repository will be copied")
flag.BoolVar(&fetchSpec.SSLVerify, "sslVerify", true, "Enable/Disable SSL verification in the git config")
- flag.BoolVar(&submodules, "submodules", true, "Initialize and fetch Git submodules")
+ flag.BoolVar(&fetchSpec.Submodules, "submodules", true, "Initialize and fetch Git submodules")
flag.UintVar(&fetchSpec.Depth, "depth", 1, "Perform a shallow clone to this depth")
flag.StringVar(&terminationMessagePath, "terminationMessagePath", "/tekton/termination", "Location of file containing termination message")
}
@@ -53,15 +53,10 @@ func main() {
if err := git.Fetch(logger, fetchSpec); err != nil {
logger.Fatalf("Error fetching git repository: %s", err)
}
- if submodules {
- if err := git.SubmoduleFetch(logger, fetchSpec.Path); err != nil {
- logger.Fatalf("Error initializing or fetching the git submodules")
- }
- }
- commit, err := git.Commit(logger, fetchSpec.Revision, fetchSpec.Path)
+ commit, err := git.ShowCommit(logger, "HEAD", fetchSpec.Path)
if err != nil {
- logger.Fatalf("Error parsing commit of git repository: %s", err)
+ logger.Fatalf("Error parsing revision %s of git repository: %s", fetchSpec.Revision, err)
}
resourceName := os.Getenv("TEKTON_RESOURCE_NAME")
output := []v1alpha1.PipelineResourceResult{
diff --git a/docs/resources.md b/docs/resources.md
index 7fba1ac1071..c786d0d357a 100644
--- a/docs/resources.md
+++ b/docs/resources.md
@@ -313,19 +313,53 @@ Params that can be added are the following:
change the repo, e.g. [to use a fork](#using-a-fork)
1. `revision`: Git [revision][git-rev] (branch, tag, commit SHA or ref) to
clone. You can use this to control what commit [or branch](#using-a-branch)
- is used. _If no revision is specified, the resource will default to `latest`
- from `master`._
+ is used. [git checkout][git-checkout] is used to switch to the
+ revision, and will result in a detached HEAD in most cases. Use refspec
+ along with revision if you want to checkout a particular branch without a
+ detached HEAD. _If no revision is specified, the resource will default to `master`._
+1. `refspec`: (Optional) specify a git [refspec][git-refspec] to pass to git-fetch.
+ Note that if this field is specified, it must specify all refs, branches, tags,
+ or commits required to checkout the specified `revision`. An additional fetch
+ will not be run to obtain the contents of the revision field. If no refspec
+ is specified, the value of the `revision` field will be fetched directly.
+ The refspec is useful in manipulating the repository in several cases:
+ * when the server does not support fetches via the commit SHA (i.e. does
+ not have `uploadpack.allowReachableSHA1InWant` enabled) and you want
+ to fetch and checkout a specific commit hash from a ref chain.
+ * when you want to fetch several other refs alongside your revision
+ (for instance, tags)
+ * when you want to checkout a specific branch, the revision and refspec
+ fields can work together to be able to set the destination of the incoming
+ branch and switch to the branch.
+
+ Examples:
+ - Check out a specified revision commit SHA1 after fetching ref (detached)
+ `revision`: cb17eba165fe7973ef9afec20e7c6971565bd72f
+ `refspec`: refs/smoke/myref
+ - Fetch all tags alongside refs/heads/master and switch to the master branch
+ (not detached)
+ `revision`: master
+ `refspec`: "refs/tags/\*:refs/tags/\* +refs/heads/master:refs/heads/master"
+ - Fetch the develop branch and switch to it (not detached)
+ `revision`: develop
+ `refspec`: refs/heads/develop:refs/heads/develop
+ - Fetch refs/pull/1009/head into the master branch and switch to it (not detached)
+ `revision`: master
+ `refspec`: refs/pull/1009/head:refs/heads/master
+
1. `submodules`: defines if the resource should initialize and fetch the
submodules, value is either `true` or `false`. _If not specified, this will
default to true_
1. `depth`: performs a [shallow clone][git-depth] where only the most recent
- commit(s) will be fetched. If set to `'0'`, all commits will be fetched. _If
- not specified, the default depth is 1._
+ commit(s) will be fetched. This setting also applies to submodules. If set to
+ `'0'`, all commits will be fetched. _If not specified, the default depth is 1._
1. `sslVerify`: defines if [http.sslVerify][git-http.sslVerify] should be set
to `true` or `false` in the global git config. _Defaults to `true` if
omitted._
[git-rev]: https://git-scm.com/docs/gitrevisions#_specifying_revisions
+[git-checkout]: https://git-scm.com/docs/git-checkout
+[git-refspec]: https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
[git-depth]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---depthltdepthgt
[git-http.sslVerify]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslVerify
diff --git a/pkg/apis/pipeline/v1alpha1/pipeline_resource_types.go b/pkg/apis/pipeline/v1alpha1/pipeline_resource_types.go
index 7ad12816ec9..14945cdfa90 100644
--- a/pkg/apis/pipeline/v1alpha1/pipeline_resource_types.go
+++ b/pkg/apis/pipeline/v1alpha1/pipeline_resource_types.go
@@ -30,7 +30,7 @@ var (
)
const (
- // PipelineResourceTypeGit indicates that this source is a GitHub repo.
+ // PipelineResourceTypeGit indicates that this source is a Git repo.
PipelineResourceTypeGit PipelineResourceType = resource.PipelineResourceTypeGit
// PipelineResourceTypeStorage indicates that this source is a storage blob resource.
diff --git a/pkg/apis/resource/v1alpha1/git/git_resource.go b/pkg/apis/resource/v1alpha1/git/git_resource.go
index 857f297e0b4..bd117216622 100644
--- a/pkg/apis/resource/v1alpha1/git/git_resource.go
+++ b/pkg/apis/resource/v1alpha1/git/git_resource.go
@@ -38,10 +38,10 @@ type Resource struct {
Name string `json:"name"`
Type resource.PipelineResourceType `json:"type"`
URL string `json:"url"`
- // Git revision (branch, tag, commit SHA or ref) to clone. See
- // https://git-scm.com/docs/gitrevisions#_specifying_revisions for more
- // information.
+ // Git revision (branch, tag, commit SHA) to clone, and optionally the refspec to fetch from.
+ //See https://git-scm.com/docs/gitrevisions#_specifying_revisions for more information.
Revision string `json:"revision"`
+ Refspec string `json:"refspec"`
Submodules bool `json:"submodules"`
Depth uint `json:"depth"`
@@ -71,6 +71,8 @@ func NewResource(gitImage string, r *resource.PipelineResource) (*Resource, erro
gitResource.URL = param.Value
case strings.EqualFold(param.Name, "Revision"):
gitResource.Revision = param.Value
+ case strings.EqualFold(param.Name, "Refspec"):
+ gitResource.Refspec = param.Value
case strings.EqualFold(param.Name, "Submodules"):
gitResource.Submodules = toBool(param.Value, true)
case strings.EqualFold(param.Name, "Depth"):
@@ -133,6 +135,8 @@ func (s *Resource) Replacements() map[string]string {
"type": s.Type,
"url": s.URL,
"revision": s.Revision,
+ "refspec": s.Refspec,
+ "submodules": strconv.FormatBool(s.Submodules),
"depth": strconv.FormatUint(uint64(s.Depth), 10),
"sslVerify": strconv.FormatBool(s.SSLVerify),
"httpProxy": s.HTTPProxy,
@@ -149,6 +153,9 @@ func (s *Resource) GetInputTaskModifier(_ *v1alpha1.TaskSpec, path string) (v1al
"-path", path,
}
+ if s.Refspec != "" {
+ args = append(args, "-refspec", s.Refspec)
+ }
if !s.Submodules {
args = append(args, "-submodules=false")
}
diff --git a/pkg/apis/resource/v1alpha1/git/git_resource_test.go b/pkg/apis/resource/v1alpha1/git/git_resource_test.go
index 3a5cf6731ef..79d065c47dc 100644
--- a/pkg/apis/resource/v1alpha1/git/git_resource_test.go
+++ b/pkg/apis/resource/v1alpha1/git/git_resource_test.go
@@ -52,6 +52,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 1,
@@ -72,6 +73,50 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
+ GitImage: "override-with-git:latest",
+ Submodules: true,
+ Depth: 1,
+ SSLVerify: true,
+ HTTPProxy: "",
+ HTTPSProxy: "",
+ NOProxy: "",
+ },
+ }, {
+ desc: "With Refspec",
+ pipelineResource: tb.PipelineResource("git-resource", "default",
+ tb.PipelineResourceSpec(resourcev1alpha1.PipelineResourceTypeGit,
+ tb.PipelineResourceSpecParam("URL", "git@github.com:test/test.git"),
+ tb.PipelineResourceSpecParam("Refspec", "refs/changes/22/222134"),
+ ),
+ ),
+ want: &git.Resource{
+ Name: "git-resource",
+ Type: resourcev1alpha1.PipelineResourceTypeGit,
+ URL: "git@github.com:test/test.git",
+ Revision: "master",
+ Refspec: "refs/changes/22/222134",
+ GitImage: "override-with-git:latest",
+ Submodules: true,
+ Depth: 1,
+ SSLVerify: true,
+ HTTPProxy: "",
+ HTTPSProxy: "",
+ NOProxy: "",
+ },
+ }, {
+ desc: "Without Refspec",
+ pipelineResource: tb.PipelineResource("git-resource", "default",
+ tb.PipelineResourceSpec(resourcev1alpha1.PipelineResourceTypeGit,
+ tb.PipelineResourceSpecParam("URL", "git@github.com:test/test.git"),
+ ),
+ ),
+ want: &git.Resource{
+ Name: "git-resource",
+ Type: resourcev1alpha1.PipelineResourceTypeGit,
+ URL: "git@github.com:test/test.git",
+ Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 1,
@@ -93,6 +138,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 1,
@@ -115,6 +161,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: false,
Depth: 1,
@@ -137,6 +184,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 8,
@@ -159,6 +207,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 0,
@@ -182,6 +231,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 0,
@@ -205,6 +255,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 0,
@@ -228,6 +279,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 0,
@@ -251,6 +303,7 @@ func TestNewGitResource_Valid(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "test",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 0,
@@ -279,6 +332,8 @@ func TestGitResource_Replacements(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
+ Submodules: false,
Depth: 16,
SSLVerify: false,
HTTPProxy: "http-proxy.git.com",
@@ -291,6 +346,8 @@ func TestGitResource_Replacements(t *testing.T) {
"type": string(resourcev1alpha1.PipelineResourceTypeGit),
"url": "git@github.com:test/test.git",
"revision": "master",
+ "refspec": "",
+ "submodules": "false",
"depth": "16",
"sslVerify": "false",
"httpProxy": "http-proxy.git.com",
@@ -319,6 +376,7 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 1,
@@ -354,6 +412,7 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: false,
Depth: 1,
@@ -390,6 +449,7 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: true,
Depth: 8,
@@ -427,6 +487,7 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: false,
Depth: 1,
@@ -464,6 +525,7 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: false,
Depth: 1,
@@ -499,6 +561,7 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: false,
Depth: 1,
@@ -534,6 +597,7 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
Type: resourcev1alpha1.PipelineResourceTypeGit,
URL: "git@github.com:test/test.git",
Revision: "master",
+ Refspec: "",
GitImage: "override-with-git:latest",
Submodules: false,
Depth: 1,
@@ -562,6 +626,45 @@ func TestGitResource_GetDownloadTaskModifier(t *testing.T) {
{Name: "HTTPS_PROXY", Value: "https-proxy.git.com"},
},
},
+ }, {
+ desc: "With Refspec",
+ gitResource: &git.Resource{
+ Name: "git-resource",
+ Type: resourcev1alpha1.PipelineResourceTypeGit,
+ URL: "git@github.com:test/test.git",
+ Revision: "master",
+ Refspec: "refs/tags/v1.0:refs/tags/v1.0 refs/heads/master:refs/heads/master",
+ GitImage: "override-with-git:latest",
+ Submodules: false,
+ Depth: 1,
+ SSLVerify: true,
+ HTTPProxy: "http-proxy.git.com",
+ HTTPSProxy: "https-proxy.git.com",
+ NOProxy: "no-proxy.git.com",
+ },
+ want: corev1.Container{
+ Name: "git-source-git-resource-l22wn",
+ Image: "override-with-git:latest",
+ Command: []string{"/ko-app/git-init"},
+ Args: []string{
+ "-url",
+ "git@github.com:test/test.git",
+ "-revision",
+ "master",
+ "-path",
+ "/test/test",
+ "-refspec",
+ "refs/tags/v1.0:refs/tags/v1.0 refs/heads/master:refs/heads/master",
+ "-submodules=false",
+ },
+ WorkingDir: "/workspace",
+ Env: []corev1.EnvVar{
+ {Name: "TEKTON_RESOURCE_NAME", Value: "git-resource"},
+ {Name: "HTTP_PROXY", Value: "http-proxy.git.com"},
+ {Name: "HTTPS_PROXY", Value: "https-proxy.git.com"},
+ {Name: "NO_PROXY", Value: "no-proxy.git.com"},
+ },
+ },
}} {
t.Run(tc.desc, func(t *testing.T) {
ts := pipelinev1alpha1.TaskSpec{}
diff --git a/pkg/git/git.go b/pkg/git/git.go
index bcf14cb5ee2..8bc5b97d9fd 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -48,15 +48,17 @@ func run(logger *zap.SugaredLogger, dir string, args ...string) (string, error)
type FetchSpec struct {
URL string
Revision string
+ Refspec string
Path string
Depth uint
+ Submodules bool
SSLVerify bool
HTTPProxy string
HTTPSProxy string
NOProxy string
}
-// Fetch fetches the specified git repository at the revision into path.
+// Fetch fetches the specified git repository at the revision into path, using the refspec to fetch if provided.
func Fetch(logger *zap.SugaredLogger, spec FetchSpec) error {
if err := ensureHomeEnv(logger); err != nil {
return err
@@ -84,53 +86,95 @@ func Fetch(logger *zap.SugaredLogger, spec FetchSpec) error {
return err
}
- fetchArgs := []string{"fetch", "--recurse-submodules=yes"}
+ fetchArgs := []string{"fetch"}
+ if spec.Submodules {
+ fetchArgs = append(fetchArgs, "--recurse-submodules=yes")
+ }
if spec.Depth > 0 {
fetchArgs = append(fetchArgs, fmt.Sprintf("--depth=%d", spec.Depth))
}
- fetchArgs = append(fetchArgs, "origin", spec.Revision)
- if _, err := run(logger, "", fetchArgs...); err != nil {
- // Fetch can fail if an old commitid was used so try git pull, performing regardless of error
- // as no guarantee that the same error is returned by all git servers gitlab, github etc...
- if _, err := run(logger, "", "pull", "--recurse-submodules=yes", "origin"); err != nil {
- logger.Warnf("Failed to pull origin : %s", err)
- }
- if _, err := run(logger, "", "checkout", spec.Revision); err != nil {
+ // Fetch the revision and verify with FETCH_HEAD
+ fetchParam := []string{spec.Revision}
+ checkoutParam := "FETCH_HEAD"
+
+ if spec.Refspec != "" {
+ // if refspec is specified, fetch the refspec and verify with provided revision
+ fetchParam = strings.Split(spec.Refspec, " ")
+ checkoutParam = spec.Revision
+ }
+
+ // git-init always creates and checks out an empty master branch. When the user requests
+ // "master" as the revision, git-fetch will refuse to update the HEAD of the branch it is
+ // currently on. The --update-head-ok parameter tells git-fetch that it is ok to update
+ // the current (empty) HEAD on initial fetch.
+ // The --force parameter tells git-fetch that its ok to update an existing HEAD in a
+ // non-fast-forward manner (though this cannot be possible on initial fetch, it can help
+ // when the refspec specifies the same destination twice)
+ fetchArgs = append(fetchArgs, "origin", "--update-head-ok", "--force")
+ fetchArgs = append(fetchArgs, fetchParam...)
+ if _, err := run(logger, spec.Path, fetchArgs...); err != nil {
+ return fmt.Errorf("failed to fetch %v: %v", fetchParam, err)
+ }
+ // After performing a fetch, verify that the item to checkout is actually valid
+ if _, err := ShowCommit(logger, checkoutParam, spec.Path); err != nil {
+ return fmt.Errorf("error parsing %s after fetching refspec %s", checkoutParam, spec.Refspec)
+ }
+
+ if _, err := run(logger, "", "checkout", "-f", checkoutParam); err != nil {
+ return err
+ }
+
+ commit, err := ShowCommit(logger, "HEAD", spec.Path)
+ if err != nil {
+ return err
+ }
+ ref, err := ShowRef(logger, "HEAD", spec.Path)
+ if err != nil {
+ return err
+ }
+ logger.Infof("Successfully cloned %s @ %s (%s) in path %s", trimmedURL, commit, ref, spec.Path)
+ if spec.Submodules {
+ if err := SubmoduleFetch(logger, spec); err != nil {
return err
}
- } else if _, err := run(logger, "", "reset", "--hard", "FETCH_HEAD"); err != nil {
- return err
}
- logger.Infof("Successfully cloned %s @ %s in path %s", trimmedURL, spec.Revision, spec.Path)
return nil
}
-func Commit(logger *zap.SugaredLogger, revision, path string) (string, error) {
- output, err := run(logger, path, "rev-parse", "HEAD")
+func ShowCommit(logger *zap.SugaredLogger, revision, path string) (string, error) {
+ output, err := run(logger, path, "show", "-q", "--pretty=format:%H", revision)
if err != nil {
return "", err
}
return strings.TrimSuffix(output, "\n"), nil
}
-func SubmoduleFetch(logger *zap.SugaredLogger, path string) error {
- if err := ensureHomeEnv(logger); err != nil {
- return err
+func ShowRef(logger *zap.SugaredLogger, revision, path string) (string, error) {
+ output, err := run(logger, path, "show", "-q", "--pretty=format:%D", revision)
+ if err != nil {
+ return "", err
}
+ return strings.TrimSuffix(output, "\n"), nil
+}
- if path != "" {
- if err := os.Chdir(path); err != nil {
- return fmt.Errorf("failed to change directory with path %s; err: %w", path, err)
+func SubmoduleFetch(logger *zap.SugaredLogger, spec FetchSpec) error {
+ if spec.Path != "" {
+ if err := os.Chdir(spec.Path); err != nil {
+ return fmt.Errorf("failed to change directory with path %s; err: %w", spec.Path, err)
}
}
if _, err := run(logger, "", "submodule", "init"); err != nil {
return err
}
- if _, err := run(logger, "", "submodule", "update", "--recursive"); err != nil {
+ updateArgs := []string{"submodule", "update", "--recursive"}
+ if spec.Depth > 0 {
+ updateArgs = append(updateArgs, "--depth", fmt.Sprintf("--depth=%d", spec.Depth))
+ }
+ if _, err := run(logger, "", updateArgs...); err != nil {
return err
}
- logger.Infof("Successfully initialized and updated submodules in path %s", path)
+ logger.Infof("Successfully initialized and updated submodules in path %s", spec.Path)
return nil
}
diff --git a/test/git_checkout_test.go b/test/git_checkout_test.go
index dc0779aec5b..52284a6df84 100644
--- a/test/git_checkout_test.go
+++ b/test/git_checkout_test.go
@@ -43,7 +43,7 @@ const (
func TestGitPipelineRun(t *testing.T) {
t.Parallel()
- revisions := []string{"master", "c15aced0e5aaee6456fbe6f7a7e95e0b5b3b2b2f", "c15aced", "release-0.1", "v0.1.0", "refs/pull/347/head"}
+ revisions := []string{"master", "c15aced0e5aaee6456fbe6f7a7e95e0b5b3b2b2f", "release-0.1", "v0.1.0", "refs/pull/347/head"}
for _, revision := range revisions {
@@ -53,7 +53,7 @@ func TestGitPipelineRun(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, revision, "true", "", "", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, revision, "", "true", "", "", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -80,6 +80,57 @@ func TestGitPipelineRun(t *testing.T) {
}
}
+// Test revision fetching with refspec specified
+func TestGitPipelineRunWithRefspec(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ description string
+ revision string
+ refspec string
+ }{{
+ description: "Fetch refs/tags/v0.1.0 alongside master and checkout the master branch",
+ revision: "master",
+ refspec: "refs/tags/v0.1.0:refs/tags/v0.1.0 refs/heads/master:refs/heads/master",
+ }, {
+ description: "Checkout specific revision from refs/pull/1009/head's commit chain",
+ revision: "968d5d37a61bfb85426c885dc1090c1cc4b33436",
+ refspec: "refs/pull/1009/head",
+ }, {
+ description: "Fetch refs/pull/1009/head into a named master branch and then check it out",
+ revision: "master",
+ refspec: "refs/pull/1009/head:refs/heads/master",
+ }} {
+ t.Run(tc.description, func(t *testing.T) {
+ c, namespace := setup(t)
+ knativetest.CleanupOnInterrupt(func() { tearDown(t, c, namespace) }, t.Logf)
+ defer tearDown(t, c, namespace)
+
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, tc.revision, tc.refspec, "true", "", "", "")); err != nil {
+ t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
+ }
+
+ if _, err := c.TaskClient.Create(getGitCheckTask(namespace)); err != nil {
+ t.Fatalf("Failed to create Task `%s`: %s", gitTestTaskName, err)
+ }
+
+ if _, err := c.PipelineClient.Create(getGitCheckPipeline(namespace)); err != nil {
+ t.Fatalf("Failed to create Pipeline `%s`: %s", gitTestPipelineName, err)
+ }
+
+ if _, err := c.PipelineRunClient.Create(getGitCheckPipelineRun(namespace)); err != nil {
+ t.Fatalf("Failed to create Pipeline `%s`: %s", gitTestPipelineRunName, err)
+ }
+
+ if err := WaitForPipelineRunState(c, gitTestPipelineRunName, timeout, PipelineRunSucceed(gitTestPipelineRunName), "PipelineRunCompleted"); err != nil {
+ t.Errorf("Error waiting for PipelineRun %s to finish: %s", gitTestPipelineRunName, err)
+ t.Fatalf("PipelineRun execution failed")
+ }
+
+ })
+ }
+}
+
// TestGitPipelineRun_Disable_SSLVerify will verify the source code is retrieved even after disabling SSL certificates (sslVerify)
func TestGitPipelineRun_Disable_SSLVerify(t *testing.T) {
t.Parallel()
@@ -89,7 +140,7 @@ func TestGitPipelineRun_Disable_SSLVerify(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "false", "", "", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "", "false", "", "", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -124,7 +175,7 @@ func TestGitPipelineRunFail(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "Idontexistrabbitmonkeydonkey", "true", "", "", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "Idontexistrabbitmonkeydonkey", "", "true", "", "", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -164,8 +215,7 @@ func TestGitPipelineRunFail(t *testing.T) {
t.Fatalf("Error getting pod logs for pod `%s` and container `%s` in namespace `%s`", tr.Status.PodName, stat.Name, namespace)
}
// Check for failure messages from fetch and pull in the log file
- if strings.Contains(strings.ToLower(string(logContent)), "couldn't find remote ref idontexistrabbitmonkeydonkey") &&
- strings.Contains(strings.ToLower(string(logContent)), "pathspec 'idontexistrabbitmonkeydonkey' did not match any file(s) known to git") {
+ if strings.Contains(strings.ToLower(string(logContent)), "couldn't find remote ref idontexistrabbitmonkeydonkey") {
t.Logf("Found exepected errors when retrieving non-existent git revision")
} else {
t.Logf("Container `%s` log File: %s", stat.Name, logContent)
@@ -192,7 +242,7 @@ func TestGitPipelineRunFail_HTTPS_PROXY(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "true", "", "invalid.https.proxy.com", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "", "true", "", "invalid.https.proxy.com", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -232,8 +282,7 @@ func TestGitPipelineRunFail_HTTPS_PROXY(t *testing.T) {
t.Fatalf("Error getting pod logs for pod `%s` and container `%s` in namespace `%s`", tr.Status.PodName, stat.Name, namespace)
}
// Check for failure messages from fetch and pull in the log file
- if strings.Contains(strings.ToLower(string(logContent)), "could not resolve proxy: invalid.https.proxy.com") &&
- strings.Contains(strings.ToLower(string(logContent)), "pathspec 'master' did not match any file(s) known to git") {
+ if strings.Contains(strings.ToLower(string(logContent)), "could not resolve proxy: invalid.https.proxy.com") {
t.Logf("Found exepected errors when using non-existent https proxy")
} else {
t.Logf("Container `%s` log File: %s", stat.Name, logContent)
@@ -250,11 +299,12 @@ func TestGitPipelineRunFail_HTTPS_PROXY(t *testing.T) {
}
}
-func getGitPipelineResource(namespace, revision, sslverify, httpproxy, httpsproxy, noproxy string) *v1alpha1.PipelineResource {
+func getGitPipelineResource(namespace, revision, refspec, sslverify, httpproxy, httpsproxy, noproxy string) *v1alpha1.PipelineResource {
return tb.PipelineResource(gitSourceResourceName, namespace, tb.PipelineResourceSpec(
v1alpha1.PipelineResourceTypeGit,
tb.PipelineResourceSpecParam("Url", "https://github.com/tektoncd/pipeline"),
tb.PipelineResourceSpecParam("Revision", revision),
+ tb.PipelineResourceSpecParam("Refspec", refspec),
tb.PipelineResourceSpecParam("sslVerify", sslverify),
tb.PipelineResourceSpecParam("httpProxy", httpproxy),
tb.PipelineResourceSpecParam("httpsProxy", httpsproxy),
diff --git a/test/v1alpha1/git_checkout_test.go b/test/v1alpha1/git_checkout_test.go
index c90444f716b..e906b18e7d4 100644
--- a/test/v1alpha1/git_checkout_test.go
+++ b/test/v1alpha1/git_checkout_test.go
@@ -41,7 +41,7 @@ const (
func TestGitPipelineRun(t *testing.T) {
t.Parallel()
- revisions := []string{"master", "c15aced0e5aaee6456fbe6f7a7e95e0b5b3b2b2f", "c15aced", "release-0.1", "v0.1.0", "refs/pull/347/head"}
+ revisions := []string{"master", "c15aced0e5aaee6456fbe6f7a7e95e0b5b3b2b2f", "release-0.1", "v0.1.0", "refs/pull/347/head"}
for _, revision := range revisions {
@@ -51,7 +51,7 @@ func TestGitPipelineRun(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, revision, "true", "", "", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, revision, "", "true", "", "", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -78,6 +78,57 @@ func TestGitPipelineRun(t *testing.T) {
}
}
+// Test revision fetching with refspec specified
+func TestGitPipelineRunWithRefspec(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ description string
+ revision string
+ refspec string
+ }{{
+ description: "Fetch refs/tags/v0.1.0 alongside master and checkout the master branch",
+ revision: "master",
+ refspec: "refs/tags/v0.1.0:refs/tags/v0.1.0 refs/heads/master:refs/heads/master",
+ }, {
+ description: "Checkout specific revision from refs/pull/1009/head's commit chain",
+ revision: "968d5d37a61bfb85426c885dc1090c1cc4b33436",
+ refspec: "refs/pull/1009/head",
+ }, {
+ description: "Fetch refs/pull/1009/head into a named master branch and then check it out",
+ revision: "master",
+ refspec: "refs/pull/1009/head:refs/heads/master",
+ }} {
+ t.Run(tc.description, func(t *testing.T) {
+ c, namespace := setup(t)
+ knativetest.CleanupOnInterrupt(func() { tearDown(t, c, namespace) }, t.Logf)
+ defer tearDown(t, c, namespace)
+
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, tc.revision, tc.refspec, "true", "", "", "")); err != nil {
+ t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
+ }
+
+ if _, err := c.TaskClient.Create(getGitCheckTask(namespace)); err != nil {
+ t.Fatalf("Failed to create Task `%s`: %s", gitTestTaskName, err)
+ }
+
+ if _, err := c.PipelineClient.Create(getGitCheckPipeline(namespace)); err != nil {
+ t.Fatalf("Failed to create Pipeline `%s`: %s", gitTestPipelineName, err)
+ }
+
+ if _, err := c.PipelineRunClient.Create(getGitCheckPipelineRun(namespace)); err != nil {
+ t.Fatalf("Failed to create Pipeline `%s`: %s", gitTestPipelineRunName, err)
+ }
+
+ if err := WaitForPipelineRunState(c, gitTestPipelineRunName, timeout, PipelineRunSucceed(gitTestPipelineRunName), "PipelineRunCompleted"); err != nil {
+ t.Errorf("Error waiting for PipelineRun %s to finish: %s", gitTestPipelineRunName, err)
+ t.Fatalf("PipelineRun execution failed")
+ }
+
+ })
+ }
+}
+
// TestGitPipelineRun_Disable_SSLVerify will verify the source code is retrieved even after disabling SSL certificates (sslVerify)
func TestGitPipelineRun_Disable_SSLVerify(t *testing.T) {
t.Parallel()
@@ -87,7 +138,7 @@ func TestGitPipelineRun_Disable_SSLVerify(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "false", "", "", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "", "false", "", "", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -122,7 +173,7 @@ func TestGitPipelineRunFail(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "Idontexistrabbitmonkeydonkey", "true", "", "", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "Idontexistrabbitmonkeydonkey", "", "true", "", "", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -162,8 +213,7 @@ func TestGitPipelineRunFail(t *testing.T) {
t.Fatalf("Error getting pod logs for pod `%s` and container `%s` in namespace `%s`", tr.Status.PodName, stat.Name, namespace)
}
// Check for failure messages from fetch and pull in the log file
- if strings.Contains(strings.ToLower(string(logContent)), "couldn't find remote ref idontexistrabbitmonkeydonkey") &&
- strings.Contains(strings.ToLower(string(logContent)), "pathspec 'idontexistrabbitmonkeydonkey' did not match any file(s) known to git") {
+ if strings.Contains(strings.ToLower(string(logContent)), "couldn't find remote ref idontexistrabbitmonkeydonkey") {
t.Logf("Found exepected errors when retrieving non-existent git revision")
} else {
t.Logf("Container `%s` log File: %s", stat.Name, logContent)
@@ -190,7 +240,7 @@ func TestGitPipelineRunFail_HTTPS_PROXY(t *testing.T) {
defer tearDown(t, c, namespace)
t.Logf("Creating Git PipelineResource %s", gitSourceResourceName)
- if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "true", "", "invalid.https.proxy.com", "")); err != nil {
+ if _, err := c.PipelineResourceClient.Create(getGitPipelineResource(namespace, "master", "", "true", "", "invalid.https.proxy.com", "")); err != nil {
t.Fatalf("Failed to create Pipeline Resource `%s`: %s", gitSourceResourceName, err)
}
@@ -230,9 +280,8 @@ func TestGitPipelineRunFail_HTTPS_PROXY(t *testing.T) {
t.Fatalf("Error getting pod logs for pod `%s` and container `%s` in namespace `%s`", tr.Status.PodName, stat.Name, namespace)
}
// Check for failure messages from fetch and pull in the log file
- if strings.Contains(strings.ToLower(string(logContent)), "could not resolve proxy: invalid.https.proxy.com") &&
- strings.Contains(strings.ToLower(string(logContent)), "pathspec 'master' did not match any file(s) known to git") {
- t.Logf("Found exepected errors when using non-existent https proxy")
+ if strings.Contains(strings.ToLower(string(logContent)), "could not resolve proxy: invalid.https.proxy.com") {
+ t.Logf("Found expected errors when using non-existent https proxy")
} else {
t.Logf("Container `%s` log File: %s", stat.Name, logContent)
t.Fatalf("The git code extraction did not fail as expected. Expected errors not found in log file.")
@@ -248,11 +297,12 @@ func TestGitPipelineRunFail_HTTPS_PROXY(t *testing.T) {
}
}
-func getGitPipelineResource(namespace, revision, sslverify, httpproxy, httpsproxy, noproxy string) *v1alpha1.PipelineResource {
+func getGitPipelineResource(namespace, revision, refspec, sslverify, httpproxy, httpsproxy, noproxy string) *v1alpha1.PipelineResource {
return tb.PipelineResource(gitSourceResourceName, namespace, tb.PipelineResourceSpec(
v1alpha1.PipelineResourceTypeGit,
tb.PipelineResourceSpecParam("Url", "https://github.com/tektoncd/pipeline"),
tb.PipelineResourceSpecParam("Revision", revision),
+ tb.PipelineResourceSpecParam("Refspec", refspec),
tb.PipelineResourceSpecParam("sslVerify", sslverify),
tb.PipelineResourceSpecParam("httpProxy", httpproxy),
tb.PipelineResourceSpecParam("httpsProxy", httpsproxy),