Repository: argoproj/argo-cd
Pull request #8574 (2022-02-19 05:38:12)

feat(ui): Add warning and confirmation popup when force/replace sync is selected
Closes https://github.com/argoproj/argo-cd/issues/8466.

<img width="1356" alt="Screen Shot 2022-02-22 at 1 27 10 AM" src="https://user-images.githubusercontent.com/4269898/155075100-5a2efc08-6397-489a-988a-32bdfdc30086.png">


Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

Note on DCO:

If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.

Checklist:

* [x] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
* [x] The title of the PR states what changed and the related issues number (used for the release note).
* [x] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
* [x] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
* [x] Does this PR require documentation updates?
* [x] I've updated documentation as required by this PR.
* [x] Optional. My organization is added to USERS.md.
* [x] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/tree/master/community#contributing-to-argo)
* [x] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [x] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)). 



jessesuen: Shouldn't we use the same technique and be consistent with the replace checkbox?

<img width="507" alt="image" src="https://user-images.githubusercontent.com/12677113/155208406-e8ae11c3-5fbf-4339-a847-cac824c6bbf3.png">

terrytangyuan: > Shouldn't we use the same technique and be consistent with the replace checkbox?

Yep. I just added a popup for replace checkbox as well in applications panel.

terrytangyuan: I just discussed with @jessesuen. We are fine with the current approach - have a popup window for both force/replace in both applications and application sync panels. 

Patch:
diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml
index 3646323dc..e957031dc 100644
--- a/.github/workflows/ci-build.yaml
+++ b/.github/workflows/ci-build.yaml
@@ -376,6 +376,9 @@ jobs:
       - name: Add /usr/local/bin to PATH
         run: |
           echo "/usr/local/bin" >> $GITHUB_PATH
+      - name: Add ./dist to PATH
+        run: |
+          echo "$(pwd)/dist" >> $GITHUB_PATH
       - name: Download Go dependencies
         run: |
           go mod download
diff --git a/Dockerfile b/Dockerfile
index 4216e5901..81a2c00d9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -54,7 +54,6 @@ RUN groupadd -g 999 argocd && \
     apt-get clean && \
     rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 
-COPY hack/git-ask-pass.sh /usr/local/bin/git-ask-pass.sh
 COPY hack/gpg-wrapper.sh /usr/local/bin/gpg-wrapper.sh
 COPY hack/git-verify-wrapper.sh /usr/local/bin/git-verify-wrapper.sh
 COPY --from=builder /usr/local/bin/ks /usr/local/bin/ks
@@ -92,7 +91,7 @@ FROM docker.io/library/node:12.18.4 as argocd-ui
 WORKDIR /src
 ADD ["ui/package.json", "ui/yarn.lock", "./"]
 
-RUN yarn install --network-timeout 100000
+RUN yarn install --network-timeout 200000
 
 ADD ["ui/", "."]
 
diff --git a/assets/swagger.json b/assets/swagger.json
index 9dfe2b138..13f68e58b 100644
--- a/assets/swagger.json
+++ b/assets/swagger.json
@@ -2744,6 +2744,16 @@
             "type": "string",
             "name": "revision",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "name": "appName",
+            "in": "query"
+          },
+          {
+            "type": "string",
+            "name": "appProject",
+            "in": "query"
           }
         ],
         "responses": {
@@ -4108,6 +4118,9 @@
         "appName": {
           "type": "string"
         },
+        "appProject": {
+          "type": "string"
+        },
         "source": {
           "$ref": "#/definitions/v1alpha1ApplicationSource"
         }
diff --git a/cmd/argocd-git-ask-pass/commands/argocd_git_ask_pass.go b/cmd/argocd-git-ask-pass/commands/argocd_git_ask_pass.go
new file mode 100644
index 000000000..8c3596562
--- /dev/null
+++ b/cmd/argocd-git-ask-pass/commands/argocd_git_ask_pass.go
@@ -0,0 +1,57 @@
+package commands
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/argoproj/argo-cd/v2/util/git"
+
+	"github.com/spf13/cobra"
+	"google.golang.org/grpc"
+
+	"github.com/argoproj/argo-cd/v2/reposerver/askpass"
+	"github.com/argoproj/argo-cd/v2/util/errors"
+	grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
+	"github.com/argoproj/argo-cd/v2/util/io"
+)
+
+const (
+	// cliName is the name of the CLI
+	cliName = "argocd-git-ask-pass"
+)
+
+func NewCommand() *cobra.Command {
+	var command = cobra.Command{
+		Use:               cliName,
+		Short:             "Argo CD git credential helper",
+		DisableAutoGenTag: true,
+		Run: func(c *cobra.Command, args []string) {
+			if len(os.Args) != 2 {
+				errors.CheckError(fmt.Errorf("expected 1 argument, got %d", len(os.Args)-1))
+			}
+			nonce := os.Getenv(git.ASKPASS_NONCE_ENV)
+			if nonce == "" {
+				errors.CheckError(fmt.Errorf("%s is not set", git.ASKPASS_NONCE_ENV))
+			}
+			conn, err := grpc_util.BlockingDial(context.Background(), "unix", askpass.SocketPath, nil, grpc.WithInsecure())
+			errors.CheckError(err)
+			defer io.Close(conn)
+			client := askpass.NewAskPassServiceClient(conn)
+
+			creds, err := client.GetCredentials(context.Background(), &askpass.CredentialsRequest{Nonce: nonce})
+			errors.CheckError(err)
+			switch {
+			case strings.HasPrefix(os.Args[1], "Username"):
+				fmt.Println(creds.Username)
+			case strings.HasPrefix(os.Args[1], "Password"):
+				fmt.Println(creds.Password)
+			default:
+				errors.CheckError(fmt.Errorf("unknown credential type '%s'", os.Args[1]))
+			}
+		},
+	}
+
+	return &command
+}
diff --git a/cmd/argocd-repo-server/commands/argocd_repo_server.go b/cmd/argocd-repo-server/commands/argocd_repo_server.go
index ef0a18952..1d3c2eaf1 100644
--- a/cmd/argocd-repo-server/commands/argocd_repo_server.go
+++ b/cmd/argocd-repo-server/commands/argocd_repo_server.go
@@ -18,6 +18,7 @@ import (
 	"github.com/argoproj/argo-cd/v2/common"
 	"github.com/argoproj/argo-cd/v2/reposerver"
 	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
+	"github.com/argoproj/argo-cd/v2/reposerver/askpass"
 	reposervercache "github.com/argoproj/argo-cd/v2/reposerver/cache"
 	"github.com/argoproj/argo-cd/v2/reposerver/metrics"
 	"github.com/argoproj/argo-cd/v2/reposerver/repository"
@@ -94,6 +95,7 @@ func NewCommand() *cobra.Command {
 			cache, err := cacheSrc()
 			errors.CheckError(err)
 
+			askPassServer := askpass.NewServer()
 			metricsServer := metrics.NewMetricsServer()
 			cacheutil.CollectMetrics(redisClient, metricsServer)
 			server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, repository.RepoServerInitConstants{
@@ -102,7 +104,7 @@ func NewCommand() *cobra.Command {
 				PauseGenerationOnFailureForMinutes:           getPauseGenerationOnFailureForMinutes(),
 				PauseGenerationOnFailureForRequests:          getPauseGenerationOnFailureForRequests(),
 				SubmoduleEnabled:                             getSubmoduleEnabled(),
-			})
+			}, askPassServer)
 			errors.CheckError(err)
 
 			grpc := server.CreateGRPC()
@@ -133,6 +135,7 @@ func NewCommand() *cobra.Command {
 			})
 			http.Handle("/metrics", metricsServer.GetHandler())
 			go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", metricsPort), nil)) }()
+			go func() { errors.CheckError(askPassServer.Run(askpass.SocketPath)) }()
 
 			if gpg.IsGPGEnabled() {
 				log.Infof("Initializing GnuPG keyring at %s", common.GetGnuPGHomePath())
diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go
index 8d036492f..8ba572964 100644
--- a/cmd/argocd/commands/app.go
+++ b/cmd/argocd/commands/app.go
@@ -775,7 +775,7 @@ func getLocalObjectsString(app *argoappv1.Application, local, localRepoRoot, app
 		ApiVersions:       apiVersions,
 		Plugins:           configManagementPlugins,
 		TrackingMethod:    trackingMethod,
-	}, true)
+	}, true, &git.NoopCredsStore{})
 	errors.CheckError(err)
 
 	return res.Manifests
diff --git a/cmd/main.go b/cmd/main.go
index 8db5f903a..1756d2ddf 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -10,6 +10,7 @@ import (
 	appcontroller "github.com/argoproj/argo-cd/v2/cmd/argocd-application-controller/commands"
 	cmpserver "github.com/argoproj/argo-cd/v2/cmd/argocd-cmp-server/commands"
 	dex "github.com/argoproj/argo-cd/v2/cmd/argocd-dex/commands"
+	gitaskpass "github.com/argoproj/argo-cd/v2/cmd/argocd-git-ask-pass/commands"
 	notification "github.com/argoproj/argo-cd/v2/cmd/argocd-notification/commands"
 	reposerver "github.com/argoproj/argo-cd/v2/cmd/argocd-repo-server/commands"
 	apiserver "github.com/argoproj/argo-cd/v2/cmd/argocd-server/commands"
@@ -42,6 +43,8 @@ func main() {
 		command = dex.NewCommand()
 	case "argocd-notifications":
 		command = notification.NewCommand()
+	case "argocd-git-ask-pass":
+		command = gitaskpass.NewCommand()
 	default:
 		command = cli.NewCommand()
 	}
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 09bb5c287..25afa7119 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -62,7 +62,7 @@ Kubectl port-forwarding can also be used to connect to the API server without ex
 kubectl port-forward svc/argocd-server -n argocd 8080:443
 ```
 
-The API server can then be accessed using the localhost:8080
+The API server can then be accessed using https://localhost:8080
 
 
 ## 4. Login Using The CLI
diff --git a/docs/operator-manual/ingress.md b/docs/operator-manual/ingress.md
index 91a8b8edd..315a60e99 100644
--- a/docs/operator-manual/ingress.md
+++ b/docs/operator-manual/ingress.md
@@ -74,7 +74,7 @@ The Contour ingress controller can terminate TLS ingress traffic at the edge.
 
 The Argo CD API server should be run with TLS disabled. Edit the `argocd-server` Deployment to add the `--insecure` flag to the argocd-server container command.
 
-It is also possible to provide an internal-only ingress path and an external-only ingress path by deploying two instances of Contour: one behind a private-subnet LoadBalancer service and one behind a public-subnet LoadBalancer service. The private Contour deployment will pick up Ingresses annotated with `kubernetes.io/ingress.class: contour-external` and the public Contour deployment will pick up Ingresses annotated with `kubernetes.io/ingress.class: contour-external`.
+It is also possible to provide an internal-only ingress path and an external-only ingress path by deploying two instances of Contour: one behind a private-subnet LoadBalancer service and one behind a public-subnet LoadBalancer service. The private Contour deployment will pick up Ingresses annotated with `kubernetes.io/ingress.class: contour-internal` and the public Contour deployment will pick up Ingresses annotated with `kubernetes.io/ingress.class: contour-external`.
 
 This provides the opportunity to deploy the Argo CD UI privately but still allow for SSO callbacks to succeed.
 
diff --git a/pkg/apiclient/repository/repository.pb.go b/pkg/apiclient/repository/repository.pb.go
index 6ce4bf9f1..dfd466ecd 100644
--- a/pkg/apiclient/repository/repository.pb.go
+++ b/pkg/apiclient/repository/repository.pb.go
@@ -39,6 +39,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 type RepoAppsQuery struct {
 	Repo                 string   `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
 	Revision             string   `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
+	AppName              string   `protobuf:"bytes,3,opt,name=appName,proto3" json:"appName,omitempty"`
+	AppProject           string   `protobuf:"bytes,4,opt,name=appProject,proto3" json:"appProject,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -91,6 +93,20 @@ func (m *RepoAppsQuery) GetRevision() string {
 	return ""
 }
 
