github.com/argoproj/argo-cd/v3@v3.2.1/util/git/git_test.go (about)

     1  package git
     2  
     3  import (
     4  	"io"
     5  	"net/http"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/argoproj/argo-cd/v3/common"
    14  	"github.com/argoproj/argo-cd/v3/test/fixture/log"
    15  	"github.com/argoproj/argo-cd/v3/test/fixture/path"
    16  	"github.com/argoproj/argo-cd/v3/test/fixture/test"
    17  )
    18  
    19  func TestIsCommitSHA(t *testing.T) {
    20  	assert.True(t, IsCommitSHA("9d921f65f3c5373b682e2eb4b37afba6592e8f8b"))
    21  	assert.True(t, IsCommitSHA("9D921F65F3C5373B682E2EB4B37AFBA6592E8F8B"))
    22  	assert.False(t, IsCommitSHA("gd921f65f3c5373b682e2eb4b37afba6592e8f8b"))
    23  	assert.False(t, IsCommitSHA("master"))
    24  	assert.False(t, IsCommitSHA("HEAD"))
    25  	assert.False(t, IsCommitSHA("9d921f6")) // only consider 40 characters hex strings as a commit-sha
    26  	assert.True(t, IsTruncatedCommitSHA("9d921f6"))
    27  	assert.False(t, IsTruncatedCommitSHA("9d921f")) // we only consider 7+ characters
    28  	assert.False(t, IsTruncatedCommitSHA("branch-name"))
    29  }
    30  
    31  func TestEnsurePrefix(t *testing.T) {
    32  	data := [][]string{
    33  		{"world", "hello", "helloworld"},
    34  		{"helloworld", "hello", "helloworld"},
    35  		{"example.com", "https://", "https://example.com"},
    36  		{"https://example.com", "https://", "https://example.com"},
    37  		{"cd", "argo", "argocd"},
    38  		{"argocd", "argo", "argocd"},
    39  		{"", "argocd", "argocd"},
    40  		{"argocd", "", "argocd"},
    41  	}
    42  	for _, table := range data {
    43  		result := ensurePrefix(table[0], table[1])
    44  		assert.Equal(t, table[2], result)
    45  	}
    46  }
    47  
    48  func TestIsSSHURL(t *testing.T) {
    49  	data := map[string]bool{
    50  		"git://github.com/argoproj/test.git":     false,
    51  		"git@GITHUB.com:argoproj/test.git":       true,
    52  		"git@github.com:test":                    true,
    53  		"git@github.com:test.git":                true,
    54  		"https://github.com/argoproj/test":       false,
    55  		"https://github.com/argoproj/test.git":   false,
    56  		"ssh://git@GITHUB.com:argoproj/test":     true,
    57  		"ssh://git@GITHUB.com:argoproj/test.git": true,
    58  		"ssh://git@github.com:test.git":          true,
    59  	}
    60  	for k, v := range data {
    61  		isSSH, _ := IsSSHURL(k)
    62  		assert.Equal(t, v, isSSH)
    63  	}
    64  }
    65  
    66  func TestIsSSHURLUserName(t *testing.T) {
    67  	isSSH, user := IsSSHURL("ssh://john@john-server.org:29418/project")
    68  	assert.True(t, isSSH)
    69  	assert.Equal(t, "john", user)
    70  
    71  	isSSH, user = IsSSHURL("john@john-server.org:29418/project")
    72  	assert.True(t, isSSH)
    73  	assert.Equal(t, "john", user)
    74  
    75  	isSSH, user = IsSSHURL("john@doe.org@john-server.org:29418/project")
    76  	assert.True(t, isSSH)
    77  	assert.Equal(t, "john@doe.org", user)
    78  
    79  	isSSH, user = IsSSHURL("ssh://john@doe.org@john-server.org:29418/project")
    80  	assert.True(t, isSSH)
    81  	assert.Equal(t, "john@doe.org", user)
    82  
    83  	isSSH, user = IsSSHURL("john@doe.org@john-server.org:project")
    84  	assert.True(t, isSSH)
    85  	assert.Equal(t, "john@doe.org", user)
    86  
    87  	isSSH, user = IsSSHURL("john@doe.org@john-server.org:29418/project")
    88  	assert.True(t, isSSH)
    89  	assert.Equal(t, "john@doe.org", user)
    90  }
    91  
    92  func TestSameURL(t *testing.T) {
    93  	data := map[string]string{
    94  		"git@GITHUB.com:argoproj/test":                     "git@github.com:argoproj/test.git",
    95  		"git@GITHUB.com:argoproj/test.git":                 "git@github.com:argoproj/test.git",
    96  		"git@GITHUB.com:test":                              "git@github.com:test.git",
    97  		"git@GITHUB.com:test.git":                          "git@github.com:test.git",
    98  		"https://GITHUB.com/argoproj/test":                 "https://github.com/argoproj/test.git",
    99  		"https://GITHUB.com/argoproj/test.git":             "https://github.com/argoproj/test.git",
   100  		"https://github.com/FOO":                           "https://github.com/foo",
   101  		"https://github.com/TEST":                          "https://github.com/TEST.git",
   102  		"https://github.com/TEST.git":                      "https://github.com/TEST.git",
   103  		"https://github.com:4443/TEST":                     "https://github.com:4443/TEST.git",
   104  		"https://github.com:4443/TEST.git":                 "https://github.com:4443/TEST",
   105  		"ssh://git@GITHUB.com/argoproj/test":               "git@github.com:argoproj/test.git",
   106  		"ssh://git@GITHUB.com/argoproj/test.git":           "git@github.com:argoproj/test.git",
   107  		"ssh://git@GITHUB.com/test.git":                    "git@github.com:test.git",
   108  		"ssh://git@github.com/test":                        "git@github.com:test.git",
   109  		" https://github.com/argoproj/test ":               "https://github.com/argoproj/test.git", //nolint:gocritic // This includes whitespaces for testing
   110  		"\thttps://github.com/argoproj/test\n":             "https://github.com/argoproj/test.git",
   111  		"https://1234.visualstudio.com/myproj/_git/myrepo": "https://1234.visualstudio.com/myproj/_git/myrepo",
   112  		"https://dev.azure.com/1234/myproj/_git/myrepo":    "https://dev.azure.com/1234/myproj/_git/myrepo",
   113  	}
   114  	for k, v := range data {
   115  		assert.True(t, SameURL(k, v))
   116  	}
   117  }
   118  
   119  func TestCustomHTTPClient(t *testing.T) {
   120  	certFile, err := filepath.Abs("../../test/fixture/certs/argocd-test-client.crt")
   121  	require.NoError(t, err)
   122  	assert.NotEmpty(t, certFile)
   123  
   124  	keyFile, err := filepath.Abs("../../test/fixture/certs/argocd-test-client.key")
   125  	require.NoError(t, err)
   126  	assert.NotEmpty(t, keyFile)
   127  
   128  	certData, err := os.ReadFile(certFile)
   129  	require.NoError(t, err)
   130  	assert.NotEmpty(t, string(certData))
   131  
   132  	keyData, err := os.ReadFile(keyFile)
   133  	require.NoError(t, err)
   134  	assert.NotEmpty(t, string(keyData))
   135  
   136  	// Get HTTPSCreds with client cert creds specified, and insecure connection
   137  	creds := NewHTTPSCreds("test", "test", "", string(certData), string(keyData), false, &NoopCredsStore{}, false)
   138  	client := GetRepoHTTPClient("https://localhost:9443/foo/bar", false, creds, "http://proxy:5000", "")
   139  	assert.NotNil(t, client)
   140  	assert.NotNil(t, client.Transport)
   141  	if client.Transport != nil {
   142  		transport := client.Transport.(*http.Transport)
   143  		assert.NotNil(t, transport.TLSClientConfig)
   144  		assert.True(t, transport.DisableKeepAlives)
   145  		assert.False(t, transport.TLSClientConfig.InsecureSkipVerify)
   146  		assert.NotNil(t, transport.TLSClientConfig.GetClientCertificate)
   147  		assert.Nil(t, transport.TLSClientConfig.RootCAs)
   148  		if transport.TLSClientConfig.GetClientCertificate != nil {
   149  			cert, err := transport.TLSClientConfig.GetClientCertificate(nil)
   150  			require.NoError(t, err)
   151  			if err == nil {
   152  				assert.NotNil(t, cert)
   153  				assert.NotEmpty(t, cert.Certificate)
   154  				assert.NotNil(t, cert.PrivateKey)
   155  			}
   156  		}
   157  		proxy, err := transport.Proxy(nil)
   158  		require.NoError(t, err)
   159  		assert.NotNil(t, proxy) // nil would mean no proxy is used
   160  		assert.Equal(t, "http://proxy:5000", proxy.String())
   161  	}
   162  
   163  	t.Setenv("http_proxy", "http://proxy-from-env:7878")
   164  
   165  	// Get HTTPSCreds without client cert creds, but insecure connection
   166  	creds = NewHTTPSCreds("test", "test", "", "", "", true, &NoopCredsStore{}, false)
   167  	client = GetRepoHTTPClient("https://localhost:9443/foo/bar", true, creds, "", "")
   168  	assert.NotNil(t, client)
   169  	assert.NotNil(t, client.Transport)
   170  	if client.Transport != nil {
   171  		transport := client.Transport.(*http.Transport)
   172  		assert.NotNil(t, transport.TLSClientConfig)
   173  		assert.True(t, transport.DisableKeepAlives)
   174  		assert.True(t, transport.TLSClientConfig.InsecureSkipVerify)
   175  		assert.NotNil(t, transport.TLSClientConfig.GetClientCertificate)
   176  		assert.Nil(t, transport.TLSClientConfig.RootCAs)
   177  		if transport.TLSClientConfig.GetClientCertificate != nil {
   178  			cert, err := transport.TLSClientConfig.GetClientCertificate(nil)
   179  			require.NoError(t, err)
   180  			if err == nil {
   181  				assert.NotNil(t, cert)
   182  				assert.Empty(t, cert.Certificate)
   183  				assert.Nil(t, cert.PrivateKey)
   184  			}
   185  		}
   186  		req, err := http.NewRequest(http.MethodGet, "http://proxy-from-env:7878", http.NoBody)
   187  		require.NoError(t, err)
   188  		proxy, err := transport.Proxy(req)
   189  		require.NoError(t, err)
   190  		assert.Equal(t, "http://proxy-from-env:7878", proxy.String())
   191  	}
   192  	// GetRepoHTTPClient with root ca
   193  	cert, err := os.ReadFile("../../test/fixture/certs/argocd-test-server.crt")
   194  	require.NoError(t, err)
   195  	temppath := t.TempDir()
   196  	defer os.RemoveAll(temppath)
   197  	err = os.WriteFile(filepath.Join(temppath, "127.0.0.1"), cert, 0o666)
   198  	require.NoError(t, err)
   199  	t.Setenv(common.EnvVarTLSDataPath, temppath)
   200  	client = GetRepoHTTPClient("https://127.0.0.1", false, creds, "", "")
   201  	assert.NotNil(t, client)
   202  	assert.NotNil(t, client.Transport)
   203  	if client.Transport != nil {
   204  		transport := client.Transport.(*http.Transport)
   205  		assert.NotNil(t, transport.TLSClientConfig)
   206  		assert.True(t, transport.DisableKeepAlives)
   207  		assert.False(t, transport.TLSClientConfig.InsecureSkipVerify)
   208  		assert.NotNil(t, transport.TLSClientConfig.RootCAs)
   209  	}
   210  }
   211  
   212  func TestLsRemote(t *testing.T) {
   213  	clnt, err := NewClientExt("https://github.com/argoproj/argo-cd.git", "/tmp", NopCreds{}, false, false, "", "")
   214  	require.NoError(t, err)
   215  
   216  	testCases := []struct {
   217  		name           string
   218  		revision       string
   219  		expectedCommit string
   220  	}{
   221  		{
   222  			name:     "should resolve symbolic link reference",
   223  			revision: "HEAD",
   224  		},
   225  		{
   226  			name:     "should resolve branch name",
   227  			revision: "master",
   228  		},
   229  		{
   230  			name:           "should resolve tag without semantic versioning",
   231  			revision:       "release-0.8",
   232  			expectedCommit: "ff87d8cb9e669d3738434733ecba3c6dd2c64d70",
   233  		},
   234  		{
   235  			name:           "should resolve a pinned tag with semantic versioning",
   236  			revision:       "v0.8.0",
   237  			expectedCommit: "d7c04ae24c16f8ec611b0331596fbc595537abe9",
   238  		},
   239  		{
   240  			name:           "should resolve a range tag with semantic versioning",
   241  			revision:       "v0.8.*", // it should resolve to v0.8.2
   242  			expectedCommit: "e5eefa2b943ae14a3e4491d4e35ef082e1c2a3f4",
   243  		},
   244  		{
   245  			name:           "should resolve a range tag with semantic versioning without the 'v' prefix",
   246  			revision:       "0.8.*", // it should resolve to v0.8.2
   247  			expectedCommit: "e5eefa2b943ae14a3e4491d4e35ef082e1c2a3f4",
   248  		},
   249  		{
   250  			name:           "should resolve a conditional range tag with semantic versioning",
   251  			revision:       ">=v2.9.0 <2.10.4", // it should resolve to v2.10.3
   252  			expectedCommit: "0fd6344537eb948cff602824a1d060421ceff40e",
   253  		},
   254  		{
   255  			name:     "should resolve a star range tag with semantic versioning",
   256  			revision: "*",
   257  		},
   258  		{
   259  			name:     "should resolve a star range suffixed tag with semantic versioning",
   260  			revision: "*-0",
   261  		},
   262  		{
   263  			name:           "should resolve commit sha",
   264  			revision:       "4e22a3cb21fa447ca362a05a505a69397c8a0d44",
   265  			expectedCommit: "4e22a3cb21fa447ca362a05a505a69397c8a0d44",
   266  		},
   267  	}
   268  
   269  	for _, tc := range testCases {
   270  		t.Run(tc.name, func(t *testing.T) {
   271  			commitSHA, err := clnt.LsRemote(tc.revision)
   272  			require.NoError(t, err)
   273  			assert.True(t, IsCommitSHA(commitSHA))
   274  			if tc.expectedCommit != "" {
   275  				assert.Equal(t, tc.expectedCommit, commitSHA)
   276  			}
   277  		})
   278  	}
   279  
   280  	// We do not resolve truncated git hashes and return the commit as-is if it appears to be a commit
   281  	t.Run("truncated commit", func(t *testing.T) {
   282  		commitSHA, err := clnt.LsRemote("4e22a3c")
   283  		require.NoError(t, err)
   284  		assert.False(t, IsCommitSHA(commitSHA))
   285  		assert.True(t, IsTruncatedCommitSHA(commitSHA))
   286  	})
   287  
   288  	t.Run("unresolvable revisions", func(t *testing.T) {
   289  		xfail := []string{
   290  			"unresolvable",
   291  			"4e22a3", // too short (6 characters)
   292  		}
   293  
   294  		for _, revision := range xfail {
   295  			_, err := clnt.LsRemote(revision)
   296  			assert.ErrorContains(t, err, "unable to resolve")
   297  		}
   298  	})
   299  }
   300  
   301  // Running this test requires git-lfs to be installed on your machine.
   302  func TestLFSClient(t *testing.T) {
   303  	// temporary disable LFS test
   304  	// TODO(alexmt): dockerize tests in and enabled it
   305  	t.Skip()
   306  
   307  	tempDir := t.TempDir()
   308  
   309  	client, err := NewClientExt("https://github.com/argoproj-labs/argocd-testrepo-lfs", tempDir, NopCreds{}, false, true, "", "")
   310  	require.NoError(t, err)
   311  
   312  	commitSHA, err := client.LsRemote("HEAD")
   313  	require.NoError(t, err)
   314  	assert.NotEmpty(t, commitSHA)
   315  
   316  	err = client.Init()
   317  	require.NoError(t, err)
   318  
   319  	err = client.Fetch("")
   320  	require.NoError(t, err)
   321  
   322  	_, err = client.Checkout(commitSHA, true)
   323  	require.NoError(t, err)
   324  
   325  	largeFiles, err := client.LsLargeFiles()
   326  	require.NoError(t, err)
   327  	assert.Len(t, largeFiles, 3)
   328  
   329  	fileHandle, err := os.Open(tempDir + "/test3.yaml")
   330  	require.NoError(t, err)
   331  	if err == nil {
   332  		defer func() {
   333  			if err = fileHandle.Close(); err != nil {
   334  				require.NoError(t, err)
   335  			}
   336  		}()
   337  		text, err := io.ReadAll(fileHandle)
   338  		require.NoError(t, err)
   339  		if err == nil {
   340  			assert.Equal(t, "This is not a YAML, sorry.\n", string(text))
   341  		}
   342  	}
   343  }
   344  
   345  func TestVerifyCommitSignature(t *testing.T) {
   346  	p := t.TempDir()
   347  
   348  	client, err := NewClientExt("https://github.com/argoproj/argo-cd.git", p, NopCreds{}, false, false, "", "")
   349  	require.NoError(t, err)
   350  
   351  	err = client.Init()
   352  	require.NoError(t, err)
   353  
   354  	err = client.Fetch("")
   355  	require.NoError(t, err)
   356  
   357  	commitSHA, err := client.LsRemote("HEAD")
   358  	require.NoError(t, err)
   359  
   360  	_, err = client.Checkout(commitSHA, true)
   361  	require.NoError(t, err)
   362  
   363  	// 28027897aad1262662096745f2ce2d4c74d02b7f is a commit that is signed in the repo
   364  	// It doesn't matter whether we know the key or not at this stage
   365  	{
   366  		out, err := client.VerifyCommitSignature("28027897aad1262662096745f2ce2d4c74d02b7f")
   367  		require.NoError(t, err)
   368  		assert.NotEmpty(t, out)
   369  		assert.Contains(t, out, "gpg: Signature made")
   370  	}
   371  
   372  	// 85d660f0b967960becce3d49bd51c678ba2a5d24 is a commit that is not signed
   373  	{
   374  		out, err := client.VerifyCommitSignature("85d660f0b967960becce3d49bd51c678ba2a5d24")
   375  		require.NoError(t, err)
   376  		assert.Empty(t, out)
   377  	}
   378  }
   379  
   380  func TestNewFactory(t *testing.T) {
   381  	addBinDirToPath := path.NewBinDirToPath(t)
   382  	defer addBinDirToPath.Close()
   383  	closer := log.Debug()
   384  	defer closer()
   385  	type args struct {
   386  		url                   string
   387  		insecureIgnoreHostKey bool
   388  	}
   389  	tests := []struct {
   390  		name string
   391  		args args
   392  	}{
   393  		{"GitHub", args{url: "https://github.com/argoproj/argocd-example-apps"}},
   394  	}
   395  	for _, tt := range tests {
   396  		if tt.name == "PrivateSSHRepo" {
   397  			test.Flaky(t)
   398  		}
   399  
   400  		dirName := t.TempDir()
   401  
   402  		client, err := NewClientExt(tt.args.url, dirName, NopCreds{}, tt.args.insecureIgnoreHostKey, false, "", "")
   403  		require.NoError(t, err)
   404  		commitSHA, err := client.LsRemote("HEAD")
   405  		require.NoError(t, err)
   406  
   407  		err = client.Init()
   408  		require.NoError(t, err)
   409  
   410  		err = client.Fetch("")
   411  		require.NoError(t, err)
   412  
   413  		// Do a second fetch to make sure we can treat `already up-to-date` error as not an error
   414  		err = client.Fetch("")
   415  		require.NoError(t, err)
   416  
   417  		_, err = client.Checkout(commitSHA, true)
   418  		require.NoError(t, err)
   419  
   420  		revisionMetadata, err := client.RevisionMetadata(commitSHA)
   421  		require.NoError(t, err)
   422  		assert.NotNil(t, revisionMetadata)
   423  		assert.Regexp(t, "^.*<.*>$", revisionMetadata.Author)
   424  		assert.Empty(t, revisionMetadata.Tags)
   425  		assert.NotEmpty(t, revisionMetadata.Date)
   426  		assert.NotEmpty(t, revisionMetadata.Message)
   427  
   428  		commitSHA2, err := client.CommitSHA()
   429  		require.NoError(t, err)
   430  
   431  		assert.Equal(t, commitSHA, commitSHA2)
   432  	}
   433  }
   434  
   435  func TestListRevisions(t *testing.T) {
   436  	dir := t.TempDir()
   437  
   438  	repoURL := "https://github.com/argoproj/argo-cd.git"
   439  	client, err := NewClientExt(repoURL, dir, NopCreds{}, false, false, "", "")
   440  	require.NoError(t, err)
   441  
   442  	lsResult, err := client.LsRefs()
   443  	require.NoError(t, err)
   444  
   445  	testBranch := "master"
   446  	testTag := "v1.0.0"
   447  
   448  	assert.Contains(t, lsResult.Branches, testBranch)
   449  	assert.Contains(t, lsResult.Tags, testTag)
   450  	assert.NotContains(t, lsResult.Branches, testTag)
   451  	assert.NotContains(t, lsResult.Tags, testBranch)
   452  }
   453  
   454  func TestLsFiles(t *testing.T) {
   455  	tmpDir1 := t.TempDir()
   456  	tmpDir2 := t.TempDir()
   457  
   458  	client, err := NewClientExt("", tmpDir1, NopCreds{}, false, false, "", "")
   459  	require.NoError(t, err)
   460  
   461  	require.NoError(t, runCmd(tmpDir1, "git", "init"))
   462  
   463  	// Setup files
   464  	require.NoError(t, os.WriteFile(filepath.Join(tmpDir1, "a.yaml"), []byte{}, 0o644))
   465  	require.NoError(t, os.MkdirAll(filepath.Join(tmpDir1, "subdir"), 0o755))
   466  	require.NoError(t, os.WriteFile(filepath.Join(tmpDir1, "subdir", "b.yaml"), []byte{}, 0o644))
   467  
   468  	require.NoError(t, os.MkdirAll(filepath.Join(tmpDir2, "subdir"), 0o755))
   469  	require.NoError(t, os.WriteFile(filepath.Join(tmpDir2, "c.yaml"), []byte{}, 0o644))
   470  
   471  	require.NoError(t, os.Symlink(filepath.Join(tmpDir2, "c.yaml"), filepath.Join(tmpDir1, "link.yaml")))
   472  
   473  	require.NoError(t, runCmd(tmpDir1, "git", "add", "."))
   474  	require.NoError(t, runCmd(tmpDir1, "git", "commit", "-m", "Initial commit"))
   475  
   476  	tests := []struct {
   477  		name           string
   478  		pattern        string
   479  		safeGlobbing   bool
   480  		expectedResult []string
   481  	}{
   482  		{
   483  			name:           "Old globbing with symlinks and subdir",
   484  			pattern:        "*.yaml",
   485  			safeGlobbing:   false,
   486  			expectedResult: []string{"a.yaml", "link.yaml", "subdir/b.yaml"},
   487  		},
   488  		{
   489  			name:           "Safe globbing excludes symlinks",
   490  			pattern:        "*.yaml",
   491  			safeGlobbing:   true,
   492  			expectedResult: []string{"a.yaml"},
   493  		},
   494  		{
   495  			name:           "Safe globbing excludes external paths",
   496  			pattern:        filepath.Join(tmpDir2, "*.yaml"),
   497  			safeGlobbing:   true,
   498  			expectedResult: nil,
   499  		},
   500  	}
   501  
   502  	for _, tt := range tests {
   503  		t.Run(tt.name, func(t *testing.T) {
   504  			lsResult, err := client.LsFiles(tt.pattern, tt.safeGlobbing)
   505  			require.NoError(t, err)
   506  			assert.Equal(t, tt.expectedResult, lsResult)
   507  		})
   508  	}
   509  }
   510  
   511  func TestLsFilesForGitFileGeneratorGlobbingPatterns(t *testing.T) {
   512  	tmpDir := t.TempDir()
   513  
   514  	client, err := NewClientExt("", tmpDir, NopCreds{}, false, false, "", "")
   515  	require.NoError(t, err)
   516  
   517  	err = runCmd(tmpDir, "git", "init")
   518  	require.NoError(t, err)
   519  
   520  	// Setup directory structure and files
   521  	files := []string{
   522  		"cluster-charts/cluster1/mychart/charts/mysubchart/values.yaml",
   523  		"cluster-charts/cluster1/mychart/values.yaml",
   524  		"cluster-charts/cluster1/myotherchart/values.yaml",
   525  		"cluster-charts/cluster2/values.yaml",
   526  		"some-path/values.yaml",
   527  		"some-path/staging/values.yaml",
   528  		"cluster-config/engineering/production/config.json",
   529  		"cluster-config/engineering/dev/config.json",
   530  		"p1/p2/config.json",
   531  		"p1/app2/config.json",
   532  		"p1/app3/config.json",
   533  		"p1/config.json",
   534  	}
   535  	for _, file := range files {
   536  		dir := filepath.Dir(file)
   537  		require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, dir), 0o755))
   538  		_, err := os.Create(filepath.Join(tmpDir, file))
   539  		require.NoError(t, err)
   540  	}
   541  	require.NoError(t, runCmd(tmpDir, "git", "add", "."))
   542  	require.NoError(t, runCmd(tmpDir, "git", "commit", "-m", "Initial commit"))
   543  
   544  	tests := []struct {
   545  		name                 string
   546  		pattern              string
   547  		isNewGlobbingEnabled bool
   548  		expected             []string
   549  	}{
   550  		{
   551  			name:                 "**/config.json (isNewGlobbingEnabled)",
   552  			pattern:              "**/config.json",
   553  			isNewGlobbingEnabled: true,
   554  			expected: []string{
   555  				"cluster-config/engineering/production/config.json",
   556  				"cluster-config/engineering/dev/config.json",
   557  				"p1/config.json",
   558  				"p1/p2/config.json",
   559  				"p1/app2/config.json",
   560  				"p1/app3/config.json",
   561  			},
   562  		},
   563  		{
   564  			name:                 "**/config.json (non-isNewGlobbingEnabled)",
   565  			pattern:              "**/config.json",
   566  			isNewGlobbingEnabled: false,
   567  			expected: []string{
   568  				"cluster-config/engineering/production/config.json",
   569  				"cluster-config/engineering/dev/config.json",
   570  				"p1/config.json",
   571  				"p1/p2/config.json",
   572  				"p1/app2/config.json",
   573  				"p1/app3/config.json",
   574  			},
   575  		},
   576  		{
   577  			name:                 "some-path/*.yaml (isNewGlobbingEnabled)",
   578  			pattern:              "some-path/*.yaml",
   579  			isNewGlobbingEnabled: true,
   580  			expected:             []string{"some-path/values.yaml"},
   581  		},
   582  		{
   583  			name:                 "some-path/*.yaml (non-isNewGlobbingEnabled)",
   584  			pattern:              "some-path/*.yaml",
   585  			isNewGlobbingEnabled: false,
   586  			expected: []string{
   587  				"some-path/values.yaml",
   588  				"some-path/staging/values.yaml",
   589  			},
   590  		},
   591  		{
   592  			name:                 "p1/**/config.json (isNewGlobbingEnabled)",
   593  			pattern:              "p1/**/config.json",
   594  			isNewGlobbingEnabled: true,
   595  			expected: []string{
   596  				"p1/config.json",
   597  				"p1/p2/config.json",
   598  				"p1/app2/config.json",
   599  				"p1/app3/config.json",
   600  			},
   601  		},
   602  		{
   603  			name:                 "p1/**/config.json (non-isNewGlobbingEnabled)",
   604  			pattern:              "p1/**/config.json",
   605  			isNewGlobbingEnabled: false,
   606  			expected: []string{
   607  				"p1/p2/config.json",
   608  				"p1/app2/config.json",
   609  				"p1/app3/config.json",
   610  			},
   611  		},
   612  		{
   613  			name:                 "cluster-config/**/config.json (isNewGlobbingEnabled)",
   614  			pattern:              "cluster-config/**/config.json",
   615  			isNewGlobbingEnabled: true,
   616  			expected: []string{
   617  				"cluster-config/engineering/production/config.json",
   618  				"cluster-config/engineering/dev/config.json",
   619  			},
   620  		},
   621  		{
   622  			name:                 "cluster-config/**/config.json (isNewGlobbingEnabled=false)",
   623  			pattern:              "cluster-config/**/config.json",
   624  			isNewGlobbingEnabled: false,
   625  			expected: []string{
   626  				"cluster-config/engineering/dev/config.json",
   627  				"cluster-config/engineering/production/config.json",
   628  			},
   629  		},
   630  		{
   631  			name:                 "cluster-config/*/dev/config.json (isNewGlobbingEnabled)",
   632  			pattern:              "cluster-config/*/dev/config.json",
   633  			isNewGlobbingEnabled: true,
   634  			expected:             []string{"cluster-config/engineering/dev/config.json"},
   635  		},
   636  		{
   637  			name:                 "cluster-config/*/dev/config.json (isNewGlobbingEnabled=false)",
   638  			pattern:              "cluster-config/*/dev/config.json",
   639  			isNewGlobbingEnabled: false,
   640  			expected:             []string{"cluster-config/engineering/dev/config.json"},
   641  		},
   642  		{
   643  			name:                 "cluster-charts/*/*/values.yaml (isNewGlobbingEnabled)",
   644  			pattern:              "cluster-charts/*/*/values.yaml",
   645  			isNewGlobbingEnabled: true,
   646  			expected: []string{
   647  				"cluster-charts/cluster1/mychart/values.yaml",
   648  				"cluster-charts/cluster1/myotherchart/values.yaml",
   649  			},
   650  		},
   651  		{
   652  			name:                 "cluster-charts/*/*/values.yaml (isNewGlobbingEnabled=false)",
   653  			pattern:              "cluster-charts/*/*/values.yaml",
   654  			isNewGlobbingEnabled: false,
   655  			expected: []string{
   656  				"cluster-charts/cluster1/mychart/values.yaml",
   657  				"cluster-charts/cluster1/myotherchart/values.yaml",
   658  				"cluster-charts/cluster1/mychart/charts/mysubchart/values.yaml",
   659  			},
   660  		},
   661  		{
   662  			name:                 "cluster-charts/*/values.yaml (isNewGlobbingEnabled)",
   663  			pattern:              "cluster-charts/*/values.yaml",
   664  			isNewGlobbingEnabled: true,
   665  			expected: []string{
   666  				"cluster-charts/cluster2/values.yaml",
   667  			},
   668  		},
   669  		{
   670  			name:                 "cluster-charts/*/values.yaml (non-isNewGlobbingEnabled)",
   671  			pattern:              "cluster-charts/*/values.yaml",
   672  			isNewGlobbingEnabled: false,
   673  			expected: []string{
   674  				"cluster-charts/cluster2/values.yaml",
   675  				"cluster-charts/cluster1/mychart/values.yaml",
   676  				"cluster-charts/cluster1/myotherchart/values.yaml",
   677  				"cluster-charts/cluster1/mychart/charts/mysubchart/values.yaml",
   678  			},
   679  		},
   680  	}
   681  
   682  	for _, tt := range tests {
   683  		t.Run(tt.name, func(t *testing.T) {
   684  			lsResult, err := client.LsFiles(tt.pattern, tt.isNewGlobbingEnabled)
   685  			require.NoError(t, err)
   686  			assert.ElementsMatch(t, tt.expected, lsResult)
   687  		})
   688  	}
   689  }
   690  
   691  func TestAnnotatedTagHandling(t *testing.T) {
   692  	dir := t.TempDir()
   693  
   694  	client, err := NewClientExt("https://github.com/argoproj/argo-cd.git", dir, NopCreds{}, false, false, "", "")
   695  	require.NoError(t, err)
   696  
   697  	err = client.Init()
   698  	require.NoError(t, err)
   699  
   700  	// Test annotated tag resolution
   701  	commitSHA, err := client.LsRemote("v1.0.0") // Known annotated tag
   702  	require.NoError(t, err)
   703  
   704  	// Verify we get commit SHA, not tag SHA
   705  	assert.True(t, IsCommitSHA(commitSHA))
   706  
   707  	// Test tag reference handling
   708  	refs, err := client.LsRefs()
   709  	require.NoError(t, err)
   710  
   711  	// Verify tag exists in the list and points to a valid commit SHA
   712  	assert.Contains(t, refs.Tags, "v1.0.0", "Tag v1.0.0 should exist in refs")
   713  }