feat: add ability to use shallow clone for repositories (#24931)

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
This commit is contained in:
Alexander Matyushentsev 2025-11-03 13:00:59 -08:00 committed by GitHub
parent 0a93e5701f
commit 1b08fd1004
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 845 additions and 714 deletions

5
assets/swagger.json generated
View file

@ -9480,6 +9480,11 @@
"connectionState": {
"$ref": "#/definitions/v1alpha1ConnectionState"
},
"depth": {
"description": "Depth specifies the depth for shallow clones. A value of 0 or omitting the field indicates a full clone.",
"type": "integer",
"format": "int64"
},
"enableLfs": {
"description": "EnableLFS specifies whether git-lfs support should be enabled for this repo. Only valid for Git repositories.",
"type": "boolean"

View file

@ -191,6 +191,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
repoOpts.Repo.NoProxy = repoOpts.NoProxy
repoOpts.Repo.ForceHttpBasicAuth = repoOpts.ForceHttpBasicAuth
repoOpts.Repo.UseAzureWorkloadIdentity = repoOpts.UseAzureWorkloadIdentity
repoOpts.Repo.Depth = repoOpts.Depth
if repoOpts.Repo.Type == "helm" && repoOpts.Repo.Name == "" {
errors.Fatal(errors.ErrorGeneric, "Must specify --name for repos of type 'helm'")

View file

@ -27,6 +27,7 @@ type RepoOptions struct {
GCPServiceAccountKeyPath string
ForceHttpBasicAuth bool //nolint:revive //FIXME(var-naming)
UseAzureWorkloadIdentity bool
Depth int64
}
func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
@ -53,4 +54,5 @@ func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
command.Flags().BoolVar(&opts.ForceHttpBasicAuth, "force-http-basic-auth", false, "whether to force use of basic auth when connecting repository via HTTP")
command.Flags().BoolVar(&opts.UseAzureWorkloadIdentity, "use-azure-workload-identity", false, "whether to use azure workload identity for authentication")
command.Flags().BoolVar(&opts.InsecureOCIForceHTTP, "insecure-oci-force-http", false, "Use http when accessing an OCI repository")
command.Flags().Int64Var(&opts.Depth, "depth", 0, "Specify a custom depth for git clone operations. Unless specified, a full clone is performed using the depth of 0")
}

View file

@ -229,7 +229,7 @@ func (s *Service) initGitClient(logCtx *log.Entry, r *apiclient.CommitHydratedMa
}
logCtx.Debugf("Fetching repo %s", r.Repo.Repo)
err = gitClient.Fetch("")
err = gitClient.Fetch("", 0)
if err != nil {
cleanupOrLog()
return nil, "", nil, fmt.Errorf("failed to clone repo: %w", err)

View file

@ -95,7 +95,7 @@ func Test_CommitHydratedManifests(t *testing.T) {
service, mockRepoClientFactory := newServiceWithMocks(t)
mockGitClient := gitmocks.NewClient(t)
mockGitClient.EXPECT().Init().Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().SetAuthor("Argo CD", "argo-cd@example.com").Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrOrphan("env/test", false).Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrNew("main", "env/test", false).Return("", nil).Once()
@ -115,7 +115,7 @@ func Test_CommitHydratedManifests(t *testing.T) {
service, mockRepoClientFactory := newServiceWithMocks(t)
mockGitClient := gitmocks.NewClient(t)
mockGitClient.EXPECT().Init().Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().SetAuthor("Argo CD", "argo-cd@example.com").Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrOrphan("env/test", false).Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrNew("main", "env/test", false).Return("", nil).Once()
@ -162,7 +162,7 @@ func Test_CommitHydratedManifests(t *testing.T) {
service, mockRepoClientFactory := newServiceWithMocks(t)
mockGitClient := gitmocks.NewClient(t)
mockGitClient.EXPECT().Init().Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().SetAuthor("Argo CD", "argo-cd@example.com").Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrOrphan("env/test", false).Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrNew("main", "env/test", false).Return("", nil).Once()
@ -202,7 +202,7 @@ func Test_CommitHydratedManifests(t *testing.T) {
service, mockRepoClientFactory := newServiceWithMocks(t)
mockGitClient := gitmocks.NewClient(t)
mockGitClient.EXPECT().Init().Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().SetAuthor("Argo CD", "argo-cd@example.com").Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrOrphan("env/test", false).Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrNew("main", "env/test", false).Return("", nil).Once()
@ -258,7 +258,7 @@ func Test_CommitHydratedManifests(t *testing.T) {
service, mockRepoClientFactory := newServiceWithMocks(t)
mockGitClient := gitmocks.NewClient(t)
mockGitClient.EXPECT().Init().Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil).Once()
mockGitClient.EXPECT().SetAuthor("Argo CD", "argo-cd@example.com").Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrOrphan("env/test", false).Return("", nil).Once()
mockGitClient.EXPECT().CheckoutOrNew("main", "env/test", false).Return("", nil).Once()

View file

@ -253,7 +253,7 @@ func (m *appStateManager) GetRepoObjs(ctx context.Context, app *v1alpha1.Applica
appNamespace = ""
}
if !source.IsHelm() && !source.IsOCI() && syncedRevision != "" && keyManifestGenerateAnnotationExists && keyManifestGenerateAnnotationVal != "" {
if repo.Depth == 0 && !source.IsHelm() && !source.IsOCI() && syncedRevision != "" && keyManifestGenerateAnnotationExists && keyManifestGenerateAnnotationVal != "" {
// Validate the manifest-generate-path annotation to avoid generating manifests if it has not changed.
updateRevisionResult, err := repoClient.UpdateRevisionForPaths(ctx, &apiclient.UpdateRevisionForPathsRequest{
Repo: repo,

View file

@ -533,3 +533,28 @@ Once the endpoint is enabled, you can use go profile tool to collect the CPU and
$ kubectl port-forward svc/argocd-metrics 8082:8082
$ go tool pprof http://localhost:8082/debug/pprof/heap
```
## Shallow Clone
Monorepos can be large and slow to clone. To speed up the clone process, you can use the `depth: "1"` repository option:
```yaml
apiVersion: v1
stringData:
depth: "1"
type: "git"
url: "https://github.com/argoproj/argocd-example-apps.git"
kind: Secret
metadata:
annotations:
managed-by: argocd.argoproj.io
labels:
argocd.argoproj.io/secret-type: repository
name: my-repo
namespace: argocd
type: Opaque
```
> [!NOTE] You can use the `argocd repo add <repo-url> --depth` command to add a repository with shallow cloning enabled.
When shallow cloning, the repository is cloned with a depth of 1, which means only the required commit is cloned as opposed to the full history. This approach makes sense when the repository has a large history.

View file

@ -51,6 +51,7 @@ argocd admin repo generate-spec REPOURL [flags]
```
--bearer-token string bearer token to the Git BitBucket Data Center repository
--depth int Specify a custom depth for git clone operations. Unless specified, a full clone is performed using the depth of 0
--enable-lfs enable git-lfs (Large File Support) on this repository
--enable-oci enable helm-oci (Helm OCI-Based Repository) (only valid for helm type repositories)
--force-http-basic-auth whether to force use of basic auth when connecting repository via HTTP

View file

@ -62,6 +62,7 @@ argocd repo add REPOURL [flags]
```
--bearer-token string bearer token to the Git BitBucket Data Center repository
--depth int Specify a custom depth for git clone operations. Unless specified, a full clone is performed using the depth of 0
--enable-lfs enable git-lfs (Large File Support) on this repository
--enable-oci enable helm-oci (Helm OCI-Based Repository) (only valid for helm type repositories)
--force-http-basic-auth whether to force use of basic auth when connecting repository via HTTP

File diff suppressed because it is too large Load diff

View file

@ -1916,6 +1916,9 @@ message Repository {
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
optional bool insecureOCIForceHttp = 26;
// Depth specifies the depth for shallow clones. A value of 0 or omitting the field indicates a full clone.
optional int64 depth = 27;
}
// A RepositoryCertificate is either SSH known hosts entry or TLS certificate

View file

@ -114,6 +114,8 @@ type Repository struct {
BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,25,opt,name=bearerToken"`
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
InsecureOCIForceHttp bool `json:"insecureOCIForceHttp,omitempty" protobuf:"bytes,26,opt,name=insecureOCIForceHttp"` //nolint:revive //FIXME(var-naming)
// Depth specifies the depth for shallow clones. A value of 0 or omitting the field indicates a full clone.
Depth int64 `json:"depth,omitempty" protobuf:"bytes,27,opt,name=depth"`
}
// IsInsecure returns true if the repository has been configured to skip server verification or set to HTTP only
@ -330,6 +332,7 @@ func (repo *Repository) CopySettingsFrom(source *Repository) {
repo.InsecureIgnoreHostKey = source.InsecureIgnoreHostKey
repo.Insecure = source.Insecure
repo.InheritedCreds = source.InheritedCreds
repo.Depth = source.Depth
}
}

View file

@ -247,7 +247,7 @@ func (s *Service) ListApps(ctx context.Context, q *apiclient.ListAppsRequest) (*
defer s.metricsServer.DecPendingRepoRequest(q.Repo.Repo)
closer, err := s.repoLock.Lock(gitClient.Root(), commitSHA, true, func() (goio.Closer, error) {
return s.checkoutRevision(gitClient, commitSHA, s.initConstants.SubmoduleEnabled)
return s.checkoutRevision(gitClient, commitSHA, s.initConstants.SubmoduleEnabled, q.Repo.Depth)
})
if err != nil {
return nil, fmt.Errorf("error acquiring repository lock: %w", err)
@ -449,7 +449,7 @@ func (s *Service) runRepoOperation(
})
}
closer, err := s.repoLock.Lock(gitClient.Root(), revision, settings.allowConcurrent, func() (goio.Closer, error) {
return s.checkoutRevision(gitClient, revision, s.initConstants.SubmoduleEnabled)
return s.checkoutRevision(gitClient, revision, s.initConstants.SubmoduleEnabled, repo.Depth)
})
if err != nil {
return err
@ -834,7 +834,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
return
}
closer, err := s.repoLock.Lock(gitClient.Root(), referencedCommitSHA, true, func() (goio.Closer, error) {
return s.checkoutRevision(gitClient, referencedCommitSHA, s.initConstants.SubmoduleEnabled)
return s.checkoutRevision(gitClient, referencedCommitSHA, s.initConstants.SubmoduleEnabled, q.Repo.Depth)
})
if err != nil {
log.Errorf("failed to acquire lock for referenced source %s", normalizedRepoURL)
@ -2435,7 +2435,7 @@ func (s *Service) GetRevisionMetadata(_ context.Context, q *apiclient.RepoServer
defer s.metricsServer.DecPendingRepoRequest(q.Repo.Repo)
closer, err := s.repoLock.Lock(gitClient.Root(), q.Revision, true, func() (goio.Closer, error) {
return s.checkoutRevision(gitClient, q.Revision, s.initConstants.SubmoduleEnabled)
return s.checkoutRevision(gitClient, q.Revision, s.initConstants.SubmoduleEnabled, q.Repo.Depth)
})
if err != nil {
return nil, fmt.Errorf("error acquiring repo lock: %w", err)
@ -2658,9 +2658,9 @@ func directoryPermissionInitializer(rootPath string) goio.Closer {
// checkoutRevision is a convenience function to initialize a repo, fetch, and checkout a revision
// Returns the 40 character commit SHA after the checkout has been performed
func (s *Service) checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bool) (goio.Closer, error) {
func (s *Service) checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bool, depth int64) (goio.Closer, error) {
closer := s.gitRepoInitializer(gitClient.Root())
err := checkoutRevision(gitClient, revision, submoduleEnabled)
err := checkoutRevision(gitClient, revision, submoduleEnabled, depth)
if err != nil {
s.metricsServer.IncGitFetchFail(gitClient.Root(), revision)
}
@ -2692,7 +2692,7 @@ func fetch(gitClient git.Client, targetRevisions []string) error {
return nil
}
// Fetching with no revision first. Fetching with an explicit version can cause repo bloat. https://github.com/argoproj/argo-cd/issues/8845
err := gitClient.Fetch("")
err := gitClient.Fetch("", 0)
if err != nil {
return err
}
@ -2703,7 +2703,7 @@ func fetch(gitClient git.Client, targetRevisions []string) error {
log.Infof("Failed to fetch revision %s: %v", revision, err)
log.Infof("Fallback to fetching specific revision %s. ref might not have been in the default refspec fetched.", revision)
if err := gitClient.Fetch(revision); err != nil {
if err := gitClient.Fetch(revision, 0); err != nil {
return status.Errorf(codes.Internal, "Failed to fetch revision %s: %v", revision, err)
}
}
@ -2711,7 +2711,7 @@ func fetch(gitClient git.Client, targetRevisions []string) error {
return nil
}
func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bool) error {
func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bool, depth int64) error {
err := gitClient.Init()
if err != nil {
return status.Errorf(codes.Internal, "Failed to initialize git repo: %v", err)
@ -2725,8 +2725,13 @@ func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bo
// Fetching can be skipped if the revision is already present locally.
if !revisionPresent {
// Fetching with no revision first. Fetching with an explicit version can cause repo bloat. https://github.com/argoproj/argo-cd/issues/8845
err = gitClient.Fetch("")
if depth > 0 {
err = gitClient.Fetch(revision, depth)
} else {
// Fetching with no revision first. Fetching with an explicit version can cause repo bloat. https://github.com/argoproj/argo-cd/issues/8845
err = gitClient.Fetch("", depth)
}
if err != nil {
return status.Errorf(codes.Internal, "Failed to fetch default: %v", err)
}
@ -2738,8 +2743,7 @@ func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bo
// for the given revision, try explicitly fetching it.
log.Infof("Failed to checkout revision %s: %v", revision, err)
log.Infof("Fallback to fetching specific revision %s. ref might not have been in the default refspec fetched.", revision)
err = gitClient.Fetch(revision)
err = gitClient.Fetch(revision, depth)
if err != nil {
return status.Errorf(codes.Internal, "Failed to checkout revision %s: %v", revision, err)
}
@ -2886,7 +2890,7 @@ func (s *Service) GetGitFiles(_ context.Context, request *apiclient.GitFilesRequ
// cache miss, generate the results
closer, err := s.repoLock.Lock(gitClient.Root(), revision, true, func() (goio.Closer, error) {
return s.checkoutRevision(gitClient, revision, request.GetSubmoduleEnabled())
return s.checkoutRevision(gitClient, revision, request.GetSubmoduleEnabled(), repo.Depth)
})
if err != nil {
return nil, status.Errorf(codes.Internal, "unable to checkout git repo %s with revision %s pattern %s: %v", repo.Repo, revision, gitPath, err)
@ -2968,7 +2972,7 @@ func (s *Service) GetGitDirectories(_ context.Context, request *apiclient.GitDir
// cache miss, generate the results
closer, err := s.repoLock.Lock(gitClient.Root(), revision, true, func() (goio.Closer, error) {
return s.checkoutRevision(gitClient, revision, request.GetSubmoduleEnabled())
return s.checkoutRevision(gitClient, revision, request.GetSubmoduleEnabled(), repo.Depth)
})
if err != nil {
return nil, status.Errorf(codes.Internal, "unable to checkout git repo %s with revision %s: %v", repo.Repo, revision, err)
@ -3060,7 +3064,7 @@ func (s *Service) UpdateRevisionForPaths(_ context.Context, request *apiclient.U
defer s.metricsServer.DecPendingRepoRequest(repo.Repo)
closer, err := s.repoLock.Lock(gitClient.Root(), revision, true, func() (goio.Closer, error) {
return s.checkoutRevision(gitClient, revision, false)
return s.checkoutRevision(gitClient, revision, false, 0)
})
if err != nil {
return nil, status.Errorf(codes.Internal, "unable to checkout git repo %s with revision %s: %v", repo.Repo, revision, err)

View file

@ -111,7 +111,7 @@ func newServiceWithMocks(t *testing.T, root string, signed bool) (*Service, *git
return newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, ociClient *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent(mock.Anything).Return(false)
gitClient.EXPECT().Fetch(mock.Anything).Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil)
gitClient.EXPECT().Checkout(mock.Anything, mock.Anything).Return("", nil)
gitClient.EXPECT().LsRemote(mock.Anything).Return(mock.Anything, nil)
gitClient.EXPECT().CommitSHA().Return(mock.Anything, nil)
@ -198,7 +198,7 @@ func newServiceWithCommitSHA(t *testing.T, root, revision string) *Service {
service, gitClient, _ := newServiceWithOpt(t, func(gitClient *gitmocks.Client, _ *helmmocks.Client, _ *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent(mock.Anything).Return(false)
gitClient.EXPECT().Fetch(mock.Anything).Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil)
gitClient.EXPECT().Checkout(mock.Anything, mock.Anything).Return("", nil)
gitClient.EXPECT().LsRemote(revision).Return(revision, revisionErr)
gitClient.EXPECT().CommitSHA().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
@ -399,7 +399,7 @@ func TestGenerateManifests_EmptyCache(t *testing.T) {
ExternalDeletes: 1,
})
gitMocks.AssertCalled(t, "LsRemote", mock.Anything)
gitMocks.AssertCalled(t, "Fetch", mock.Anything)
gitMocks.AssertCalled(t, "Fetch", mock.Anything, mock.Anything)
}
// Test that when Generate manifest is called with a source that is ref only it does not try to generate manifests or hit the manifest cache
@ -3173,10 +3173,10 @@ func TestCheckoutRevisionCanGetNonstandardRefs(t *testing.T) {
pullSha, err := gitClient.LsRemote("refs/pull/123/head")
require.NoError(t, err)
err = checkoutRevision(gitClient, "does-not-exist", false)
err = checkoutRevision(gitClient, "does-not-exist", false, 0)
require.Error(t, err)
err = checkoutRevision(gitClient, pullSha, false)
err = checkoutRevision(gitClient, pullSha, false, 0)
require.NoError(t, err)
}
@ -3188,7 +3188,7 @@ func TestCheckoutRevisionPresentSkipFetch(t *testing.T) {
gitClient.EXPECT().IsRevisionPresent(revision).Return(true)
gitClient.EXPECT().Checkout(revision, mock.Anything).Return("", nil)
err := checkoutRevision(gitClient, revision, false)
err := checkoutRevision(gitClient, revision, false, 0)
require.NoError(t, err)
}
@ -3198,10 +3198,10 @@ func TestCheckoutRevisionNotPresentCallFetch(t *testing.T) {
gitClient := &gitmocks.Client{}
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent(revision).Return(false)
gitClient.EXPECT().Fetch("").Return(nil)
gitClient.EXPECT().Fetch("", mock.Anything).Return(nil)
gitClient.EXPECT().Checkout(revision, mock.Anything).Return("", nil)
err := checkoutRevision(gitClient, revision, false)
err := checkoutRevision(gitClient, revision, false, 0)
require.NoError(t, err)
}
@ -3213,7 +3213,7 @@ func TestFetch(t *testing.T) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent(revision1).Once().Return(true)
gitClient.EXPECT().IsRevisionPresent(revision2).Once().Return(false)
gitClient.EXPECT().Fetch("").Return(nil)
gitClient.EXPECT().Fetch("", mock.Anything).Return(nil)
gitClient.EXPECT().IsRevisionPresent(revision1).Once().Return(true)
gitClient.EXPECT().IsRevisionPresent(revision2).Once().Return(true)
@ -3660,7 +3660,7 @@ func TestGetGitDirectories(t *testing.T) {
s, _, cacheMocks := newServiceWithOpt(t, func(gitClient *gitmocks.Client, _ *helmmocks.Client, _ *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent(mock.Anything).Return(false)
gitClient.EXPECT().Fetch(mock.Anything).Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil)
gitClient.EXPECT().Checkout(mock.Anything, mock.Anything).Once().Return("", nil)
gitClient.EXPECT().LsRemote("HEAD").Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
gitClient.EXPECT().Root().Return(root)
@ -3693,7 +3693,7 @@ func TestGetGitDirectoriesWithHiddenDirSupported(t *testing.T) {
s, _, cacheMocks := newServiceWithOpt(t, func(gitClient *gitmocks.Client, _ *helmmocks.Client, _ *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent(mock.Anything).Return(false)
gitClient.EXPECT().Fetch(mock.Anything).Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil)
gitClient.EXPECT().Checkout(mock.Anything, mock.Anything).Once().Return("", nil)
gitClient.EXPECT().LsRemote("HEAD").Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
gitClient.EXPECT().Root().Return(root)
@ -3787,7 +3787,7 @@ func TestGetGitFiles(t *testing.T) {
s, _, cacheMocks := newServiceWithOpt(t, func(gitClient *gitmocks.Client, _ *helmmocks.Client, _ *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent(mock.Anything).Return(false)
gitClient.EXPECT().Fetch(mock.Anything).Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Return(nil)
gitClient.EXPECT().Checkout(mock.Anything, mock.Anything).Once().Return("", nil)
gitClient.EXPECT().LsRemote("HEAD").Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
gitClient.EXPECT().Root().Return(root)
@ -3962,12 +3962,12 @@ func TestUpdateRevisionForPaths(t *testing.T) {
{name: "ChangedFilesDoNothing", fields: func() fields {
s, _, c := newServiceWithOpt(t, func(gitClient *gitmocks.Client, _ *helmmocks.Client, _ *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything).Once().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Once().Return(nil)
gitClient.EXPECT().IsRevisionPresent("632039659e542ed7de0c170a4fcc1c571b288fc0").Once().Return(false)
gitClient.EXPECT().Checkout("632039659e542ed7de0c170a4fcc1c571b288fc0", mock.Anything).Once().Return("", nil)
// fetch
gitClient.EXPECT().IsRevisionPresent("1e67a504d03def3a6a1125d934cb511680f72555").Once().Return(false)
gitClient.EXPECT().Fetch(mock.Anything).Once().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Once().Return(nil)
gitClient.EXPECT().IsRevisionPresent("1e67a504d03def3a6a1125d934cb511680f72555").Once().Return(true)
gitClient.EXPECT().LsRemote("HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
gitClient.EXPECT().LsRemote("SYNCEDHEAD").Once().Return("1e67a504d03def3a6a1125d934cb511680f72555", nil)
@ -3995,12 +3995,12 @@ func TestUpdateRevisionForPaths(t *testing.T) {
{name: "NoChangesUpdateCache", fields: func() fields {
s, _, c := newServiceWithOpt(t, func(gitClient *gitmocks.Client, _ *helmmocks.Client, _ *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything).Once().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Once().Return(nil)
gitClient.EXPECT().IsRevisionPresent("632039659e542ed7de0c170a4fcc1c571b288fc0").Once().Return(false)
gitClient.EXPECT().Checkout(mock.Anything, mock.Anything).Return("", nil)
gitClient.EXPECT().IsRevisionPresent("1e67a504d03def3a6a1125d934cb511680f72555").Once().Return(false)
// fetch
gitClient.EXPECT().Fetch(mock.Anything).Once().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Once().Return(nil)
gitClient.EXPECT().IsRevisionPresent("1e67a504d03def3a6a1125d934cb511680f72555").Once().Return(true)
gitClient.EXPECT().LsRemote("HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
gitClient.EXPECT().LsRemote("SYNCEDHEAD").Once().Return("1e67a504d03def3a6a1125d934cb511680f72555", nil)
@ -4038,11 +4038,11 @@ func TestUpdateRevisionForPaths(t *testing.T) {
s, _, c := newServiceWithOpt(t, func(gitClient *gitmocks.Client, _ *helmmocks.Client, _ *ocimocks.Client, paths *iomocks.TempPaths) {
gitClient.EXPECT().Init().Return(nil)
gitClient.EXPECT().IsRevisionPresent("632039659e542ed7de0c170a4fcc1c571b288fc0").Once().Return(false)
gitClient.EXPECT().Fetch(mock.Anything).Once().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Once().Return(nil)
gitClient.EXPECT().Checkout(mock.Anything, mock.Anything).Return("", nil)
// fetch
gitClient.EXPECT().IsRevisionPresent("1e67a504d03def3a6a1125d934cb511680f72555").Once().Return(true)
gitClient.EXPECT().Fetch(mock.Anything).Once().Return(nil)
gitClient.EXPECT().Fetch(mock.Anything, mock.Anything).Once().Return(nil)
gitClient.EXPECT().LsRemote("HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
gitClient.EXPECT().LsRemote("SYNCEDHEAD").Once().Return("1e67a504d03def3a6a1125d934cb511680f72555", nil)
paths.EXPECT().GetPath(mock.Anything).Return(".", nil)

View file

@ -137,13 +137,13 @@ func (c *Context) CustomSSHKnownHostsAdded() *Context {
return c
}
func (c *Context) HTTPSRepoURLAdded(withCreds bool) *Context {
repos.AddHTTPSRepo(c.t, false, withCreds, "", fixture.RepoURLTypeHTTPS)
func (c *Context) HTTPSRepoURLAdded(withCreds bool, opts ...repos.AddRepoOpts) *Context {
repos.AddHTTPSRepo(c.t, false, withCreds, "", fixture.RepoURLTypeHTTPS, opts...)
return c
}
func (c *Context) HTTPSInsecureRepoURLAdded(withCreds bool) *Context {
repos.AddHTTPSRepo(c.t, true, withCreds, "", fixture.RepoURLTypeHTTPS)
func (c *Context) HTTPSInsecureRepoURLAdded(withCreds bool, opts ...repos.AddRepoOpts) *Context {
repos.AddHTTPSRepo(c.t, true, withCreds, "", fixture.RepoURLTypeHTTPS, opts...)
return c
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
@ -30,8 +31,26 @@ func mustToAbsPath(t *testing.T, relativePath string) string {
return res
}
type AddRepoOpts func(args []string) []string
func WithDepth(depth int64) AddRepoOpts {
return func(args []string) []string {
if depth > 0 {
args = append(args, "--depth", strconv.FormatInt(depth, 10))
}
return args
}
}
func applyOpts(args []string, opts []AddRepoOpts) []string {
for _, opt := range opts {
args = opt(args)
}
return args
}
// sets the current repo as the default SSH test repo
func AddSSHRepo(t *testing.T, insecure bool, credentials bool, repoURLType fixture.RepoURLType) {
func AddSSHRepo(t *testing.T, insecure bool, credentials bool, repoURLType fixture.RepoURLType, opts ...AddRepoOpts) {
t.Helper()
keyPath, err := filepath.Abs("../fixture/testrepos/id_rsa")
require.NoError(t, err)
@ -42,11 +61,11 @@ func AddSSHRepo(t *testing.T, insecure bool, credentials bool, repoURLType fixtu
if insecure {
args = append(args, "--insecure-ignore-host-key")
}
errors.NewHandler(t).FailOnErr(fixture.RunCli(args...))
errors.NewHandler(t).FailOnErr(fixture.RunCli(applyOpts(args, opts)...))
}
// sets the current repo as the default HTTPS test repo
func AddHTTPSRepo(t *testing.T, insecure bool, credentials bool, project string, repoURLType fixture.RepoURLType) {
func AddHTTPSRepo(t *testing.T, insecure bool, credentials bool, project string, repoURLType fixture.RepoURLType, opts ...AddRepoOpts) {
t.Helper()
// This construct is somewhat necessary to satisfy the compiler
args := []string{"repo", "add", fixture.RepoURL(repoURLType)}
@ -59,7 +78,7 @@ func AddHTTPSRepo(t *testing.T, insecure bool, credentials bool, project string,
if project != "" {
args = append(args, "--project", project)
}
errors.NewHandler(t).FailOnErr(fixture.RunCli(args...))
errors.NewHandler(t).FailOnErr(fixture.RunCli(applyOpts(args, opts)...))
}
// sets a HTTPS repo using TLS client certificate authentication

View file

@ -6,16 +6,16 @@ import (
"testing"
"time"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/stretchr/testify/require"
. "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/test/e2e/fixture"
. "github.com/argoproj/argo-cd/v3/test/e2e/fixture/app"
"github.com/argoproj/argo-cd/v3/test/e2e/fixture/repos"
"github.com/argoproj/argo-cd/v3/util/errors"
)
@ -34,6 +34,27 @@ func TestGitSemverResolutionNotUsingConstraint(t *testing.T) {
Expect(SyncStatusIs(SyncStatusCodeSynced))
}
func TestGitShallowClone(t *testing.T) {
Given(t).
Path("deployment").
HTTPSInsecureRepoURLAdded(true, repos.WithDepth(1)).
RepoURLType(fixture.RepoURLTypeHTTPS).
When().
CreateApp().
Sync().
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
When().
PatchFile("deployment.yaml", `[{"op": "add", "path": "/metadata/labels", "value": {"foo": "bar"}}]`).
Refresh(RefreshTypeNormal).
Then().
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
When().
Sync().
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced))
}
func TestGitSemverResolutionNotUsingConstraintWithLeadingZero(t *testing.T) {
Given(t).
Path("deployment").

View file

@ -397,6 +397,12 @@ func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) {
}
repository.UseAzureWorkloadIdentity = useAzureWorkloadIdentity
depth, err := intOrZero(secret, "depth")
if err != nil {
return repository, err
}
repository.Depth = depth
return repository, nil
}
@ -431,6 +437,7 @@ func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Reposit
updateSecretString(secretCopy, "gcpServiceAccountKey", repository.GCPServiceAccountKey)
updateSecretBool(secretCopy, "forceHttpBasicAuth", repository.ForceHttpBasicAuth)
updateSecretBool(secretCopy, "useAzureWorkloadIdentity", repository.UseAzureWorkloadIdentity)
updateSecretInt(secretCopy, "depth", repository.Depth)
addSecretMetadata(secretCopy, s.getSecretType())
return secretCopy

View file

@ -109,7 +109,7 @@ type gitRefCache interface {
type Client interface {
Root() string
Init() error
Fetch(revision string) error
Fetch(revision string, depth int64) error
Submodule() error
Checkout(revision string, submoduleEnabled bool) (string, error)
LsRefs() (*Refs, error)
@ -415,14 +415,19 @@ func (m *nativeGitClient) IsLFSEnabled() bool {
return m.enableLfs
}
func (m *nativeGitClient) fetch(ctx context.Context, revision string) error {
var err error
func (m *nativeGitClient) fetch(ctx context.Context, revision string, depth int64) error {
args := []string{"fetch", "origin"}
if revision != "" {
err = m.runCredentialedCmd(ctx, "fetch", "origin", revision, "--tags", "--force", "--prune")
} else {
err = m.runCredentialedCmd(ctx, "fetch", "origin", "--tags", "--force", "--prune")
args = append(args, revision)
}
return err
if depth > 0 {
args = append(args, "--depth", strconv.FormatInt(depth, 10))
} else {
args = append(args, "--tags")
}
args = append(args, "--force", "--prune")
return m.runCredentialedCmd(ctx, args...)
}
// IsRevisionPresent checks to see if the given revision already exists locally.
@ -440,14 +445,14 @@ func (m *nativeGitClient) IsRevisionPresent(revision string) bool {
}
// Fetch fetches latest updates from origin
func (m *nativeGitClient) Fetch(revision string) error {
func (m *nativeGitClient) Fetch(revision string, depth int64) error {
if m.OnFetch != nil {
done := m.OnFetch(m.repoURL)
defer done()
}
ctx := context.Background()
err := m.fetch(ctx, revision)
err := m.fetch(ctx, revision, depth)
// When we have LFS support enabled, check for large files and fetch them too.
if err == nil && m.IsLFSEnabled() {

View file

@ -67,7 +67,7 @@ func Test_nativeGitClient_Fetch(t *testing.T) {
err = client.Init()
require.NoError(t, err)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
}
@ -85,7 +85,7 @@ func Test_nativeGitClient_Fetch_Prune(t *testing.T) {
err = runCmd(ctx, tempDir, "git", "branch", "test/foo")
require.NoError(t, err)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
err = runCmd(ctx, tempDir, "git", "branch", "-d", "test/foo")
@ -93,7 +93,7 @@ func Test_nativeGitClient_Fetch_Prune(t *testing.T) {
err = runCmd(ctx, tempDir, "git", "branch", "test/foo/bar")
require.NoError(t, err)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
}
@ -397,7 +397,7 @@ func Test_nativeGitClient_Submodule(t *testing.T) {
err = client.Init()
require.NoError(t, err)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
commitSHA, err := client.LsRemote("HEAD")
@ -666,7 +666,7 @@ func Test_nativeGitClient_CheckoutOrOrphan(t *testing.T) {
out, err := client.SetAuthor("test", "test@example.com")
require.NoError(t, err, "error output: %s", out)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
// checkout to origin base branch
@ -884,7 +884,7 @@ func Test_nativeGitClient_CommitAndPush(t *testing.T) {
out, err := client.SetAuthor("test", "test@example.com")
require.NoError(t, err, "error output: ", out)
err = client.Fetch(branch)
err = client.Fetch(branch, 0)
require.NoError(t, err)
out, err = client.Checkout(branch, false)

View file

@ -316,7 +316,7 @@ func TestLFSClient(t *testing.T) {
err = client.Init()
require.NoError(t, err)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
_, err = client.Checkout(commitSHA, true)
@ -351,7 +351,7 @@ func TestVerifyCommitSignature(t *testing.T) {
err = client.Init()
require.NoError(t, err)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
commitSHA, err := client.LsRemote("HEAD")
@ -407,11 +407,11 @@ func TestNewFactory(t *testing.T) {
err = client.Init()
require.NoError(t, err)
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
// Do a second fetch to make sure we can treat `already up-to-date` error as not an error
err = client.Fetch("")
err = client.Fetch("", 0)
require.NoError(t, err)
_, err = client.Checkout(commitSHA, true)

View file

@ -428,16 +428,16 @@ func (_c *Client_CommitSHA_Call) RunAndReturn(run func() (string, error)) *Clien
}
// Fetch provides a mock function for the type Client
func (_mock *Client) Fetch(revision string) error {
ret := _mock.Called(revision)
func (_mock *Client) Fetch(revision string, depth int64) error {
ret := _mock.Called(revision, depth)
if len(ret) == 0 {
panic("no return value specified for Fetch")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
r0 = returnFunc(revision)
if returnFunc, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = returnFunc(revision, depth)
} else {
r0 = ret.Error(0)
}
@ -451,18 +451,24 @@ type Client_Fetch_Call struct {
// Fetch is a helper method to define mock.On call
// - revision string
func (_e *Client_Expecter) Fetch(revision interface{}) *Client_Fetch_Call {
return &Client_Fetch_Call{Call: _e.mock.On("Fetch", revision)}
// - depth int64
func (_e *Client_Expecter) Fetch(revision interface{}, depth interface{}) *Client_Fetch_Call {
return &Client_Fetch_Call{Call: _e.mock.On("Fetch", revision, depth)}
}
func (_c *Client_Fetch_Call) Run(run func(revision string)) *Client_Fetch_Call {
func (_c *Client_Fetch_Call) Run(run func(revision string, depth int64)) *Client_Fetch_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 string
if args[0] != nil {
arg0 = args[0].(string)
}
var arg1 int64
if args[1] != nil {
arg1 = args[1].(int64)
}
run(
arg0,
arg1,
)
})
return _c
@ -473,7 +479,7 @@ func (_c *Client_Fetch_Call) Return(err error) *Client_Fetch_Call {
return _c
}
func (_c *Client_Fetch_Call) RunAndReturn(run func(revision string) error) *Client_Fetch_Call {
func (_c *Client_Fetch_Call) RunAndReturn(run func(revision string, depth int64) error) *Client_Fetch_Call {
_c.Call.Return(run)
return _c
}