+func (m *RepoAppsQuery) GetAppName() string {
+	if m != nil {
+		return m.AppName
+	}
+	return ""
+}
+
+func (m *RepoAppsQuery) GetAppProject() string {
+	if m != nil {
+		return m.AppProject
+	}
+	return ""
+}
+
 // AppInfo contains application type and app file path
 type AppInfo struct {
 	Type                 string   `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
@@ -151,6 +167,7 @@ func (m *AppInfo) GetPath() string {
 type RepoAppDetailsQuery struct {
 	Source               *v1alpha1.ApplicationSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"`
 	AppName              string                      `protobuf:"bytes,2,opt,name=appName,proto3" json:"appName,omitempty"`
+	AppProject           string                      `protobuf:"bytes,3,opt,name=appProject,proto3" json:"appProject,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}                    `json:"-"`
 	XXX_unrecognized     []byte                      `json:"-"`
 	XXX_sizecache        int32                       `json:"-"`
@@ -203,6 +220,13 @@ func (m *RepoAppDetailsQuery) GetAppName() string {
 	return ""
 }
 
+func (m *RepoAppDetailsQuery) GetAppProject() string {
+	if m != nil {
+		return m.AppProject
+	}
+	return ""
+}
+
 // RepoAppsResponse contains applications of specified repository
 type RepoAppsResponse struct {
 	Items                []*AppInfo `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
@@ -663,77 +687,78 @@ func init() {
 }
 
 var fileDescriptor_8d38260443475705 = []byte{
-	// 1105 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x6f, 0x1b, 0x45,
-	0x10, 0xd7, 0xe5, 0x8f, 0x93, 0x6c, 0xfe, 0xd4, 0xd9, 0x84, 0x72, 0xb8, 0x69, 0x1a, 0x6d, 0x4b,
-	0x15, 0xa2, 0x72, 0xd7, 0x18, 0x21, 0xaa, 0x22, 0x40, 0x69, 0x12, 0xb5, 0x11, 0x11, 0x81, 0xab,
-	0xc2, 0x03, 0x02, 0xa1, 0xcd, 0x79, 0x62, 0x1f, 0x39, 0xdf, 0x6e, 0x77, 0xd7, 0x06, 0xab, 0xea,
-	0x0b, 0x4f, 0x48, 0xf0, 0x82, 0x10, 0x52, 0xdf, 0x78, 0x41, 0xe2, 0x81, 0xcf, 0xc0, 0x3b, 0x8f,
-	0x48, 0x7c, 0x01, 0x14, 0xf1, 0x39, 0x10, 0xda, 0xdd, 0xf3, 0xdd, 0x39, 0xb1, 0x9d, 0x54, 0x84,
-	0xbc, 0xed, 0xfc, 0x66, 0x76, 0xe6, 0x37, 0xe3, 0x99, 0x59, 0x1f, 0x22, 0x12, 0x44, 0x1b, 0x84,
-	0x2f, 0x80, 0x33, 0x19, 0x29, 0x26, 0x3a, 0x85, 0xa3, 0xc7, 0x05, 0x53, 0x0c, 0xa3, 0x1c, 0xa9,
-	0x2c, 0xd6, 0x59, 0x9d, 0x19, 0xd8, 0xd7, 0x27, 0x6b, 0x51, 0x59, 0xaa, 0x33, 0x56, 0x8f, 0xc1,
-	0xa7, 0x3c, 0xf2, 0x69, 0x92, 0x30, 0x45, 0x55, 0xc4, 0x12, 0x99, 0x6a, 0xc9, 0xd1, 0x3d, 0xe9,
-	0x45, 0xcc, 0x68, 0x43, 0x26, 0xc0, 0x6f, 0xaf, 0xfb, 0x75, 0x48, 0x40, 0x50, 0x05, 0xb5, 0xd4,
-	0x66, 0xb7, 0x1e, 0xa9, 0x46, 0xeb, 0xc0, 0x0b, 0x59, 0xd3, 0xa7, 0xc2, 0x84, 0xf8, 0xc2, 0x1c,
-	0x5e, 0x0f, 0x6b, 0x7e, 0xbb, 0xea, 0xf3, 0xa3, 0xba, 0xbe, 0x2f, 0x7d, 0xca, 0x79, 0x1c, 0x85,
-	0xc6, 0xbf, 0xdf, 0x5e, 0xa7, 0x31, 0x6f, 0xd0, 0xd3, 0xde, 0xb6, 0xcf, 0xf0, 0x66, 0x12, 0x3a,
-	0x33, 0x71, 0xf2, 0x1e, 0x9a, 0x0d, 0x80, 0xb3, 0x0d, 0xce, 0xe5, 0x47, 0x2d, 0x10, 0x1d, 0x8c,
-	0xd1, 0x98, 0x36, 0x72, 0x9d, 0x15, 0x67, 0x75, 0x2a, 0x30, 0x67, 0x5c, 0x41, 0x93, 0x02, 0xda,
-	0x91, 0x8c, 0x58, 0xe2, 0x8e, 0x18, 0x3c, 0x93, 0xc9, 0x3a, 0x9a, 0xd8, 0xe0, 0x7c, 0x27, 0x39,
-	0x64, 0xfa, 0xaa, 0xea, 0x70, 0xe8, 0x5e, 0xd5, 0x67, 0x8d, 0x71, 0xaa, 0x1a, 0xe9, 0x35, 0x73,
-	0x26, 0xcf, 0x1d, 0xb4, 0x90, 0x06, 0xdd, 0x02, 0x45, 0xa3, 0x38, 0x0d, 0x5d, 0x47, 0x25, 0xc9,
-	0x5a, 0x22, 0xb4, 0x1e, 0xa6, 0xab, 0x7b, 0x5e, 0x9e, 0xa3, 0xd7, 0xcd, 0xd1, 0x1c, 0x3e, 0x0f,
-	0x6b, 0x5e, 0xbb, 0xea, 0xf1, 0xa3, 0xba, 0xa7, 0x2b, 0xe6, 0x15, 0x2a, 0xe6, 0x75, 0x2b, 0xe6,
-	0x6d, 0xe4, 0xe0, 0x63, 0xe3, 0x36, 0x48, 0xdd, 0x63, 0x17, 0x4d, 0x50, 0xce, 0x3f, 0xa0, 0x4d,
-	0x48, 0x79, 0x75, 0x45, 0xf2, 0x0e, 0x2a, 0x77, 0xcb, 0x11, 0x80, 0xe4, 0x2c, 0x91, 0x80, 0x5f,
-	0x43, 0xe3, 0x91, 0x82, 0xa6, 0x74, 0x9d, 0x95, 0xd1, 0xd5, 0xe9, 0xea, 0x82, 0x57, 0x28, 0x62,
-	0x9a, 0x7a, 0x60, 0x2d, 0xc8, 0x26, 0x9a, 0xd2, 0xd7, 0x07, 0x57, 0x92, 0xa0, 0x99, 0x43, 0xa6,
-	0xa9, 0xc0, 0xa1, 0x00, 0x69, 0xcb, 0x32, 0x19, 0xf4, 0x60, 0xe4, 0xb7, 0x31, 0x74, 0xc5, 0x90,
-	0x08, 0x43, 0x90, 0xc3, 0x7f, 0x95, 0x96, 0x04, 0x91, 0xe4, 0x69, 0x64, 0xb2, 0xd6, 0x71, 0x2a,
-	0xe5, 0x97, 0x4c, 0xd4, 0xdc, 0x51, 0xab, 0xeb, 0xca, 0xf8, 0x16, 0x9a, 0x95, 0xb2, 0xf1, 0xa1,
-	0x88, 0xda, 0x54, 0xc1, 0xfb, 0xd0, 0x71, 0xc7, 0x8c, 0x41, 0x2f, 0xa8, 0x3d, 0x44, 0x89, 0x84,
-	0xb0, 0x25, 0xc0, 0x1d, 0x37, 0x2c, 0x33, 0x19, 0xdf, 0x41, 0xf3, 0x2a, 0x96, 0x9b, 0x71, 0x04,
-	0x89, 0xda, 0x04, 0xa1, 0xb6, 0xa8, 0xa2, 0x6e, 0xc9, 0x78, 0x39, 0xad, 0xc0, 0x6b, 0xa8, 0xdc,
-	0x03, 0xea, 0x90, 0x13, 0xc6, 0xf8, 0x14, 0x9e, 0xb5, 0xd0, 0x54, 0x6f, 0x0b, 0x99, 0x1c, 0x91,
-	0xc5, 0x4c, 0x7e, 0x4b, 0x68, 0x0a, 0x12, 0x7a, 0x10, 0xc3, 0x5e, 0x18, 0xb9, 0xd3, 0x86, 0x5e,
-	0x0e, 0xe0, 0xbb, 0x68, 0xc1, 0x76, 0xce, 0x06, 0xe7, 0x85, 0x3c, 0x67, 0x8c, 0x83, 0x7e, 0x2a,
-	0xbc, 0x82, 0xa6, 0x33, 0x78, 0x67, 0xcb, 0x9d, 0x5d, 0x71, 0x56, 0x47, 0x83, 0x22, 0x84, 0xef,
-	0xa1, 0x97, 0x73, 0x31, 0x91, 0x8a, 0xc6, 0xb1, 0x69, 0xad, 0x9d, 0x2d, 0x77, 0xce, 0x58, 0x0f,
-	0x52, 0xe3, 0x77, 0x51, 0x25, 0x53, 0x6d, 0x27, 0x0a, 0x04, 0x17, 0x91, 0x84, 0x07, 0x54, 0xc2,
-	0xbe, 0x88, 0xdd, 0x2b, 0x86, 0xd4, 0x10, 0x0b, 0xbc, 0x88, 0xc6, 0xb9, 0x60, 0x5f, 0x75, 0xdc,
-	0xb2, 0x31, 0xb5, 0x82, 0xee, 0x61, 0x3d, 0x0e, 0x10, 0x2a, 0x77, 0xde, 0xf6, 0x70, 0x2a, 0x92,
-	0x39, 0x34, 0xa3, 0xdb, 0xa7, 0xdb, 0xbf, 0xe4, 0x17, 0x07, 0xcd, 0x6b, 0x60, 0x53, 0x00, 0x55,
-	0x10, 0xc0, 0x93, 0x16, 0x48, 0x85, 0x3f, 0x2d, 0x74, 0xd4, 0x74, 0xf5, 0xd1, 0x7f, 0x1b, 0xb5,
-	0x20, 0x9b, 0x88, 0xb4, 0x37, 0xaf, 0xa2, 0x52, 0x8b, 0x4b, 0x10, 0x2a, 0xed, 0xf0, 0x54, 0xd2,
-	0xbf, 0x5b, 0x28, 0xa0, 0x26, 0xf7, 0x92, 0xb8, 0x63, 0x1a, 0x73, 0x32, 0xc8, 0x01, 0xf2, 0xc4,
-	0x12, 0xdd, 0xe7, 0xb5, 0xcb, 0x22, 0x5a, 0xfd, 0x67, 0xce, 0xc6, 0xb4, 0xe0, 0x63, 0x10, 0xed,
-	0x28, 0x04, 0xfc, 0x9d, 0x83, 0xc6, 0x76, 0x23, 0xa9, 0xf0, 0x4b, 0xc5, 0x61, 0xcf, 0x46, 0xbb,
-	0xb2, 0x7b, 0x51, 0x2c, 0x74, 0x10, 0x72, 0xe3, 0xeb, 0x3f, 0xff, 0xfe, 0x61, 0xe4, 0x2a, 0x5e,
-	0x34, 0xcf, 0x47, 0x7b, 0x3d, 0xdf, 0xd2, 0x11, 0xc8, 0x6f, 0x46, 0x1c, 0xfc, 0xad, 0x83, 0x46,
-	0x1f, 0xc2, 0x40, 0x36, 0x17, 0x56, 0x13, 0x72, 0xd3, 0x30, 0xb9, 0x8e, 0xaf, 0xf5, 0x63, 0xe2,
-	0x3f, 0xd5, 0xd2, 0x33, 0xfc, 0xa3, 0x83, 0xca, 0x9a, 0x77, 0x50, 0xd0, 0x5d, 0x4e, 0xa1, 0x96,
-	0x86, 0x15, 0x0a, 0x7f, 0x86, 0x26, 0x2d, 0xad, 0xc3, 0x81, 0x74, 0xca, 0xbd, 0xf0, 0xa1, 0x24,
-	0xab, 0xc6, 0x25, 0xc1, 0x2b, 0x43, 0x32, 0xf6, 0x85, 0x76, 0xd9, 0xb4, 0xee, 0xf5, 0xd3, 0x80,
-	0x5f, 0x39, 0xe9, 0x3e, 0x7b, 0x3f, 0x2b, 0x4b, 0xfd, 0x54, 0xd9, 0x2c, 0x9e, 0x2b, 0x1c, 0xd5,
-	0x21, 0xbe, 0x77, 0xd0, 0xec, 0x43, 0x50, 0xf9, 0x1b, 0x89, 0x6f, 0xf4, 0xf1, 0x5c, 0x7c, 0x3f,
-	0x2b, 0x64, 0xb0, 0x41, 0x46, 0xe0, 0x6d, 0x43, 0xe0, 0x4d, 0x72, 0xb7, 0x3f, 0x01, 0xfb, 0x40,
-	0x1a, 0x3f, 0xfb, 0xc1, 0xae, 0xa1, 0x52, 0xb3, 0x1e, 0xee, 0x3b, 0x6b, 0xb8, 0x6d, 0x28, 0x3d,
-	0x82, 0xb8, 0xb9, 0xd9, 0xa0, 0x42, 0x0d, 0x2c, 0xf3, 0x72, 0x11, 0xce, 0xcd, 0x33, 0x12, 0x9e,
-	0x21, 0xb1, 0x8a, 0x6f, 0x0f, 0xab, 0x42, 0x03, 0xe2, 0x66, 0x68, 0xc3, 0x3c, 0x77, 0x50, 0xc9,
-	0x6e, 0x2f, 0x7c, 0xfd, 0x64, 0xc4, 0x9e, 0xad, 0x76, 0x81, 0xa3, 0xf0, 0xaa, 0xe1, 0xb8, 0x44,
-	0xfa, 0xf6, 0xda, 0x7d, 0xb3, 0x3c, 0xf4, 0x68, 0xfe, 0xe4, 0xa0, 0x72, 0x97, 0x42, 0xf7, 0xee,
-	0xe5, 0x91, 0x24, 0x67, 0x93, 0xc4, 0x3f, 0x3b, 0xa8, 0x64, 0x37, 0xea, 0x69, 0x5e, 0x3d, 0x9b,
-	0xf6, 0x02, 0x79, 0xad, 0xdb, 0x1f, 0xb8, 0x32, 0xa4, 0xcd, 0x0d, 0x95, 0x67, 0x79, 0x21, 0x7f,
-	0x75, 0x50, 0xb9, 0x4b, 0x67, 0x70, 0x21, 0xff, 0x2f, 0xc2, 0xde, 0x8b, 0x11, 0xc6, 0x14, 0x95,
-	0xb6, 0x20, 0x06, 0x05, 0x83, 0x46, 0xc0, 0x3d, 0x09, 0x67, 0xcd, 0x7f, 0xdb, 0xee, 0xd8, 0xb5,
-	0x61, 0x3b, 0x56, 0x17, 0xa4, 0x81, 0xca, 0x36, 0x44, 0xa1, 0x1e, 0x2f, 0x1c, 0xec, 0xe6, 0x39,
-	0x82, 0xe1, 0xa7, 0x68, 0xee, 0x63, 0x1a, 0x47, 0xba, 0xb2, 0xf6, 0x3f, 0x27, 0xbe, 0x76, 0x6a,
-	0x93, 0xe4, 0xff, 0x45, 0x87, 0x44, 0xab, 0x9a, 0x68, 0x77, 0xc8, 0xad, 0x61, 0x73, 0xdd, 0x4e,
-	0x43, 0xd9, 0x4a, 0x3e, 0xd8, 0xfe, 0xfd, 0x78, 0xd9, 0xf9, 0xe3, 0x78, 0xd9, 0xf9, 0xeb, 0x78,
-	0xd9, 0xf9, 0xe4, 0xad, 0xf3, 0x7d, 0x23, 0x85, 0xe6, 0x4f, 0x63, 0xe1, 0x6b, 0xe6, 0xa0, 0x64,
-	0x3e, 0x67, 0xde, 0xf8, 0x37, 0x00, 0x00, 0xff, 0xff, 0x38, 0x35, 0xe9, 0x0a, 0xed, 0x0d, 0x00,
-	0x00,
+	// 1129 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4d, 0x6f, 0x1c, 0x45,
+	0x13, 0xd6, 0xf8, 0x63, 0x6d, 0xb7, 0x3f, 0xb2, 0x6e, 0xfb, 0xcd, 0x3b, 0x6c, 0x1c, 0xc7, 0x9a,
+	0x84, 0xc8, 0x58, 0x61, 0x26, 0x5e, 0x84, 0x88, 0x82, 0x40, 0x72, 0x6c, 0x2b, 0xb1, 0xb0, 0x70,
+	0x98, 0xc8, 0x1c, 0x10, 0x08, 0xb5, 0x67, 0x6b, 0x77, 0x27, 0x9e, 0x9d, 0xee, 0x74, 0xf7, 0x0e,
+	0xac, 0xa2, 0x5c, 0x38, 0x21, 0xc1, 0x05, 0x21, 0x24, 0x6e, 0x5c, 0x90, 0x38, 0xf0, 0x07, 0xb8,
+	0x70, 0xe7, 0x88, 0xc4, 0x1f, 0x40, 0x16, 0xbf, 0x03, 0xa1, 0xee, 0x9e, 0x9d, 0x99, 0xf5, 0x7e,
+	0xd8, 0x11, 0xc6, 0xb7, 0xae, 0xa7, 0x6a, 0xab, 0x9e, 0x7a, 0xba, 0xba, 0x7b, 0x07, 0x39, 0x02,
+	0x78, 0x02, 0xdc, 0xe3, 0xc0, 0xa8, 0x08, 0x25, 0xe5, 0x9d, 0xc2, 0xd2, 0x65, 0x9c, 0x4a, 0x8a,
+	0x51, 0x8e, 0x54, 0x96, 0x1b, 0xb4, 0x41, 0x35, 0xec, 0xa9, 0x95, 0x89, 0xa8, 0xac, 0x34, 0x28,
+	0x6d, 0x44, 0xe0, 0x11, 0x16, 0x7a, 0x24, 0x8e, 0xa9, 0x24, 0x32, 0xa4, 0xb1, 0x48, 0xbd, 0xce,
+	0xf1, 0x3d, 0xe1, 0x86, 0x54, 0x7b, 0x03, 0xca, 0xc1, 0x4b, 0x36, 0xbd, 0x06, 0xc4, 0xc0, 0x89,
+	0x84, 0x5a, 0x1a, 0xb3, 0xdf, 0x08, 0x65, 0xb3, 0x7d, 0xe4, 0x06, 0xb4, 0xe5, 0x11, 0xae, 0x4b,
+	0x3c, 0xd5, 0x8b, 0xd7, 0x83, 0x9a, 0x97, 0x54, 0x3d, 0x76, 0xdc, 0x50, 0xbf, 0x17, 0x1e, 0x61,
+	0x2c, 0x0a, 0x03, 0x9d, 0xdf, 0x4b, 0x36, 0x49, 0xc4, 0x9a, 0xa4, 0x3f, 0xdb, 0xee, 0x19, 0xd9,
+	0x74, 0x43, 0x67, 0x36, 0xee, 0x74, 0xd0, 0xbc, 0x0f, 0x8c, 0x6e, 0x31, 0x26, 0x3e, 0x68, 0x03,
+	0xef, 0x60, 0x8c, 0x26, 0x54, 0x90, 0x6d, 0xad, 0x59, 0xeb, 0x33, 0xbe, 0x5e, 0xe3, 0x0a, 0x9a,
+	0xe6, 0x90, 0x84, 0x22, 0xa4, 0xb1, 0x3d, 0xa6, 0xf1, 0xcc, 0xc6, 0x36, 0x9a, 0x22, 0x8c, 0xbd,
+	0x4f, 0x5a, 0x60, 0x8f, 0x6b, 0x57, 0xd7, 0xc4, 0xab, 0x08, 0x11, 0xc6, 0x1e, 0x73, 0xfa, 0x14,
+	0x02, 0x69, 0x4f, 0x68, 0x67, 0x01, 0x71, 0x36, 0xd1, 0xd4, 0x16, 0x63, 0x7b, 0x71, 0x9d, 0xaa,
+	0xa2, 0xb2, 0xc3, 0xa0, 0x5b, 0x54, 0xad, 0x15, 0xc6, 0x88, 0x6c, 0xa6, 0x05, 0xf5, 0xda, 0xf9,
+	0xc5, 0x42, 0x4b, 0x29, 0xdd, 0x1d, 0x90, 0x24, 0x8c, 0x52, 0xd2, 0x0d, 0x54, 0x12, 0xb4, 0xcd,
+	0x03, 0x93, 0x61, 0xb6, 0x7a, 0xe0, 0xe6, 0xea, 0xb8, 0x5d, 0x75, 0xf4, 0xe2, 0xd3, 0xa0, 0xe6,
+	0x26, 0x55, 0x97, 0x1d, 0x37, 0x5c, 0xa5, 0xb5, 0x5b, 0xd0, 0xda, 0xed, 0x6a, 0xed, 0x6e, 0xe5,
+	0xe0, 0x13, 0x9d, 0xd6, 0x4f, 0xd3, 0x17, 0xbb, 0x1d, 0x1b, 0xd5, 0xed, 0x78, 0x5f, 0xb7, 0xef,
+	0xa0, 0x72, 0x57, 0x68, 0x1f, 0x04, 0xa3, 0xb1, 0x00, 0xfc, 0x1a, 0x9a, 0x0c, 0x25, 0xb4, 0x84,
+	0x6d, 0xad, 0x8d, 0xaf, 0xcf, 0x56, 0x97, 0xdc, 0xc2, 0xf6, 0xa4, 0xd2, 0xf8, 0x26, 0xc2, 0xd9,
+	0x46, 0x33, 0xea, 0xe7, 0xc3, 0xf7, 0xc8, 0x41, 0x73, 0x75, 0xaa, 0xa8, 0x42, 0x9d, 0x83, 0x30,
+	0xb2, 0x4d, 0xfb, 0x3d, 0x98, 0xf3, 0xeb, 0x04, 0xba, 0xa2, 0x49, 0x04, 0x01, 0x88, 0xd1, 0xfb,
+	0xdd, 0x16, 0xc0, 0xe3, 0xbc, 0xcd, 0xcc, 0x56, 0x3e, 0x46, 0x84, 0xf8, 0x8c, 0xf2, 0x5a, 0xda,
+	0x65, 0x66, 0xe3, 0x5b, 0x68, 0x5e, 0x88, 0xe6, 0x63, 0x1e, 0x26, 0x44, 0xc2, 0x7b, 0xd0, 0x49,
+	0x37, 0xbd, 0x17, 0x54, 0x19, 0xc2, 0x58, 0x40, 0xd0, 0xe6, 0x60, 0x4f, 0x6a, 0x96, 0x99, 0x8d,
+	0xef, 0xa0, 0x45, 0x19, 0x89, 0xed, 0x28, 0x84, 0x58, 0x6e, 0x03, 0x97, 0x3b, 0x44, 0x12, 0xbb,
+	0xa4, 0xb3, 0xf4, 0x3b, 0xf0, 0x06, 0x2a, 0xf7, 0x80, 0xaa, 0xe4, 0x94, 0x0e, 0xee, 0xc3, 0xb3,
+	0x11, 0x9b, 0xe9, 0x1d, 0x31, 0xdd, 0x23, 0x32, 0x98, 0xee, 0x6f, 0x05, 0xcd, 0x40, 0x4c, 0x8e,
+	0x22, 0x38, 0x08, 0x42, 0x7b, 0x56, 0xd3, 0xcb, 0x01, 0x7c, 0x17, 0x2d, 0x99, 0xc9, 0xda, 0x52,
+	0x3b, 0x9b, 0xf5, 0x39, 0xa7, 0x13, 0x0c, 0x72, 0xe1, 0x35, 0x34, 0x9b, 0xc1, 0x7b, 0x3b, 0xf6,
+	0xfc, 0x9a, 0xb5, 0x3e, 0xee, 0x17, 0x21, 0x7c, 0x0f, 0xfd, 0x3f, 0x37, 0x63, 0x21, 0x49, 0x14,
+	0xe9, 0xd1, 0xdb, 0xdb, 0xb1, 0x17, 0x74, 0xf4, 0x30, 0x37, 0x7e, 0x17, 0x55, 0x32, 0xd7, 0x6e,
+	0x2c, 0x81, 0x33, 0x1e, 0x0a, 0x78, 0x40, 0x04, 0x1c, 0xf2, 0xc8, 0xbe, 0xa2, 0x49, 0x8d, 0x88,
+	0xc0, 0xcb, 0x68, 0x92, 0x71, 0xfa, 0x79, 0xc7, 0x2e, 0xeb, 0x50, 0x63, 0xa8, 0x19, 0x67, 0xe9,
+	0x18, 0x2f, 0x9a, 0x19, 0x4f, 0x4d, 0x67, 0x01, 0xcd, 0xa9, 0xf1, 0xe9, 0xce, 0xaf, 0xf3, 0x93,
+	0x85, 0x16, 0x15, 0xb0, 0xcd, 0x81, 0x48, 0xf0, 0xe1, 0x59, 0x1b, 0x84, 0xc4, 0x1f, 0x17, 0x26,
+	0x6a, 0xb6, 0xfa, 0xe8, 0xdf, 0x1d, 0x45, 0x3f, 0x3b, 0x11, 0xe9, 0x6c, 0x5e, 0x45, 0xa5, 0x36,
+	0x13, 0xc0, 0x65, 0x3a, 0xe1, 0xa9, 0xa5, 0xf6, 0x2d, 0xe0, 0x50, 0x13, 0x07, 0x71, 0xd4, 0xd1,
+	0x83, 0x39, 0xed, 0xe7, 0x80, 0xf3, 0xcc, 0x10, 0x3d, 0x64, 0xb5, 0xcb, 0x22, 0x5a, 0xfd, 0x7b,
+	0xc1, 0xd4, 0x34, 0xe0, 0x13, 0xe0, 0x49, 0x18, 0x00, 0xfe, 0xda, 0x42, 0x13, 0xfb, 0xa1, 0x90,
+	0xf8, 0x7f, 0xc5, 0xc3, 0x9e, 0x1d, 0xed, 0xca, 0xfe, 0x45, 0xb1, 0x50, 0x45, 0x9c, 0x1b, 0x5f,
+	0xfc, 0xf1, 0xd7, 0xb7, 0x63, 0x57, 0xf1, 0xb2, 0x7e, 0x98, 0x92, 0xcd, 0xfc, 0xfe, 0x0f, 0x41,
+	0x7c, 0x39, 0x66, 0xe1, 0xaf, 0x2c, 0x34, 0xfe, 0x10, 0x86, 0xb2, 0xb9, 0x30, 0x4d, 0x9c, 0x9b,
+	0x9a, 0xc9, 0x75, 0x7c, 0x6d, 0x10, 0x13, 0xef, 0xb9, 0xb2, 0x5e, 0xe0, 0xef, 0x2c, 0x54, 0x56,
+	0xbc, 0xfd, 0x82, 0xef, 0x72, 0x84, 0x5a, 0x19, 0x25, 0x14, 0xfe, 0x04, 0x4d, 0x1b, 0x5a, 0xf5,
+	0xa1, 0x74, 0xca, 0xbd, 0x70, 0x5d, 0x38, 0xeb, 0x3a, 0xa5, 0x83, 0xd7, 0x46, 0x74, 0xec, 0x71,
+	0x95, 0xb2, 0x65, 0xd2, 0xab, 0xa7, 0x01, 0xbf, 0x72, 0x3a, 0x7d, 0xf6, 0x32, 0x57, 0x56, 0x06,
+	0xb9, 0xb2, 0xb3, 0x78, 0xae, 0x72, 0x44, 0x95, 0xf8, 0xc6, 0x42, 0xf3, 0x0f, 0x41, 0xe6, 0x6f,
+	0x28, 0xbe, 0x31, 0x20, 0x73, 0xf1, 0x7d, 0xad, 0x38, 0xc3, 0x03, 0x32, 0x02, 0x6f, 0x6b, 0x02,
+	0x6f, 0x3a, 0x77, 0x07, 0x13, 0x30, 0x0f, 0xa8, 0xce, 0x73, 0xe8, 0xef, 0x6b, 0x2a, 0x35, 0x93,
+	0xe1, 0xbe, 0xb5, 0x81, 0x13, 0x4d, 0xe9, 0x11, 0x44, 0xad, 0xed, 0x26, 0xe1, 0x72, 0xa8, 0xcc,
+	0xab, 0x45, 0x38, 0x0f, 0xcf, 0x48, 0xb8, 0x9a, 0xc4, 0x3a, 0xbe, 0x3d, 0x4a, 0x85, 0x26, 0x44,
+	0xad, 0xc0, 0x94, 0xf9, 0xde, 0x42, 0x25, 0x73, 0x7b, 0xe1, 0xeb, 0xa7, 0x2b, 0xf6, 0xdc, 0x6a,
+	0x17, 0x78, 0x14, 0x5e, 0xd5, 0x1c, 0x57, 0x9c, 0x81, 0xb3, 0x76, 0x5f, 0x5f, 0x1e, 0xea, 0x68,
+	0xfe, 0x60, 0xa1, 0x72, 0x97, 0x42, 0xf7, 0xb7, 0x97, 0x47, 0xd2, 0x39, 0x9b, 0x24, 0xfe, 0xd1,
+	0x42, 0x25, 0x73, 0xa3, 0xf6, 0xf3, 0xea, 0xb9, 0x69, 0x2f, 0x90, 0xd7, 0xa6, 0xd9, 0xe0, 0xca,
+	0x88, 0x31, 0xd7, 0x54, 0x5e, 0xe4, 0x42, 0xfe, 0x6c, 0xa1, 0x72, 0x97, 0xce, 0x70, 0x21, 0xff,
+	0x2b, 0xc2, 0xee, 0xcb, 0x11, 0xc6, 0x04, 0x95, 0x76, 0x20, 0x02, 0x09, 0xc3, 0x8e, 0x80, 0x7d,
+	0x1a, 0xce, 0x86, 0xff, 0xb6, 0xb9, 0x63, 0x37, 0x46, 0xdd, 0xb1, 0x4a, 0x90, 0x26, 0x2a, 0x9b,
+	0x12, 0x05, 0x3d, 0x5e, 0xba, 0xd8, 0xcd, 0x73, 0x14, 0xc3, 0xcf, 0xd1, 0xc2, 0x87, 0x24, 0x0a,
+	0x95, 0xb2, 0xe6, 0x3f, 0x27, 0xbe, 0xd6, 0x77, 0x93, 0xe4, 0xff, 0x45, 0x47, 0x54, 0xab, 0xea,
+	0x6a, 0x77, 0x9c, 0x5b, 0xa3, 0xce, 0x75, 0x92, 0x96, 0x32, 0x4a, 0x3e, 0xd8, 0xfd, 0xed, 0x64,
+	0xd5, 0xfa, 0xfd, 0x64, 0xd5, 0xfa, 0xf3, 0x64, 0xd5, 0xfa, 0xe8, 0xad, 0xf3, 0x7d, 0x7d, 0x05,
+	0xfa, 0x4f, 0x63, 0xe1, 0x3b, 0xe9, 0xa8, 0xa4, 0x3f, 0x94, 0xde, 0xf8, 0x27, 0x00, 0x00, 0xff,
+	0xff, 0x52, 0x69, 0xb3, 0xbe, 0x47, 0x0e, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -1338,6 +1363,20 @@ func (m *RepoAppsQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if len(m.AppProject) > 0 {
+		i -= len(m.AppProject)
+		copy(dAtA[i:], m.AppProject)
+		i = encodeVarintRepository(dAtA, i, uint64(len(m.AppProject)))
+		i--
+		dAtA[i] = 0x22
+	}
+	if len(m.AppName) > 0 {
+		i -= len(m.AppName)
+		copy(dAtA[i:], m.AppName)
+		i = encodeVarintRepository(dAtA, i, uint64(len(m.AppName)))
+		i--
+		dAtA[i] = 0x1a
+	}
 	if len(m.Revision) > 0 {
 		i -= len(m.Revision)
 		copy(dAtA[i:], m.Revision)
@@ -1420,6 +1459,13 @@ func (m *RepoAppDetailsQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if len(m.AppProject) > 0 {
+		i -= len(m.AppProject)
+		copy(dAtA[i:], m.AppProject)
+		i = encodeVarintRepository(dAtA, i, uint64(len(m.AppProject)))
+		i--
+		dAtA[i] = 0x1a
+	}
 	if len(m.AppName) > 0 {
 		i -= len(m.AppName)
 		copy(dAtA[i:], m.AppName)
@@ -1822,6 +1868,14 @@ func (m *RepoAppsQuery) Size() (n int) {
 	if l > 0 {
 		n += 1 + l + sovRepository(uint64(l))
 	}
+	l = len(m.AppName)
+	if l > 0 {
+		n += 1 + l + sovRepository(uint64(l))
+	}
+	l = len(m.AppProject)
+	if l > 0 {
+		n += 1 + l + sovRepository(uint64(l))
+	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
 	}
@@ -1862,6 +1916,10 @@ func (m *RepoAppDetailsQuery) Size() (n int) {
 	if l > 0 {
 		n += 1 + l + sovRepository(uint64(l))
 	}
+	l = len(m.AppProject)
+	if l > 0 {
+		n += 1 + l + sovRepository(uint64(l))
+	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
 	}
@@ -2126,6 +2184,70 @@ func (m *RepoAppsQuery) Unmarshal(dAtA []byte) error {
 			}
 			m.Revision = string(dAtA[iNdEx:postIndex])
 			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AppName", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRepository
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRepository
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthRepository
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.AppName = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AppProject", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRepository
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRepository
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthRepository
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.AppProject = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipRepository(dAtA[iNdEx:])
@@ -2360,6 +2482,38 @@ func (m *RepoAppDetailsQuery) Unmarshal(dAtA []byte) error {
 			}
 			m.AppName = string(dAtA[iNdEx:postIndex])
 			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AppProject", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRepository
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthRepository
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthRepository
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.AppProject = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipRepository(dAtA[iNdEx:])
diff --git a/pkg/apis/application/v1alpha1/repository_types.go b/pkg/apis/application/v1alpha1/repository_types.go
index dc54c604b..862627f21 100644
--- a/pkg/apis/application/v1alpha1/repository_types.go
+++ b/pkg/apis/application/v1alpha1/repository_types.go
@@ -166,18 +166,18 @@ func (repo *Repository) CopyCredentialsFrom(source *RepoCreds) {
 }
 
 // GetGitCreds returns the credentials from a repository configuration used to authenticate at a Git repository
-func (repo *Repository) GetGitCreds() git.Creds {
+func (repo *Repository) GetGitCreds(store git.CredsStore) git.Creds {
 	if repo == nil {
 		return git.NopCreds{}
 	}
 	if repo.Password != "" {
-		return git.NewHTTPSCreds(repo.Username, repo.Password, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy)
+		return git.NewHTTPSCreds(repo.Username, repo.Password, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy, store)
 	}
 	if repo.SSHPrivateKey != "" {
-		return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure())
+		return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure(), store)
 	}
 	if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 && repo.GithubAppInstallationId != 0 {
-		return git.NewGitHubAppCreds(repo.GithubAppId, repo.GithubAppInstallationId, repo.GithubAppPrivateKey, repo.GitHubAppEnterpriseBaseURL, repo.Repo, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure())
+		return git.NewGitHubAppCreds(repo.GithubAppId, repo.GithubAppInstallationId, repo.GithubAppPrivateKey, repo.GitHubAppEnterpriseBaseURL, repo.Repo, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), store)
 	}
 	return git.NopCreds{}
 }
diff --git a/reposerver/askpass/askpass.pb.go b/reposerver/askpass/askpass.pb.go
new file mode 100644
index 000000000..d1d2a4612
--- /dev/null
+++ b/reposerver/askpass/askpass.pb.go
@@ -0,0 +1,646 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: reposerver/askpass/askpass.proto
+
+package askpass
+
+import (
+	context "context"
+	fmt "fmt"
+	proto "github.com/gogo/protobuf/proto"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type CredentialsRequest struct {
+	Nonce                string   `protobuf:"bytes,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CredentialsRequest) Reset()         { *m = CredentialsRequest{} }
+func (m *CredentialsRequest) String() string { return proto.CompactTextString(m) }
+func (*CredentialsRequest) ProtoMessage()    {}
+func (*CredentialsRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_099f282cab154dba, []int{0}
+}
+func (m *CredentialsRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *CredentialsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_CredentialsRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *CredentialsRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CredentialsRequest.Merge(m, src)
+}
+func (m *CredentialsRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *CredentialsRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_CredentialsRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CredentialsRequest proto.InternalMessageInfo
+
+func (m *CredentialsRequest) GetNonce() string {
+	if m != nil {
+		return m.Nonce
+	}
+	return ""
+}
+
+type CredentialsResponse struct {
+	Username             string   `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
+	Password             string   `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CredentialsResponse) Reset()         { *m = CredentialsResponse{} }
+func (m *CredentialsResponse) String() string { return proto.CompactTextString(m) }
+func (*CredentialsResponse) ProtoMessage()    {}
+func (*CredentialsResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_099f282cab154dba, []int{1}
+}
+func (m *CredentialsResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *CredentialsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_CredentialsResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *CredentialsResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CredentialsResponse.Merge(m, src)
+}
+func (m *CredentialsResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *CredentialsResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CredentialsResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CredentialsResponse proto.InternalMessageInfo
+
+func (m *CredentialsResponse) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+func (m *CredentialsResponse) GetPassword() string {
+	if m != nil {
+		return m.Password
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*CredentialsRequest)(nil), "askpass.CredentialsRequest")
+	proto.RegisterType((*CredentialsResponse)(nil), "askpass.CredentialsResponse")
+}
+
+func init() { proto.RegisterFile("reposerver/askpass/askpass.proto", fileDescriptor_099f282cab154dba) }
+
+var fileDescriptor_099f282cab154dba = []byte{
+	// 231 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x28, 0x4a, 0x2d, 0xc8,
+	0x2f, 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0xd2, 0x4f, 0x2c, 0xce, 0x2e, 0x48, 0x2c, 0x2e, 0x86, 0xd1,
+	0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xec, 0x50, 0xae, 0x92, 0x16, 0x97, 0x90, 0x73, 0x51,
+	0x6a, 0x4a, 0x6a, 0x5e, 0x49, 0x66, 0x62, 0x4e, 0x71, 0x50, 0x6a, 0x61, 0x69, 0x6a, 0x71, 0x89,
+	0x90, 0x08, 0x17, 0x6b, 0x5e, 0x7e, 0x5e, 0x72, 0xaa, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10,
+	0x84, 0xa3, 0xe4, 0xcb, 0x25, 0x8c, 0xa2, 0xb6, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0x48, 0x8a,
+	0x8b, 0xa3, 0xb4, 0x38, 0xb5, 0x28, 0x2f, 0x31, 0x17, 0xa6, 0x1e, 0xce, 0x07, 0xc9, 0x81, 0xac,
+	0x29, 0xcf, 0x2f, 0x4a, 0x91, 0x60, 0x82, 0xc8, 0xc1, 0xf8, 0x46, 0xf1, 0x5c, 0x7c, 0x8e, 0xc5,
+	0xd9, 0x01, 0x89, 0xc5, 0xc5, 0xc1, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0xbe, 0x5c, 0x7c,
+	0xee, 0xa9, 0x25, 0x48, 0x76, 0x08, 0x49, 0xeb, 0xc1, 0xdc, 0x8d, 0xe9, 0x4a, 0x29, 0x19, 0xec,
+	0x92, 0x10, 0x67, 0x29, 0x31, 0x38, 0xd9, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3,
+	0x83, 0x47, 0x72, 0x8c, 0x51, 0x86, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9,
+	0xfa, 0x89, 0x45, 0xe9, 0xf9, 0x05, 0x45, 0xf9, 0x59, 0x60, 0x86, 0x6e, 0x72, 0x8a, 0x7e, 0x99,
+	0x91, 0x3e, 0x66, 0x98, 0x25, 0xb1, 0x81, 0x03, 0xcb, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x5a,
+	0x1e, 0xa9, 0xaf, 0x50, 0x01, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// AskPassServiceClient is the client API for AskPassService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type AskPassServiceClient interface {
+	GetCredentials(ctx context.Context, in *CredentialsRequest, opts ...grpc.CallOption) (*CredentialsResponse, error)
+}
+
+type askPassServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewAskPassServiceClient(cc *grpc.ClientConn) AskPassServiceClient {
+	return &askPassServiceClient{cc}
+}
+
+func (c *askPassServiceClient) GetCredentials(ctx context.Context, in *CredentialsRequest, opts ...grpc.CallOption) (*CredentialsResponse, error) {
+	out := new(CredentialsResponse)
+	err := c.cc.Invoke(ctx, "/askpass.AskPassService/GetCredentials", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// AskPassServiceServer is the server API for AskPassService service.
+type AskPassServiceServer interface {
+	GetCredentials(context.Context, *CredentialsRequest) (*CredentialsResponse, error)
+}
+
+// UnimplementedAskPassServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedAskPassServiceServer struct {
+}
+
+func (*UnimplementedAskPassServiceServer) GetCredentials(ctx context.Context, req *CredentialsRequest) (*CredentialsResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetCredentials not implemented")
+}
+
+func RegisterAskPassServiceServer(s *grpc.Server, srv AskPassServiceServer) {
+	s.RegisterService(&_AskPassService_serviceDesc, srv)
+}
+
+func _AskPassService_GetCredentials_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CredentialsRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(AskPassServiceServer).GetCredentials(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/askpass.AskPassService/GetCredentials",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(AskPassServiceServer).GetCredentials(ctx, req.(*CredentialsRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _AskPassService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "askpass.AskPassService",
+	HandlerType: (*AskPassServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "GetCredentials",
+			Handler:    _AskPassService_GetCredentials_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "reposerver/askpass/askpass.proto",
+}
+
+func (m *CredentialsRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *CredentialsRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *CredentialsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Nonce) > 0 {
+		i -= len(m.Nonce)
+		copy(dAtA[i:], m.Nonce)
+		i = encodeVarintAskpass(dAtA, i, uint64(len(m.Nonce)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *CredentialsResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *CredentialsResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *CredentialsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Password) > 0 {
+		i -= len(m.Password)
+		copy(dAtA[i:], m.Password)
+		i = encodeVarintAskpass(dAtA, i, uint64(len(m.Password)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.Username) > 0 {
+		i -= len(m.Username)
+		copy(dAtA[i:], m.Username)
+		i = encodeVarintAskpass(dAtA, i, uint64(len(m.Username)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintAskpass(dAtA []byte, offset int, v uint64) int {
+	offset -= sovAskpass(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *CredentialsRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Nonce)
+	if l > 0 {
+		n += 1 + l + sovAskpass(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *CredentialsResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Username)
+	if l > 0 {
+		n += 1 + l + sovAskpass(uint64(l))
+	}
+	l = len(m.Password)
+	if l > 0 {
+		n += 1 + l + sovAskpass(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func sovAskpass(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozAskpass(x uint64) (n int) {
+	return sovAskpass(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *CredentialsRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowAskpass
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: CredentialsRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: CredentialsRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAskpass
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Nonce = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipAskpass(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *CredentialsResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowAskpass
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: CredentialsResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: CredentialsResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Username", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAskpass
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Username = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Password", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAskpass
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Password = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipAskpass(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthAskpass
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipAskpass(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowAskpass
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowAskpass
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowAskpass
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthAskpass
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupAskpass
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthAskpass
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthAskpass        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowAskpass          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupAskpass = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/reposerver/askpass/askpass.proto b/reposerver/askpass/askpass.proto
new file mode 100644
index 000000000..4547edc3a
--- /dev/null
+++ b/reposerver/askpass/askpass.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+option go_package = "github.com/argoproj/argo-cd/v2/reposerver/askpass";
+
+package askpass;
+
+message CredentialsRequest {
+  string nonce = 1;
+}
+
+message CredentialsResponse {
+  string username = 1;
+  string password = 2;
+}
+
+
+// AskPassService
+service AskPassService {
+  rpc GetCredentials(CredentialsRequest) returns (CredentialsResponse) {
+  }
+}
diff --git a/reposerver/askpass/common.go b/reposerver/askpass/common.go
new file mode 100644
index 000000000..3f30cc0f2
--- /dev/null
+++ b/reposerver/askpass/common.go
@@ -0,0 +1,10 @@
+package askpass
+
+var (
+	SocketPath = "/tmp/reposerver-ask-pass.sock"
+)
+
+type Creds struct {
+	Username string
+	Password string
+}
diff --git a/reposerver/askpass/server.go b/reposerver/askpass/server.go
new file mode 100644
index 000000000..c34e3c332
--- /dev/null
+++ b/reposerver/askpass/server.go
@@ -0,0 +1,90 @@
+package askpass
+
+import (
+	"context"
+	"net"
+	"os"
+	"sync"
+
+	"github.com/google/uuid"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+
+	"github.com/argoproj/argo-cd/v2/util/git"
+	"github.com/argoproj/argo-cd/v2/util/io"
+)
+
+type Server interface {
+	git.CredsStore
+	AskPassServiceServer
+	Run(path string) error
+}
+
+type server struct {
+	lock  sync.Mutex
+	creds map[string]Creds
+}
+
+// NewServer returns a new server
+func NewServer() *server {
+	return &server{
+		creds: make(map[string]Creds),
+	}
+}
+
+func (s *server) GetCredentials(_ context.Context, q *CredentialsRequest) (*CredentialsResponse, error) {
+	if q.Nonce == "" {
+		return nil, status.Errorf(codes.InvalidArgument, "missing nonce")
+	}
+	creds, ok := s.getCreds(q.Nonce)
+	if !ok {
+		return nil, status.Errorf(codes.NotFound, "unknown nonce")
+	}
+	return &CredentialsResponse{Username: creds.Username, Password: creds.Password}, nil
+}
+
+func (s *server) Start(path string) (io.Closer, error) {
+	_ = os.Remove(path)
+	listener, err := net.Listen("unix", path)
+	if err != nil {
+		return nil, err
+	}
+	server := grpc.NewServer()
+	RegisterAskPassServiceServer(server, s)
+	go func() {
+		_ = server.Serve(listener)
+	}()
+	return io.NewCloser(listener.Close), nil
+}
+
+func (s *server) Run(path string) error {
+	_, err := s.Start(path)
+	return err
+}
+
+// Add adds a new credential to the server and returns associated id
+func (s *server) Add(username string, password string) string {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+	id := uuid.New().String()
+	s.creds[id] = Creds{
+		Username: username,
+		Password: password,
+	}
+	return id
+}
+
+// Remove removes the credential with the given id
+func (s *server) Remove(id string) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+	delete(s.creds, id)
+}
+
+func (s *server) getCreds(id string) (*Creds, bool) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+	creds, ok := s.creds[id]
+	return &creds, ok
+}
diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go
index aca744dda..82cb9fa18 100644
--- a/reposerver/repository/repository.go
+++ b/reposerver/repository/repository.go
@@ -67,6 +67,7 @@ const (
 
 // Service implements ManifestService interface
 type Service struct {
+	gitCredsStore             git.CredsStore
 	repoLock                  *repositoryLock
 	cache                     *reposervercache.Cache
 	parallelismLimitSemaphore *semaphore.Weighted
@@ -88,7 +89,7 @@ type RepoServerInitConstants struct {
 }
 
 // NewService returns a new instance of the Manifest service
-func NewService(metricsServer *metrics.MetricsServer, cache *reposervercache.Cache, initConstants RepoServerInitConstants, resourceTracking argo.ResourceTracking) *Service {
+func NewService(metricsServer *metrics.MetricsServer, cache *reposervercache.Cache, initConstants RepoServerInitConstants, resourceTracking argo.ResourceTracking, gitCredsStore git.CredsStore) *Service {
 	var parallelismLimitSemaphore *semaphore.Weighted
 	if initConstants.ParallelismLimit > 0 {
 		parallelismLimitSemaphore = semaphore.NewWeighted(initConstants.ParallelismLimit)
@@ -106,6 +107,7 @@ func NewService(metricsServer *metrics.MetricsServer, cache *reposervercache.Cac
 		},
 		initConstants: initConstants,
 		now:           time.Now,
+		gitCredsStore: gitCredsStore,
 	}
 }
 
@@ -329,7 +331,7 @@ func (s *Service) runManifestGen(ctx context.Context, repoRoot, commitSHA, cache
 	var manifestGenResult *apiclient.ManifestResponse
 	opContext, err := opContextSrc()
 	if err == nil {
-		manifestGenResult, err = GenerateManifests(ctx, opContext.appPath, repoRoot, commitSHA, q, false)
+		manifestGenResult, err = GenerateManifests(ctx, opContext.appPath, repoRoot, commitSHA, q, false, s.gitCredsStore)
 	}
 	if err != nil {
 
@@ -866,7 +868,7 @@ func getRepoCredential(repoCredentials []*v1alpha1.RepoCreds, repoURL string) *v
 }
 
 // GenerateManifests generates manifests from a path
-func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string, q *apiclient.ManifestRequest, isLocal bool) (*apiclient.ManifestResponse, error) {
+func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string, q *apiclient.ManifestRequest, isLocal bool, gitCredsStore git.CredsStore) (*apiclient.ManifestResponse, error) {
 	var targetObjs []*unstructured.Unstructured
 	var dest *v1alpha1.ApplicationDestination
 
@@ -891,13 +893,13 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
 		if q.KustomizeOptions != nil {
 			kustomizeBinary = q.KustomizeOptions.BinaryPath
 		}
-		k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(), repoURL, kustomizeBinary)
+		k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(gitCredsStore), repoURL, kustomizeBinary)
 		targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize, q.KustomizeOptions, env)
 	case v1alpha1.ApplicationSourceTypePlugin:
 		if q.ApplicationSource.Plugin != nil && q.ApplicationSource.Plugin.Name != "" {
-			targetObjs, err = runConfigManagementPlugin(appPath, repoRoot, env, q, q.Repo.GetGitCreds())
+			targetObjs, err = runConfigManagementPlugin(appPath, repoRoot, env, q, q.Repo.GetGitCreds(gitCredsStore))
 		} else {
-			targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, env, q, q.Repo.GetGitCreds())
+			targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, env, q, q.Repo.GetGitCreds(gitCredsStore))
 			if err != nil {
 				err = fmt.Errorf("plugin sidecar failed. %s", err.Error())
 			}
@@ -1419,7 +1421,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
 				return err
 			}
 		case v1alpha1.ApplicationSourceTypeKustomize:
-			if err := populateKustomizeAppDetails(res, q, opContext.appPath, commitSHA); err != nil {
+			if err := populateKustomizeAppDetails(res, q, opContext.appPath, commitSHA, s.gitCredsStore); err != nil {
 				return err
 			}
 		}
@@ -1566,13 +1568,13 @@ func findHelmValueFilesInPath(path string) ([]string, error) {
 	return result, nil
 }
 
-func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apiclient.RepoServerAppDetailsQuery, appPath string, reversion string) error {
+func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apiclient.RepoServerAppDetailsQuery, appPath string, reversion string, credsStore git.CredsStore) error {
 	res.Kustomize = &apiclient.KustomizeAppSpec{}
 	kustomizeBinary := ""
 	if q.KustomizeOptions != nil {
 		kustomizeBinary = q.KustomizeOptions.BinaryPath
 	}
-	k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(), q.Repo.Repo, kustomizeBinary)
+	k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(credsStore), q.Repo.Repo, kustomizeBinary)
 	fakeManifestRequest := apiclient.ManifestRequest{
 		AppName:           q.AppName,
 		Namespace:         "", // FIXME: omit it for now
@@ -1674,7 +1676,7 @@ func fileParameters(q *apiclient.RepoServerAppDetailsQuery) []v1alpha1.HelmFileP
 
 func (s *Service) newClient(repo *v1alpha1.Repository, opts ...git.ClientOpts) (git.Client, error) {
 	opts = append(opts, git.WithEventHandlers(metrics.NewGitClientEventHandlers(s.metricsServer)))
-	return s.newGitClient(repo.Repo, repo.GetGitCreds(), repo.IsInsecure(), repo.EnableLFS, repo.Proxy, opts...)
+	return s.newGitClient(repo.Repo, repo.GetGitCreds(s.gitCredsStore), repo.IsInsecure(), repo.EnableLFS, repo.Proxy, opts...)
 }
 
 // newClientResolveRevision is a helper to perform the common task of instantiating a git client
@@ -1776,7 +1778,7 @@ func (s *Service) TestRepository(ctx context.Context, q *apiclient.TestRepositor
 	}
 	checks := map[string]func() error{
 		"git": func() error {
-			return git.TestRepo(repo.Repo, repo.GetGitCreds(), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
+			return git.TestRepo(repo.Repo, repo.GetGitCreds(s.gitCredsStore), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
 		},
 		"helm": func() error {
 			if repo.EnableOCI {
@@ -1834,7 +1836,7 @@ func (s *Service) ResolveRevision(ctx context.Context, q *apiclient.ResolveRevis
 			AmbiguousRevision: fmt.Sprintf("%v (%v)", ambiguousRevision, version.String()),
 		}, nil
 	} else {
-		gitClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
+		gitClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(s.gitCredsStore), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
 		if err != nil {
 			return &apiclient.ResolveRevisionResponse{Revision: "", AmbiguousRevision: ""}, err
 		}
diff --git a/reposerver/server.go b/reposerver/server.go
index ecc86ca95..fb2e861c3 100644
--- a/reposerver/server.go
+++ b/reposerver/server.go
@@ -5,8 +5,6 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/argoproj/argo-cd/v2/util/argo"
-
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
@@ -24,7 +22,9 @@ import (
 	"github.com/argoproj/argo-cd/v2/reposerver/metrics"
 	"github.com/argoproj/argo-cd/v2/reposerver/repository"
 	"github.com/argoproj/argo-cd/v2/server/version"
+	"github.com/argoproj/argo-cd/v2/util/argo"
 	"github.com/argoproj/argo-cd/v2/util/env"
+	"github.com/argoproj/argo-cd/v2/util/git"
 	grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
 	tlsutil "github.com/argoproj/argo-cd/v2/util/tls"
 )
@@ -33,6 +33,7 @@ import (
 type ArgoCDRepoServer struct {
 	log           *log.Entry
 	metricsServer *metrics.MetricsServer
+	gitCredsStore git.CredsStore
 	cache         *reposervercache.Cache
 	opts          []grpc.ServerOption
 	initConstants repository.RepoServerInitConstants
@@ -42,7 +43,7 @@ type ArgoCDRepoServer struct {
 var tlsHostList []string = []string{"localhost", "reposerver"}
 
 // NewServer returns a new instance of the Argo CD Repo server
-func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cache, tlsConfCustomizer tlsutil.ConfigCustomizer, initConstants repository.RepoServerInitConstants) (*ArgoCDRepoServer, error) {
+func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cache, tlsConfCustomizer tlsutil.ConfigCustomizer, initConstants repository.RepoServerInitConstants, gitCredsStore git.CredsStore) (*ArgoCDRepoServer, error) {
 	var tlsConfig *tls.Config
 
 	// Generate or load TLS server certificates to use with this instance of
@@ -85,6 +86,7 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach
 		cache:         cache,
 		initConstants: initConstants,
 		opts:          serverOpts,
+		gitCredsStore: gitCredsStore,
 	}, nil
 }
 
@@ -94,7 +96,7 @@ func (a *ArgoCDRepoServer) CreateGRPC() *grpc.Server {
 	versionpkg.RegisterVersionServiceServer(server, version.NewServer(nil, func() (bool, error) {
 		return true, nil
 	}))
-	manifestService := repository.NewService(a.metricsServer, a.cache, a.initConstants, argo.NewResourceTracking())
+	manifestService := repository.NewService(a.metricsServer, a.cache, a.initConstants, argo.NewResourceTracking(), a.gitCredsStore)
 	apiclient.RegisterRepoServerServiceServer(server, manifestService)
 
 	healthService := health.NewServer()
diff --git a/resource_customizations/argoproj.io/CronWorkflow/health.lua b/resource_customizations/argoproj.io/CronWorkflow/health.lua
index 0843e280d..d1b736743 100644
--- a/resource_customizations/argoproj.io/CronWorkflow/health.lua
+++ b/resource_customizations/argoproj.io/CronWorkflow/health.lua
@@ -7,13 +7,11 @@ if obj.status ~= nil then
         hs.status = "Degraded"
         hs.message = condition.message
         return hs
-      end
-      if condition.type == "SubmissionError" and condition.status == "True" then
+      elseif condition.type == "SubmissionError" and condition.status == "True" then
         hs.status = "Degraded"
         hs.message = condition.message
         return hs
-      end
-      if condition.type == "Completed" and condition.status == "True" then
+      elseif condition.type == "Completed" and condition.status == "True" then
         hs.status = "Healthy"
         hs.message = condition.message
         return hs
@@ -22,6 +20,7 @@ if obj.status ~= nil then
   end
 end
 
-hs.status = "Progressing"
-hs.message = "CronWorkflow is working on executing workflows."
+hs.status = "Healthy"
+hs.message = "CronWorkflow is healthy."
+
 return hs
diff --git a/server/repository/repository.go b/server/repository/repository.go
index 6bbcfe752..8340f81c3 100644
--- a/server/repository/repository.go
+++ b/server/repository/repository.go
@@ -4,22 +4,24 @@ import (
 	"fmt"
 	"reflect"
 
-	"github.com/argoproj/argo-cd/v2/util/argo"
-
 	"github.com/argoproj/gitops-engine/pkg/utils/kube"
 	"github.com/argoproj/gitops-engine/pkg/utils/text"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+	apierr "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/argoproj/argo-cd/v2/common"
 	repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
+	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
 	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
 	servercache "github.com/argoproj/argo-cd/v2/server/cache"
 	"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
+	"github.com/argoproj/argo-cd/v2/util/argo"
 	"github.com/argoproj/argo-cd/v2/util/db"
 	"github.com/argoproj/argo-cd/v2/util/errors"
 	"github.com/argoproj/argo-cd/v2/util/io"
@@ -33,6 +35,8 @@ type Server struct {
 	repoClientset apiclient.Clientset
 	enf           *rbac.Enforcer
 	cache         *servercache.Cache
+	appLister     applisters.ApplicationNamespaceLister
+	projLister    applisters.AppProjectNamespaceLister
 	settings      *settings.SettingsManager
 }
 
@@ -42,6 +46,8 @@ func NewServer(
 	db db.ArgoDB,
 	enf *rbac.Enforcer,
 	cache *servercache.Cache,
+	appLister applisters.ApplicationNamespaceLister,
+	projLister applisters.AppProjectNamespaceLister,
 	settings *settings.SettingsManager,
 ) *Server {
 	return &Server{
@@ -49,14 +55,20 @@ func NewServer(
 		repoClientset: repoClientset,
 		enf:           enf,
 		cache:         cache,
+		appLister:     appLister,
+		projLister:    projLister,
 		settings:      settings,
 	}
 }
 
+var (
+	errPermissionDenied = status.Error(codes.PermissionDenied, "permission denied")
+)
+
 func (s *Server) getRepo(ctx context.Context, url string) (*appsv1.Repository, error) {
 	repo, err := s.db.GetRepository(ctx, url)
 	if err != nil {
-		return nil, status.Error(codes.PermissionDenied, "permission denied")
+		return nil, errPermissionDenied
 	}
 	return repo, nil
 }
@@ -213,14 +225,29 @@ func (s *Server) ListRefs(ctx context.Context, q *repositorypkg.RepoQuery) (*api
 	})
 }
 
-// ListApps returns list of apps in the repo
+// ListApps performs discovery of a git repository for potential sources of applications. Used
+// as a convenience to the UI for auto-complete.
 func (s *Server) ListApps(ctx context.Context, q *repositorypkg.RepoAppsQuery) (*repositorypkg.RepoAppsResponse, error) {
 	repo, err := s.getRepo(ctx, q.Repo)
 	if err != nil {
 		return nil, err
 	}
 
-	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
+	claims := ctx.Value("claims")
+	if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
+		return nil, err
+	}
+
+	// This endpoint causes us to clone git repos & invoke config management tooling for the purposes
+	// of app discovery. Only allow this to happen if user has privileges to create or update the
+	// application which it wants to retrieve these details for.
+	appRBACresource := fmt.Sprintf("%s/%s", q.AppProject, q.AppName)
+	if !s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, appRBACresource) &&
+		!s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, appRBACresource) {
+		return nil, errPermissionDenied
+	}
+	// Also ensure the repo is actually allowed in the project in question
+	if err := s.isRepoPermittedInProject(q.Repo, q.AppProject); err != nil {
 		return nil, err
 	}
 
@@ -245,6 +272,9 @@ func (s *Server) ListApps(ctx context.Context, q *repositorypkg.RepoAppsQuery) (
 	return &repositorypkg.RepoAppsResponse{Items: items}, nil
 }
 
+// GetAppDetails shows parameter values to various config tools (e.g. helm/kustomize values)
+// This is used by UI for parameter form fields during app create & edit pages.
+// It is also used when showing history of parameters used in previous syncs in the app history.
 func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDetailsQuery) (*apiclient.RepoAppDetailsResponse, error) {
 	if q.Source == nil {
 		return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
@@ -253,9 +283,38 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta
 	if err != nil {
 		return nil, err
 	}
-	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
+	claims := ctx.Value("claims")
+	if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
+		return nil, err
+	}
+
+	app, err := s.appLister.Get(q.AppName)
+	appRBACObj := createRBACObject(q.AppProject, q.AppName)
+	// ensure caller has read privileges to app
+	if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACObj); err != nil {
+		return nil, err
+	}
+	if apierr.IsNotFound(err) {
+		// app doesn't exist since it still is being formulated. verify they can create the app
+		// before we reveal repo details
+		if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, appRBACObj); err != nil {
+			return nil, err
+		}
+	} else {
+		// if we get here we are returning repo details of an existing app
+		if q.AppProject != app.Spec.Project {
+			return nil, errPermissionDenied
+		}
+		// verify caller is not making a request with arbitrary source values which were not in our history
+		if !isSourceInHistory(app, *q.Source) {
+			return nil, errPermissionDenied
+		}
+	}
+	// Ensure the repo is actually allowed in the project in question
+	if err := s.isRepoPermittedInProject(q.Source.RepoURL, q.AppProject); err != nil {
 		return nil, err
 	}
+
 	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
 	if err != nil {
 		return nil, err
@@ -473,3 +532,33 @@ func (s *Server) testRepo(ctx context.Context, repo *appsv1.Repository) error {
 	})
 	return err
 }
+
+func (s *Server) isRepoPermittedInProject(repo string, projName string) error {
+	proj, err := s.projLister.Get(projName)
+	if err != nil {
+		return err
+	}
+	if !proj.IsSourcePermitted(appsv1.ApplicationSource{RepoURL: repo}) {
+		return status.Errorf(codes.PermissionDenied, "repository '%s' not permitted in project '%s'", repo, projName)
+	}
+	return nil
+}
+
+// isSourceInHistory checks if the supplied application source is either our current application
+// source, or was something which we synced to previously.
+func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource) bool {
+	if source.Equals(app.Spec.Source) {
+		return true
+	}
+	// Iterate history. When comparing items in our history, use the actual synced revision to
+	// compare with the supplied source.targetRevision in the request. This is because
+	// history[].source.targetRevision is ambiguous (e.g. HEAD), whereas
+	// history[].revision will contain the explicit SHA
+	for _, h := range app.Status.History {
+		h.Source.TargetRevision = h.Revision
+		if source.Equals(h.Source) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/server/repository/repository.proto b/server/repository/repository.proto
index 87688d0b7..2635119d6 100644
--- a/server/repository/repository.proto
+++ b/server/repository/repository.proto
@@ -16,6 +16,8 @@ import "github.com/argoproj/argo-cd/v2/reposerver/repository/repository.proto";
 message RepoAppsQuery {
 	string repo = 1;
 	string revision = 2;
+	string appName = 3;
+	string appProject = 4;
 }
 
 
@@ -29,6 +31,7 @@ message AppInfo {
 message RepoAppDetailsQuery {
 	github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.ApplicationSource source = 1;
 	string appName = 2;
+	string appProject = 3;
 }
 
 // RepoAppsResponse contains applications of specified repository
diff --git a/server/server.go b/server/server.go
index 0f570842e..921f47ce0 100644
--- a/server/server.go
+++ b/server/server.go
@@ -156,6 +156,7 @@ type ArgoCDServer struct {
 	settingsMgr    *settings_util.SettingsManager
 	enf            *rbac.Enforcer
 	projInformer   cache.SharedIndexInformer
+	projLister     applisters.AppProjectNamespaceLister
 	policyEnforcer *rbacpolicy.RBACPolicyEnforcer
 	appInformer    cache.SharedIndexInformer
 	appLister      applisters.ApplicationNamespaceLister
@@ -250,6 +251,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
 		settingsMgr:      settingsMgr,
 		enf:              enf,
 		projInformer:     projInformer,
+		projLister:       projLister,
 		appInformer:      appInformer,
 		appLister:        appLister,
 		policyEnforcer:   policyEnf,
@@ -580,7 +582,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
 	db := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset)
 	kubectl := kubeutil.NewKubectl()
 	clusterService := cluster.NewServer(db, a.enf, a.Cache, kubectl)
-	repoService := repository.NewServer(a.RepoClientset, db, a.enf, a.Cache, a.settingsMgr)
+	repoService := repository.NewServer(a.RepoClientset, db, a.enf, a.Cache, a.appLister, a.projLister, a.settingsMgr)
 	repoCredsService := repocreds.NewServer(a.RepoClientset, db, a.enf, a.settingsMgr)
 	var loginRateLimiter func() (io.Closer, error)
 	if maxConcurrentLoginRequestsCount > 0 {
diff --git a/ui/package.json b/ui/package.json
index 121f7c813..489bb8b4d 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -39,7 +39,7 @@
     "react-router": "^4.3.1",
     "react-router-dom": "^4.2.2",
     "react-svg-piechart": "^2.1.1",
-    "redoc": "^2.0.0-rc.63",
+    "redoc": "^2.0.0-rc.64",
     "rxjs": "^6.6.6",
     "superagent": "^3.8.2",
     "superagent-promise": "^1.1.0",
diff --git a/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx b/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx
index 95af691ed..cd60efbe3 100644
--- a/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx
+++ b/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx
@@ -281,7 +281,7 @@ export const ApplicationCreatePanel = (props: {
                                                                 load={async src =>
                                                                     (src.repoURL &&
                                                                         services.repos
-                                                                            .apps(src.repoURL, src.revision)
+                                                                            .apps(src.repoURL, src.revision, app.metadata.name, app.spec.project)
                                                                             .then(apps => Array.from(new Set(apps.map(item => item.path))).sort())
                                                                             .catch(() => new Array<string>())) ||
                                                                     new Array<string>()
@@ -421,7 +421,7 @@ export const ApplicationCreatePanel = (props: {
                                                 }}
                                                 load={async src => {
                                                     if (src.repoURL && src.targetRevision && (src.path || src.chart)) {
-                                                        return services.repos.appDetails(src, src.appName).catch(() => ({
+                                                        return services.repos.appDetails(src, src.appName, app.spec.project).catch(() => ({
                                                             type: 'Directory',
                                                             details: {}
                                                         }));
diff --git a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx
index ad02b9423..c78c631ad 100644
--- a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx
+++ b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx
@@ -80,7 +80,7 @@ export const ApplicationDeploymentHistory = ({
                                 />
                                 <DataLoader
                                     input={{...recentDeployments[index].source, targetRevision: recentDeployments[index].revision, appName: app.metadata.name}}
-                                    load={src => services.repos.appDetails(src, src.appName)}>
+                                    load={src => services.repos.appDetails(src, src.appName, app.spec.project)}>
                                     {(details: models.RepoAppDetails) => (
                                         <div>
                                             <ApplicationParameters
diff --git a/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx b/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx
index 269c99fe0..8f6f16b74 100644
--- a/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx
+++ b/ui/src/app/applications/components/application-sync-options/application-sync-options.tsx
@@ -6,6 +6,7 @@ import * as ReactForm from 'react-form';
 require('./application-sync-options.scss');
 
 export const REPLACE_WARNING = `The resources will be synced using 'kubectl replace/create' command that is a potentially destructive action and might cause resources recreation.`;
+export const FORCE_WARNING = `The resources will be synced using '--force' that is a potentially destructive action and will immediately remove resources from the API and bypasses graceful deletion. Immediate deletion of some resources may result in inconsistency or data loss.`;
 
 export interface ApplicationSyncOptionProps {
     options: string[];
diff --git a/ui/src/app/applications/components/application-sync-panel/application-sync-panel.tsx b/ui/src/app/applications/components/application-sync-panel/application-sync-panel.tsx
index 93b38e706..ed3226143 100644
--- a/ui/src/app/applications/components/application-sync-panel/application-sync-panel.tsx
+++ b/ui/src/app/applications/components/application-sync-panel/application-sync-panel.tsx
@@ -7,7 +7,7 @@ import {Consumer} from '../../../shared/context';
 import * as models from '../../../shared/models';
 import {services} from '../../../shared/services';
 import {ApplicationRetryOptions} from '../application-retry-options/application-retry-options';
-import {ApplicationManualSyncFlags, ApplicationSyncOptions, SyncFlags, REPLACE_WARNING} from '../application-sync-options/application-sync-options';
+import {ApplicationManualSyncFlags, ApplicationSyncOptions, FORCE_WARNING, SyncFlags, REPLACE_WARNING} from '../application-sync-options/application-sync-options';
 import {ComparisonStatusIcon, nodeKey} from '../utils';
 
 require('./application-sync-panel.scss');
@@ -60,7 +60,6 @@ export const ApplicationSyncPanel = ({application, selectedResource, hide}: {app
                                 if (resources.length === appResources.length) {
                                     resources = null;
                                 }
-
                                 const replace = params.syncOptions?.findIndex((opt: string) => opt === 'Replace=true') > -1;
                                 if (replace) {
                                     const confirmed = await ctx.popup.confirm('Synchronize using replace?', () => (
@@ -75,11 +74,23 @@ export const ApplicationSyncPanel = ({application, selectedResource, hide}: {app
                                 }
 
                                 const syncFlags = {...params.syncFlags} as SyncFlags;
+                                const force = syncFlags.Force || false;
 
                                 if (syncFlags.ApplyOnly) {
-                                    syncStrategy.apply = {force: syncFlags.Force || false};
+                                    syncStrategy.apply = {force};
                                 } else {
-                                    syncStrategy.hook = {force: syncFlags.Force || false};
+                                    syncStrategy.hook = {force};
+                                }
+                                if (force) {
+                                    const confirmed = await ctx.popup.confirm('Synchronize with force?', () => (
+                                        <div>
+                                            <i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} /> {FORCE_WARNING} Are you sure you want to continue?
+                                        </div>
+                                    ));
+                                    if (!confirmed) {
+                                        setPending(false);
+                                        return;
+                                    }
                                 }
 
                                 try {
diff --git a/ui/src/app/applications/components/applications-sync-panel/applications-sync-panel.tsx b/ui/src/app/applications/components/applications-sync-panel/applications-sync-panel.tsx
index a80afb731..8ca9e2d74 100644
--- a/ui/src/app/applications/components/applications-sync-panel/applications-sync-panel.tsx
+++ b/ui/src/app/applications/components/applications-sync-panel/applications-sync-panel.tsx
@@ -1,12 +1,12 @@
 import {ErrorNotification, FormField, NotificationType, SlidingPanel} from 'argo-ui';
 import * as React from 'react';
 import {Form, FormApi} from 'react-form';
-import {ProgressPopup} from '../../../shared/components';
+import {ARGO_WARNING_COLOR, ProgressPopup, Spinner} from '../../../shared/components';
 import {Consumer} from '../../../shared/context';
 import * as models from '../../../shared/models';
 import {services} from '../../../shared/services';
 import {ApplicationRetryOptions} from '../application-retry-options/application-retry-options';
-import {ApplicationManualSyncFlags, ApplicationSyncOptions, SyncFlags} from '../application-sync-options/application-sync-options';
+import {ApplicationManualSyncFlags, ApplicationSyncOptions, FORCE_WARNING, REPLACE_WARNING, SyncFlags} from '../application-sync-options/application-sync-options';
 import {ApplicationSelector} from '../../../shared/components';
 
 interface Progress {
@@ -18,6 +18,7 @@ export const ApplicationsSyncPanel = ({show, apps, hide}: {show: boolean; apps:
     const [form, setForm] = React.useState<FormApi>(null);
     const [progress, setProgress] = React.useState<Progress>(null);
     const getSelectedApps = (params: any) => apps.filter((_, i) => params['app/' + i]);
+    const [isPending, setPending] = React.useState(false);
     return (
         <Consumer>
             {ctx => (
@@ -27,7 +28,8 @@ export const ApplicationsSyncPanel = ({show, apps, hide}: {show: boolean; apps:
                     onClose={() => hide()}
                     header={
                         <div>
-                            <button className='argo-button argo-button--base' onClick={() => form.submitForm(null)}>
+                            <button className='argo-button argo-button--base' disabled={isPending} onClick={() => form.submitForm(null)}>
+                                <Spinner show={isPending} style={{marginRight: '5px'}} />
                                 Sync
                             </button>{' '}
                             <button onClick={() => hide()} className='argo-button argo-button--base-o'>
@@ -38,16 +40,40 @@ export const ApplicationsSyncPanel = ({show, apps, hide}: {show: boolean; apps:
                     <Form
                         defaultValues={{syncFlags: []}}
                         onSubmit={async (params: any) => {
+                            setPending(true);
                             const selectedApps = getSelectedApps(params);
+                            const syncFlags = {...params.syncFlags} as SyncFlags;
+                            const force = syncFlags.Force || false;
+                            if (force) {
+                                const confirmed = await ctx.popup.confirm('Synchronize with force?', () => (
+                                    <div>
+                                        <i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} /> {FORCE_WARNING} Are you sure you want to continue?
+                                    </div>
+                                ));
+                                if (!confirmed) {
+                                    setPending(false);
+                                    return;
+                                }
+                            }
+                            const replace = params.syncOptions?.findIndex((opt: string) => opt === 'Replace=true') > -1;
+                            if (replace) {
+                                const confirmed = await ctx.popup.confirm('Synchronize using replace?', () => (
+                                    <div>
+                                        <i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} /> {REPLACE_WARNING} Are you sure you want to continue?
+                                    </div>
+                                ));
+                                if (!confirmed) {
+                                    setPending(false);
+                                    return;
+                                }
+                            }
                             if (selectedApps.length === 0) {
                                 ctx.notifications.show({content: `No apps selected`, type: NotificationType.Error});
+                                setPending(false);
                                 return;
                             }
 
-                            const syncFlags = {...params.syncFlags} as SyncFlags;
-
-                            const syncStrategy: models.SyncStrategy =
-                                syncFlags.ApplyOnly || false ? {apply: {force: syncFlags.Force || false}} : {hook: {force: syncFlags.Force || false}};
+                            const syncStrategy: models.SyncStrategy = syncFlags.ApplyOnly || false ? {apply: {force}} : {hook: {force}};
 
                             setProgress({percentage: 0, title: 'Starting...'});
                             let i = 0;
@@ -68,6 +94,9 @@ export const ApplicationsSyncPanel = ({show, apps, hide}: {show: boolean; apps:
                                             content: <ErrorNotification title={`Unable to sync ${app.metadata.name}`} e={e} />,
                                             type: NotificationType.Error
                                         });
+                                    })
+                                    .finally(() => {
+                                        setPending(false);
                                     });
                                 i++;
                                 setProgress({
diff --git a/ui/src/app/applications/components/resource-details/resource-details.tsx b/ui/src/app/applications/components/resource-details/resource-details.tsx
index 3680faa25..839151e01 100644
--- a/ui/src/app/applications/components/resource-details/resource-details.tsx
+++ b/ui/src/app/applications/components/resource-details/resource-details.tsx
@@ -145,7 +145,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
                         key='appDetails'
                         input={application}
                         load={app =>
-                            services.repos.appDetails(app.spec.source, app.metadata.name).catch(() => ({
+                            services.repos.appDetails(app.spec.source, app.metadata.name, app.spec.project).catch(() => ({
                                 type: 'Directory' as AppSourceType,
                                 path: application.spec.source.path
                             }))
diff --git a/ui/src/app/shared/services/repo-service.ts b/ui/src/app/shared/services/repo-service.ts
index 3b37b8f9a..c7f91ff55 100644
--- a/ui/src/app/shared/services/repo-service.ts
+++ b/ui/src/app/shared/services/repo-service.ts
@@ -132,10 +132,12 @@ export class RepositoriesService {
         return requests.get(`/repositories/${encodeURIComponent(repo)}/refs`).then(res => res.body as models.RefsInfo);
     }
 
-    public apps(repo: string, revision: string): Promise<models.AppInfo[]> {
+    public apps(repo: string, revision: string, appName: string, appProject: string): Promise<models.AppInfo[]> {
         return requests
             .get(`/repositories/${encodeURIComponent(repo)}/apps`)
             .query({revision})
+            .query({appName})
+            .query({appProject})
             .then(res => (res.body.items as models.AppInfo[]) || []);
     }
 
@@ -143,10 +145,10 @@ export class RepositoriesService {
         return requests.get(`/repositories/${encodeURIComponent(repo)}/helmcharts`).then(res => (res.body.items as models.HelmChart[]) || []);
     }
 
-    public appDetails(source: models.ApplicationSource, appName: string): Promise<models.RepoAppDetails> {
+    public appDetails(source: models.ApplicationSource, appName: string, appProject: string): Promise<models.RepoAppDetails> {
         return requests
             .post(`/repositories/${encodeURIComponent(source.repoURL)}/appdetails`)
-            .send({source, appName})
+            .send({source, appName, appProject})
             .then(res => res.body as models.RepoAppDetails);
     }
 }
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 8f1f34738..0c4b75e56 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -5560,10 +5560,10 @@ json-parse-even-better-errors@^2.3.0:
   resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
   integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
 
-json-pointer@^0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.1.tgz#3c6caa6ac139e2599f5a1659d39852154015054d"
-  integrity sha512-3OvjqKdCBvH41DLpV4iSt6v2XhZXV1bPB4OROuknvUXI7ZQNofieCPkmE26stEJ9zdQuvIxDHCuYhfgxFAAs+Q==
+json-pointer@0.6.2, json-pointer@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.2.tgz#f97bd7550be5e9ea901f8c9264c9d436a22a93cd"
+  integrity sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==
   dependencies:
     foreach "^2.0.4"
 
@@ -6614,13 +6614,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
   dependencies:
     wrappy "1"
 
-openapi-sampler@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/openapi-sampler/-/openapi-sampler-1.2.0.tgz#2e82e4e32c1506a42f370be726d759d5275a9d4d"
-  integrity sha512-Y0tFg2iH7NWnNHYnesxhMfkXc7wWXyJXYMUTTxkGkfpl0U9u/ZOf6BxrdEXBD4sgs9uMlVWsbWLDLesVmSUU7Q==
+openapi-sampler@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/openapi-sampler/-/openapi-sampler-1.2.1.tgz#2ca9eea527f8f2ddb32c3ae1dda31afd8bf0833f"
+  integrity sha512-mHrYmyvcLD0qrfqPkPRBAL2z16hGT2rW0d0B7nklfoTcc3pmkJLkSZlKSeFgerUM41E5c7jlxf0Y19xrM7mWQQ==
   dependencies:
     "@types/json-schema" "^7.0.7"
-    json-pointer "^0.6.1"
+    json-pointer "0.6.2"
 
 opn@^5.5.0:
   version "5.5.0"
@@ -8002,10 +8002,10 @@ redent@^3.0.0:
     indent-string "^4.0.0"
     strip-indent "^3.0.0"
 
-redoc@^2.0.0-rc.63:
-  version "2.0.0-rc.63"
-  resolved "https://registry.yarnpkg.com/redoc/-/redoc-2.0.0-rc.63.tgz#d1bea51d3fdf2a6f5371a9bf7f06253ac5c1a487"
-  integrity sha512-PsoPqRyNqHi7+jKUyFBwJhHrzjMl4N5vieTeBloRGbhWuY3PPH2DJ3ihgrLfdEV0glzq/LMTaqfarm8WLqCc4Q==
+redoc@^2.0.0-rc.64:
+  version "2.0.0-rc.64"
+  resolved "https://registry.yarnpkg.com/redoc/-/redoc-2.0.0-rc.64.tgz#6fc326092a826d6e0b631cde41980ba3bc8d7e73"
+  integrity sha512-zrM/vcONpbmyDUOpk7Ai7R/yZrT7W1+8ANJUB3b5kzaLQUx4VbrDoT4D6HHHquOnKx+5We4nOPPAi8fi/cqa8g==
   dependencies:
     "@redocly/openapi-core" "^1.0.0-beta.54"
     "@redocly/react-dropdown-aria" "^2.0.11"
@@ -8013,12 +8013,12 @@ redoc@^2.0.0-rc.63:
     decko "^1.2.0"
     dompurify "^2.2.8"
     eventemitter3 "^4.0.7"
-    json-pointer "^0.6.1"
+    json-pointer "^0.6.2"
     lunr "^2.3.9"
     mark.js "^8.11.1"
     marked "^4.0.10"
     mobx-react "^7.2.0"
-    openapi-sampler "^1.1.1"
+    openapi-sampler "^1.2.1"
     path-browserify "^1.0.1"
     perfect-scrollbar "^1.5.1"
     polished "^4.1.3"
diff --git a/util/git/client.go b/util/git/client.go
index 17b5ada88..cde2ad3cd 100644
--- a/util/git/client.go
+++ b/util/git/client.go
@@ -623,7 +623,7 @@ func (m *nativeGitClient) runCredentialedCmd(command string, args ...string) err
 
 func (m *nativeGitClient) runCmdOutput(cmd *exec.Cmd) (string, error) {
 	cmd.Dir = m.root
-	cmd.Env = append(cmd.Env, os.Environ()...)
+	cmd.Env = append(os.Environ(), cmd.Env...)
 	// Set $HOME to nowhere, so we can be execute Git regardless of any external
 	// authentication keys (e.g. in ~/.ssh) -- this is especially important for
 	// running tests on local machines and/or CircleCI.
diff --git a/util/git/creds.go b/util/git/creds.go
index 8e44209ed..1d94325c7 100644
--- a/util/git/creds.go
+++ b/util/git/creds.go
@@ -11,22 +11,29 @@ import (
 	"strings"
 	"time"
 
-	gocache "github.com/patrickmn/go-cache"
-
 	argoio "github.com/argoproj/gitops-engine/pkg/utils/io"
+	"github.com/argoproj/gitops-engine/pkg/utils/text"
 	"github.com/bradleyfalzon/ghinstallation/v2"
+	gocache "github.com/patrickmn/go-cache"
 	log "github.com/sirupsen/logrus"
 
 	"github.com/argoproj/argo-cd/v2/common"
-
 	certutil "github.com/argoproj/argo-cd/v2/util/cert"
+	argoioutils "github.com/argoproj/argo-cd/v2/util/io"
 )
 
-// In memory cache for storing github APP api token credentials
 var (
+	// In memory cache for storing github APP api token credentials
 	githubAppTokenCache *gocache.Cache
 )
 
+const (
+	// ASKPASS_NONCE_ENV is the environment variable that is used to pass the nonce to the askpass script
+	ASKPASS_NONCE_ENV = "ARGOCD_GIT_ASKPASS_NONCE"
+	// githubAccessTokenUsername is a username that is used to with the github access token
+	githubAccessTokenUsername = "x-access-token"
+)
+
 func init() {
 	githubAppCredsExp := common.GithubAppCredsExpirationDuration
 	if exp := os.Getenv(common.EnvGithubAppCredsExpirationDuration); exp != "" {
@@ -38,10 +45,34 @@ func init() {
 	githubAppTokenCache = gocache.New(githubAppCredsExp, 1*time.Minute)
 }
 
+type NoopCredsStore struct {
+}
+
+func (d NoopCredsStore) Add(username string, password string) string {
+	return ""
+}
+
+func (d NoopCredsStore) Remove(id string) {
+}
+
+type CredsStore interface {
+	Add(username string, password string) string
+	Remove(id string)
+}
+
 type Creds interface {
 	Environ() (io.Closer, []string, error)
 }
 
+func getGitAskPassEnv(id string) []string {
+	return []string{
+		fmt.Sprintf("GIT_ASKPASS=%s", "argocd"),
+		fmt.Sprintf("%s=%s", ASKPASS_NONCE_ENV, id),
+		"GIT_TERMINAL_PROMPT=0",
+		"ARGOCD_BINARY_NAME=argocd-git-ask-pass",
+	}
+}
+
 // nop implementation
 type NopCloser struct {
 }
@@ -84,9 +115,11 @@ type HTTPSCreds struct {
 	clientCertKey string
 	// HTTP/HTTPS proxy used to access repository
 	proxy string
+	// temporal credentials store
+	store CredsStore
 }
 
-func NewHTTPSCreds(username string, password string, clientCertData string, clientCertKey string, insecure bool, proxy string) GenericHTTPSCreds {
+func NewHTTPSCreds(username string, password string, clientCertData string, clientCertKey string, insecure bool, proxy string, store CredsStore) GenericHTTPSCreds {
 	return HTTPSCreds{
 		username,
 		password,
@@ -94,18 +127,15 @@ func NewHTTPSCreds(username string, password string, clientCertData string, clie
 		clientCertData,
 		clientCertKey,
 		proxy,
+		store,
 	}
 }
 
 // Get additional required environment variables for executing git client to
 // access specific repository via HTTPS.
 func (c HTTPSCreds) Environ() (io.Closer, []string, error) {
-	env := []string{fmt.Sprintf("GIT_ASKPASS=%s", "git-ask-pass.sh"), fmt.Sprintf("GIT_USERNAME=%s", c.username), fmt.Sprintf("GIT_PASSWORD=%s", c.password)}
-	if c.username != "" {
-		env = append(env, fmt.Sprintf("GIT_USERNAME=%s", c.username))
-	} else {
-		env = append(env, fmt.Sprintf("GIT_USERNAME=%s", "x-access-token"))
-	}
+	var env []string
+
 	httpCloser := authFilePaths(make([]string, 0))
 
 	// GIT_SSL_NO_VERIFY is used to tell git not to validate the server's cert at
@@ -157,9 +187,13 @@ func (c HTTPSCreds) Environ() (io.Closer, []string, error) {
 		}
 		// GIT_SSL_KEY is the full path to a client certificate's key to be used
 		env = append(env, fmt.Sprintf("GIT_SSL_KEY=%s", keyFile.Name()))
-
 	}
-	return httpCloser, env, nil
+	nonce := c.store.Add(text.FirstNonEmpty(c.username, githubAccessTokenUsername), c.password)
+	env = append(env, getGitAskPassEnv(nonce)...)
+	return argoioutils.NewCloser(func() error {
+		c.store.Remove(nonce)
+		return httpCloser.Close()
+	}), env, nil
 }
 
 func (g HTTPSCreds) HasClientCert() bool {
@@ -179,10 +213,11 @@ type SSHCreds struct {
 	sshPrivateKey string
 	caPath        string
 	insecure      bool
+	store         CredsStore
 }
 
-func NewSSHCreds(sshPrivateKey string, caPath string, insecureIgnoreHostKey bool) SSHCreds {
-	return SSHCreds{sshPrivateKey, caPath, insecureIgnoreHostKey}
+func NewSSHCreds(sshPrivateKey string, caPath string, insecureIgnoreHostKey bool, store CredsStore) SSHCreds {
+	return SSHCreds{sshPrivateKey, caPath, insecureIgnoreHostKey, store}
 }
 
 type sshPrivateKeyFile string
@@ -249,11 +284,12 @@ type GitHubAppCreds struct {
 	clientCertKey  string
 	insecure       bool
 	proxy          string
+	store          CredsStore
 }
 
 // NewGitHubAppCreds provide github app credentials
-func NewGitHubAppCreds(appID int64, appInstallId int64, privateKey string, baseURL string, repoURL string, clientCertData string, clientCertKey string, insecure bool) GenericHTTPSCreds {
-	return GitHubAppCreds{appID: appID, appInstallId: appInstallId, privateKey: privateKey, baseURL: baseURL, repoURL: repoURL, clientCertData: clientCertData, clientCertKey: clientCertKey, insecure: insecure}
+func NewGitHubAppCreds(appID int64, appInstallId int64, privateKey string, baseURL string, repoURL string, clientCertData string, clientCertKey string, insecure bool, store CredsStore) GenericHTTPSCreds {
+	return GitHubAppCreds{appID: appID, appInstallId: appInstallId, privateKey: privateKey, baseURL: baseURL, repoURL: repoURL, clientCertData: clientCertData, clientCertKey: clientCertKey, insecure: insecure, store: store}
 }
 
 func (g GitHubAppCreds) Environ() (io.Closer, []string, error) {
@@ -261,8 +297,7 @@ func (g GitHubAppCreds) Environ() (io.Closer, []string, error) {
 	if err != nil {
 		return NopCloser{}, nil, err
 	}
-
-	env := []string{fmt.Sprintf("GIT_ASKPASS=%s", "git-ask-pass.sh"), "GIT_USERNAME=x-access-token", fmt.Sprintf("GIT_PASSWORD=%s", token)}
+	var env []string
 	httpCloser := authFilePaths(make([]string, 0))
 
 	// GIT_SSL_NO_VERIFY is used to tell git not to validate the server's cert at
@@ -316,7 +351,12 @@ func (g GitHubAppCreds) Environ() (io.Closer, []string, error) {
 		env = append(env, fmt.Sprintf("GIT_SSL_KEY=%s", keyFile.Name()))
 
 	}
-	return httpCloser, env, nil
+	nonce := g.store.Add(githubAccessTokenUsername, token)
+	env = append(env, getGitAskPassEnv(nonce)...)
+	return argoioutils.NewCloser(func() error {
+		g.store.Remove(nonce)
+		return httpCloser.Close()
+	}), env, nil
 }
 
 // getAccessToken fetches GitHub token using the app id, install id, and private key.


Test patch:
diff --git a/reposerver/askpass/server_test.go b/reposerver/askpass/server_test.go
new file mode 100644
index 000000000..311592d7f
--- /dev/null
+++ b/reposerver/askpass/server_test.go
@@ -0,0 +1,25 @@
+package askpass
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAdd(t *testing.T) {
+	s := NewServer()
+	nonce := s.Add("foo", "bar")
+
+	assert.Equal(t, "foo", s.creds[nonce].Username)
+	assert.Equal(t, "bar", s.creds[nonce].Password)
+}
+
+func TestRemove(t *testing.T) {
+	s := NewServer()
+	s.creds["some-id"] = Creds{Username: "foo"}
+
+	s.Remove("some-id")
+
+	_, ok := s.creds["some-id"]
+	assert.False(t, ok)
+}
diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go
index f214733e7..6c8162f4c 100644
--- a/reposerver/repository/repository_test.go
+++ b/reposerver/repository/repository_test.go
@@ -72,7 +72,7 @@ func newServiceWithOpt(cf clientFunc) (*Service, *gitmocks.Client) {
 		cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Minute)),
 		1*time.Minute,
 		1*time.Minute,
-	), RepoServerInitConstants{ParallelismLimit: 1}, argo.NewResourceTracking())
+	), RepoServerInitConstants{ParallelismLimit: 1}, argo.NewResourceTracking(), &git.NoopCredsStore{})
 
 	chart := "my-chart"
 	version := "1.1.0"
@@ -140,7 +140,7 @@ func TestGenerateYamlManifestInDir(t *testing.T) {
 	assert.Equal(t, countOfManifests, len(res1.Manifests))
 
 	// this will test concatenated manifests to verify we split YAMLs correctly
-	res2, err := GenerateManifests(context.Background(), "./testdata/concatenated", "/", "", &q, false)
+	res2, err := GenerateManifests(context.Background(), "./testdata/concatenated", "/", "", &q, false, &git.NoopCredsStore{})
 	assert.NoError(t, err)
 	assert.Equal(t, 3, len(res2.Manifests))
 }
@@ -1050,9 +1050,8 @@ func TestRunCustomTool(t *testing.T) {
 
 	assert.Equal(t, obj.GetName(), "test-app")
 	assert.Equal(t, obj.GetNamespace(), "test-namespace")
-	assert.Equal(t, "git-ask-pass.sh", obj.GetAnnotations()["GIT_ASKPASS"])
-	assert.Equal(t, "foo", obj.GetAnnotations()["GIT_USERNAME"])
-	assert.Equal(t, "bar", obj.GetAnnotations()["GIT_PASSWORD"])
+	assert.Empty(t, obj.GetAnnotations()["GIT_USERNAME"])
+	assert.Empty(t, obj.GetAnnotations()["GIT_PASSWORD"])
 	// Git client is mocked, so the revision is always mock.Anything
 	assert.Equal(t, map[string]string{"revision": "prefix-mock.Anything"}, obj.GetLabels())
 }
@@ -1062,7 +1061,7 @@ func TestGenerateFromUTF16(t *testing.T) {
 		Repo:              &argoappv1.Repository{},
 		ApplicationSource: &argoappv1.ApplicationSource{},
 	}
-	res1, err := GenerateManifests(context.Background(), "./testdata/utf-16", "/", "", &q, false)
+	res1, err := GenerateManifests(context.Background(), "./testdata/utf-16", "/", "", &q, false, &git.NoopCredsStore{})
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(res1.Manifests))
 }
diff --git a/resource_customizations/argoproj.io/CronWorkflow/health_test.yaml b/resource_customizations/argoproj.io/CronWorkflow/health_test.yaml
index 4ae1d2db9..d95d06678 100644
--- a/resource_customizations/argoproj.io/CronWorkflow/health_test.yaml
+++ b/resource_customizations/argoproj.io/CronWorkflow/health_test.yaml
@@ -12,6 +12,10 @@ tests:
       message: "there is a submission error"
     inputPath: testdata/degradedCronWorkflowWithSubmissionError.yaml
   - healthStatus:
-      status: Progressing
-      message: "CronWorkflow is working on executing workflows."
+      status: Healthy
+      message: "CronWorkflow is healthy."
     inputPath: testdata/noConditionsCronWorkflow.yaml
+  - healthStatus:
+      status: Healthy
+      message: "CronWorkflow is healthy."
+    inputPath: testdata/withConditionButHealthyCronWorkflow.yaml
diff --git a/resource_customizations/argoproj.io/CronWorkflow/testdata/withConditionButHealthyCronWorkflow.yaml b/resource_customizations/argoproj.io/CronWorkflow/testdata/withConditionButHealthyCronWorkflow.yaml
new file mode 100644
index 000000000..99ada4d5d
--- /dev/null
+++ b/resource_customizations/argoproj.io/CronWorkflow/testdata/withConditionButHealthyCronWorkflow.yaml
@@ -0,0 +1,13 @@
+apiVersion: argoproj.io/v1alpha1
+kind: CronWorkflow
+metadata:
+  name: test-cron-wf
+  namespace: argocd
+spec:
+  entrypoint: sampleEntryPoint
+status:
+  conditions:
+    - lastTransitionTime: "2021-11-12T14:28:01Z"
+      message: this status may be outdated and we may still be progressing
+      status: "False"
+      type: SpecError
diff --git a/server/repository/repository_test.go b/server/repository/repository_test.go
index a8e35aa87..2b2770c6c 100644
--- a/server/repository/repository_test.go
+++ b/server/repository/repository_test.go
@@ -6,52 +6,39 @@ import (
 	"testing"
 	"time"
 
+	"github.com/golang-jwt/jwt/v4"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
-
-	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
-
-	"github.com/argoproj/argo-cd/v2/server/cache"
-
-	"github.com/stretchr/testify/mock"
-
-	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
-
-	"github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
-
-	"github.com/argoproj/argo-cd/v2/util/db"
-
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 
 	"github.com/argoproj/argo-cd/v2/common"
-	"github.com/argoproj/argo-cd/v2/util/assets"
-	"github.com/argoproj/argo-cd/v2/util/rbac"
-	"github.com/argoproj/argo-cd/v2/util/settings"
-
+	"github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
+	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	fakeapps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
+	appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
+	applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
+	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
 	"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
-
-	"github.com/stretchr/testify/assert"
-
-	"github.com/golang-jwt/jwt/v4"
-	corev1 "k8s.io/api/core/v1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
+	"github.com/argoproj/argo-cd/v2/server/cache"
+	"github.com/argoproj/argo-cd/v2/util/assets"
 	cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
 	appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
+	"github.com/argoproj/argo-cd/v2/util/db"
 	dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
+	"github.com/argoproj/argo-cd/v2/util/rbac"
+	"github.com/argoproj/argo-cd/v2/util/settings"
 )
 
 const testNamespace = "default"
 
-func Test_createRBACObject(t *testing.T) {
-	object := createRBACObject("test-prj", "test-repo")
-	assert.Equal(t, "test-prj/test-repo", object)
-	objectWithoutPrj := createRBACObject("", "test-repo")
-	assert.Equal(t, "test-repo", objectWithoutPrj)
-}
-
-func TestRepositoryServer(t *testing.T) {
-	kubeclientset := fake.NewSimpleClientset(&corev1.ConfigMap{
+var (
+	argocdCM = corev1.ConfigMap{
 		ObjectMeta: v1.ObjectMeta{
 			Namespace: testNamespace,
 			Name:      "argocd-cm",
@@ -59,7 +46,8 @@ func TestRepositoryServer(t *testing.T) {
 				"app.kubernetes.io/part-of": "argocd",
 			},
 		},
-	}, &corev1.Secret{
+	}
+	argocdSecret = corev1.Secret{
 		ObjectMeta: v1.ObjectMeta{
 			Name:      "argocd-secret",
 			Namespace: testNamespace,
@@ -68,17 +56,110 @@ func TestRepositoryServer(t *testing.T) {
 			"admin.password":   []byte("test"),
 			"server.secretkey": []byte("test"),
 		},
-	})
+	}
+	defaultProj = &appsv1.AppProject{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "AppProject",
+			APIVersion: "argoproj.io/v1alpha1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "default",
+			Namespace: testNamespace,
+		},
+		Spec: appsv1.AppProjectSpec{
+			SourceRepos:  []string{"*"},
+			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
+		},
+	}
+
+	defaultProjNoSources = &appsv1.AppProject{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "AppProject",
+			APIVersion: "argoproj.io/v1alpha1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "default",
+			Namespace: testNamespace,
+		},
+		Spec: appsv1.AppProjectSpec{
+			SourceRepos:  []string{},
+			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
+		},
+	}
+
+	guestbookApp = &appsv1.Application{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Application",
+			APIVersion: "argoproj.io/v1alpha1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "guestbook",
+			Namespace: testNamespace,
+		},
+		Spec: appsv1.ApplicationSpec{
+			Project: "default",
+			Source: appsv1.ApplicationSource{
+				RepoURL:        "https://test",
+				TargetRevision: "HEAD",
+				Helm: &appsv1.ApplicationSourceHelm{
+					ValueFiles: []string{"values.yaml"},
+				},
+			},
+		},
+		Status: appsv1.ApplicationStatus{
+			History: appsv1.RevisionHistories{
+				{
+					Revision: "abcdef123567",
+					Source: appsv1.ApplicationSource{
+						RepoURL:        "https://test",
+						TargetRevision: "HEAD",
+						Helm: &appsv1.ApplicationSourceHelm{
+							ValueFiles: []string{"values-old.yaml"},
+						},
+					},
+				},
+			},
+		},
+	}
+)
+
+func newAppAndProjLister(objects ...runtime.Object) (applisters.ApplicationNamespaceLister, applisters.AppProjectNamespaceLister) {
+	fakeAppsClientset := fakeapps.NewSimpleClientset(objects...)
+	factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
+	projInformer := factory.Argoproj().V1alpha1().AppProjects()
+	appsInformer := factory.Argoproj().V1alpha1().Applications()
+	for _, obj := range objects {
+		switch obj.(type) {
+		case *appsv1.AppProject:
+			_ = projInformer.Informer().GetStore().Add(obj)
+		case *appsv1.Application:
+			_ = appsInformer.Informer().GetStore().Add(obj)
+		}
+	}
+	appLister := appsInformer.Lister().Applications(testNamespace)
+	projLister := projInformer.Lister().AppProjects(testNamespace)
+	return appLister, projLister
+}
+
+func Test_createRBACObject(t *testing.T) {
+	object := createRBACObject("test-prj", "test-repo")
+	assert.Equal(t, "test-prj/test-repo", object)
+	objectWithoutPrj := createRBACObject("", "test-repo")
+	assert.Equal(t, "test-repo", objectWithoutPrj)
+}
+
+func TestRepositoryServer(t *testing.T) {
+	kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
 	settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
 	enforcer := newEnforcer(kubeclientset)
-
+	appLister, projLister := newAppAndProjLister(defaultProj)
 	argoDB := db.NewDB("default", settingsMgr, kubeclientset)
 
 	t.Run("Test_getRepo", func(t *testing.T) {
 		repoServerClient := mocks.RepoServerServiceClient{}
 		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
 
-		s := NewServer(&repoServerClientset, argoDB, enforcer, nil, settingsMgr)
+		s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projLister, settingsMgr)
 		url := "https://test"
 		repo, _ := s.getRepo(context.TODO(), url)
 		assert.Equal(t, repo.Repo, url)
@@ -89,7 +170,7 @@ func TestRepositoryServer(t *testing.T) {
 		repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
 		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
 
-		s := NewServer(&repoServerClientset, argoDB, enforcer, nil, settingsMgr)
+		s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projLister, settingsMgr)
 		url := "https://test"
 		_, err := s.ValidateAccess(context.TODO(), &repository.RepoAccessQuery{
 			Repo: url,
@@ -104,10 +185,10 @@ func TestRepositoryServer(t *testing.T) {
 
 		url := "https://test"
 		db := &dbmocks.ArgoDB{}
-		db.On("GetRepository", context.TODO(), url).Return(&v1alpha1.Repository{Repo: url}, nil)
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
 		db.On("RepositoryExists", context.TODO(), url).Return(true, nil)
 
-		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
 		repo, err := s.Get(context.TODO(), &repository.RepoQuery{
 			Repo: url,
 		})
@@ -124,12 +205,12 @@ func TestRepositoryServer(t *testing.T) {
 		db.On("GetRepository", context.TODO(), url).Return(nil, errors.New("some error"))
 		db.On("RepositoryExists", context.TODO(), url).Return(true, nil)
 
-		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
 		repo, err := s.Get(context.TODO(), &repository.RepoQuery{
 			Repo: url,
 		})
 		assert.Nil(t, repo)
-		assert.Equal(t, err.Error(), "rpc error: code = PermissionDenied desc = permission denied")
+		assert.Equal(t, err, errPermissionDenied)
 	})
 
 	t.Run("Test_GetWithNotExistRepoShouldReturn404", func(t *testing.T) {
@@ -138,15 +219,15 @@ func TestRepositoryServer(t *testing.T) {
 
 		url := "https://test"
 		db := &dbmocks.ArgoDB{}
-		db.On("GetRepository", context.TODO(), url).Return(&v1alpha1.Repository{Repo: url}, nil)
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
 		db.On("RepositoryExists", context.TODO(), url).Return(false, nil)
 
-		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
 		repo, err := s.Get(context.TODO(), &repository.RepoQuery{
 			Repo: url,
 		})
 		assert.Nil(t, repo)
-		assert.Equal(t, err.Error(), "rpc error: code = NotFound desc = repo 'https://test' not found")
+		assert.Equal(t, "rpc error: code = NotFound desc = repo 'https://test' not found", err.Error())
 	})
 
 	t.Run("Test_CreateRepositoryWithoutUpsert", func(t *testing.T) {
@@ -156,14 +237,14 @@ func TestRepositoryServer(t *testing.T) {
 
 		db := &dbmocks.ArgoDB{}
 		db.On("GetRepository", context.TODO(), "test").Return(nil, errors.New("not found"))
-		db.On("CreateRepository", context.TODO(), mock.Anything).Return(&apiclient.TestRepositoryResponse{}).Return(&v1alpha1.Repository{
+		db.On("CreateRepository", context.TODO(), mock.Anything).Return(&apiclient.TestRepositoryResponse{}).Return(&appsv1.Repository{
 			Repo:    "repo",
 			Project: "proj",
 		}, nil)
 
-		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
 		repo, err := s.CreateRepository(context.TODO(), &repository.RepoCreateRequest{
-			Repo: &v1alpha1.Repository{
+			Repo: &appsv1.Repository{
 				Repo:     "test",
 				Username: "test",
 			},
@@ -178,16 +259,16 @@ func TestRepositoryServer(t *testing.T) {
 		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
 
 		db := &dbmocks.ArgoDB{}
-		db.On("GetRepository", context.TODO(), "test").Return(&v1alpha1.Repository{
+		db.On("GetRepository", context.TODO(), "test").Return(&appsv1.Repository{
 			Repo:     "test",
 			Username: "test",
 		}, nil)
 		db.On("CreateRepository", context.TODO(), mock.Anything).Return(nil, status.Errorf(codes.AlreadyExists, "repository already exists"))
 		db.On("UpdateRepository", context.TODO(), mock.Anything).Return(nil, nil)
 
-		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
 		repo, err := s.CreateRepository(context.TODO(), &repository.RepoCreateRequest{
-			Repo: &v1alpha1.Repository{
+			Repo: &appsv1.Repository{
 				Repo:     "test",
 				Username: "test",
 			},
@@ -200,6 +281,295 @@ func TestRepositoryServer(t *testing.T) {
 
 }
 
+func TestRepositoryServerListApps(t *testing.T) {
+	kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
+	settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
+
+	t.Run("Test_WithoutAppCreateUpdatePrivileges", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+		enforcer.SetDefaultRole("role:readonly")
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
+			Repo:       "https://test",
+			Revision:   "HEAD",
+			AppName:    "foo",
+			AppProject: "default",
+		})
+		assert.Nil(t, resp)
+		assert.Equal(t, err, errPermissionDenied)
+	})
+
+	t.Run("Test_WithAppCreateUpdatePrivileges", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+		enforcer.SetDefaultRole("role:admin")
+		appLister, projLister := newAppAndProjLister(defaultProj)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{
+			Apps: map[string]string{
+				"path/to/dir": "Kustomize",
+			},
+		}, nil)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
+			Repo:       "https://test",
+			Revision:   "HEAD",
+			AppName:    "foo",
+			AppProject: "default",
+		})
+		assert.NoError(t, err)
+		assert.Len(t, resp.Items, 1)
+		assert.Equal(t, "path/to/dir", resp.Items[0].Path)
+		assert.Equal(t, "Kustomize", resp.Items[0].Type)
+	})
+
+	t.Run("Test_WithAppCreateUpdatePrivilegesRepoNotAllowed", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+		enforcer.SetDefaultRole("role:admin")
+		appLister, projLister := newAppAndProjLister(defaultProjNoSources)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{
+			Apps: map[string]string{
+				"path/to/dir": "Kustomize",
+			},
+		}, nil)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
+			Repo:       "https://test",
+			Revision:   "HEAD",
+			AppName:    "foo",
+			AppProject: "default",
+		})
+		assert.Nil(t, resp)
+		assert.Error(t, err, "repository 'https://test' not permitted in project 'default'")
+	})
+}
+
+func TestRepositoryServerGetAppDetails(t *testing.T) {
+	kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
+	settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
+
+	t.Run("Test_WithoutRepoReadPrivileges", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+		enforcer.SetDefaultRole("")
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source: &appsv1.ApplicationSource{
+				RepoURL: url,
+			},
+			AppName:    "newapp",
+			AppProject: "default",
+		})
+		assert.Nil(t, resp)
+		assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: repositories, get, https://test")
+	})
+	t.Run("Test_WithoutAppReadPrivileges", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+		_ = enforcer.SetUserPolicy("p, role:readrepos, repositories, get, *, allow")
+		enforcer.SetDefaultRole("role:readrepos")
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source: &appsv1.ApplicationSource{
+				RepoURL: url,
+			},
+			AppName:    "newapp",
+			AppProject: "default",
+		})
+		assert.Nil(t, resp)
+		assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: applications, get, default/newapp")
+	})
+	t.Run("Test_WithoutCreatePrivileges", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+		enforcer.SetDefaultRole("role:readonly")
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source: &appsv1.ApplicationSource{
+				RepoURL: url,
+			},
+			AppName:    "newapp",
+			AppProject: "default",
+		})
+		assert.Nil(t, resp)
+		assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: applications, create, default/newapp")
+	})
+	t.Run("Test_WithCreatePrivileges", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
+		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source: &appsv1.ApplicationSource{
+				RepoURL: url,
+			},
+			AppName:    "newapp",
+			AppProject: "default",
+		})
+		assert.NoError(t, err)
+		assert.Equal(t, expectedResp, *resp)
+	})
+	t.Run("Test_RepoNotPermitted", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
+		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
+		appLister, projLister := newAppAndProjLister(defaultProjNoSources)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source: &appsv1.ApplicationSource{
+				RepoURL: url,
+			},
+			AppName:    "newapp",
+			AppProject: "default",
+		})
+		assert.Error(t, err, "repository 'https://test' not permitted in project 'default'")
+		assert.Nil(t, resp)
+	})
+	t.Run("Test_ExistingApp", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
+		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source:     &guestbookApp.Spec.Source,
+			AppName:    "guestbook",
+			AppProject: "default",
+		})
+		assert.NoError(t, err)
+		assert.Equal(t, expectedResp, *resp)
+	})
+	t.Run("Test_ExistingAppMismatchedProjectName", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source:     &guestbookApp.Spec.Source,
+			AppName:    "guestbook",
+			AppProject: "mismatch",
+		})
+		assert.Equal(t, errPermissionDenied, err)
+		assert.Nil(t, resp)
+	})
+	t.Run("Test_ExistingAppSourceNotInHistory", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
+		differentSource := guestbookApp.Spec.Source.DeepCopy()
+		differentSource.Helm.ValueFiles = []string{"/etc/passwd"}
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source:     differentSource,
+			AppName:    "guestbook",
+			AppProject: "default",
+		})
+		assert.Equal(t, errPermissionDenied, err)
+		assert.Nil(t, resp)
+	})
+	t.Run("Test_ExistingAppSourceInHistory", func(t *testing.T) {
+		repoServerClient := mocks.RepoServerServiceClient{}
+		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
+		enforcer := newEnforcer(kubeclientset)
+
+		url := "https://test"
+		db := &dbmocks.ArgoDB{}
+		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
+		db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
+		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
+		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
+		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
+		previousSource := guestbookApp.Status.History[0].Source.DeepCopy()
+		previousSource.TargetRevision = guestbookApp.Status.History[0].Revision
+
+		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
+		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
+			Source:     previousSource,
+			AppName:    "guestbook",
+			AppProject: "default",
+		})
+		assert.NoError(t, err)
+		assert.Equal(t, expectedResp, *resp)
+	})
+}
+
 type fixtures struct {
 	*cache.Cache
 }
diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go
index 64fc5e1ae..d26777962 100644
--- a/test/e2e/app_management_test.go
+++ b/test/e2e/app_management_test.go
@@ -729,7 +729,9 @@ func TestKsonnetApp(t *testing.T) {
 			defer io.Close(closer)
 
 			details, err := client.GetAppDetails(context.Background(), &repositorypkg.RepoAppDetailsQuery{
-				Source: &app.Spec.Source,
+				AppName:    app.Name,
+				AppProject: app.Spec.Project,
+				Source:     &app.Spec.Source,
 			})
 			assert.NoError(t, err)
 
diff --git a/test/e2e/custom_tool_test.go b/test/e2e/custom_tool_test.go
index dfeaec573..26adcbc4e 100644
--- a/test/e2e/custom_tool_test.go
+++ b/test/e2e/custom_tool_test.go
@@ -26,7 +26,7 @@ func TestCustomToolWithGitCreds(t *testing.T) {
 				Name: Name(),
 				Generate: Command{
 					Command: []string{"sh", "-c"},
-					Args:    []string{`echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"GitAskpass\": \"$GIT_ASKPASS\", \"GitUsername\": \"$GIT_USERNAME\", \"GitPassword\": \"$GIT_PASSWORD\"}}}"`},
+					Args:    []string{`echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"GitAskpass\": \"$GIT_ASKPASS\"}}}"`},
 				},
 			},
 		).
@@ -45,17 +45,7 @@ func TestCustomToolWithGitCreds(t *testing.T) {
 		And(func(app *Application) {
 			output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
 			assert.NoError(t, err)
-			assert.Equal(t, "git-ask-pass.sh", output)
-		}).
-		And(func(app *Application) {
-			output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitUsername}")
-			assert.NoError(t, err)
-			assert.Equal(t, GitUsername, output)
-		}).
-		And(func(app *Application) {
-			output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitPassword}")
-			assert.NoError(t, err)
-			assert.Equal(t, GitPassword, output)
+			assert.Equal(t, "argocd", output)
 		})
 }
 
@@ -89,17 +79,17 @@ func TestCustomToolWithGitCredsTemplate(t *testing.T) {
 		And(func(app *Application) {
 			output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
 			assert.NoError(t, err)
-			assert.Equal(t, "git-ask-pass.sh", output)
+			assert.Equal(t, "argocd", output)
 		}).
 		And(func(app *Application) {
 			output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitUsername}")
 			assert.NoError(t, err)
-			assert.Equal(t, GitUsername, output)
+			assert.Empty(t, output)
 		}).
 		And(func(app *Application) {
 			output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitPassword}")
 			assert.NoError(t, err)
-			assert.Equal(t, GitPassword, output)
+			assert.Empty(t, output)
 		})
 }
 
diff --git a/test/fixture/path/hack_path.go b/test/fixture/path/hack_path.go
index e15102224..41bf98961 100644
--- a/test/fixture/path/hack_path.go
+++ b/test/fixture/path/hack_path.go
@@ -16,10 +16,10 @@ func (h AddBinDirToPath) Close() {
 	_ = os.Setenv("PATH", h.originalPath)
 }
 
-// add the hack path which has the git-ask-pass.sh shell script
+// add the hack path which has the argocd binary
 func NewBinDirToPath() AddBinDirToPath {
 	originalPath := os.Getenv("PATH")
-	binDir, err := filepath.Abs("../../hack")
+	binDir, err := filepath.Abs("../../dist")
 	errors.CheckError(err)
 	err = os.Setenv("PATH", fmt.Sprintf("%s:%s", originalPath, binDir))
 	errors.CheckError(err)
diff --git a/util/git/git_test.go b/util/git/git_test.go
index 068b83395..1d36370ac 100644
--- a/util/git/git_test.go
+++ b/util/git/git_test.go
@@ -146,7 +146,7 @@ func TestCustomHTTPClient(t *testing.T) {
 	assert.NotEqual(t, "", string(keyData))
 
 	// Get HTTPSCreds with client cert creds specified, and insecure connection
-	creds := NewHTTPSCreds("test", "test", string(certData), string(keyData), false, "http://proxy:5000")
+	creds := NewHTTPSCreds("test", "test", string(certData), string(keyData), false, "http://proxy:5000", &NoopCredsStore{})
 	client := GetRepoHTTPClient("https://localhost:9443/foo/bar", false, creds, "http://proxy:5000")
 	assert.NotNil(t, client)
 	assert.NotNil(t, client.Transport)
@@ -177,7 +177,7 @@ func TestCustomHTTPClient(t *testing.T) {
 	}()
 
 	// Get HTTPSCreds without client cert creds, but insecure connection
-	creds = NewHTTPSCreds("test", "test", "", "", true, "")
+	creds = NewHTTPSCreds("test", "test", "", "", true, "", &NoopCredsStore{})
 	client = GetRepoHTTPClient("https://localhost:9443/foo/bar", true, creds, "")
 	assert.NotNil(t, client)
 	assert.NotNil(t, client.Transport)