Repository: argoproj-labs/argocd-image-updater

Issue:
Wrong default write branch is used in multi-source app
**Describe the bug**
In a multi-source application, when a custom write-back git repo is specified, but the git branch is not specified, image-updater selects the branch from the primary source, instead of the targetRevison of the chosen source.

**To Reproduce**
```yaml
    argocd-image-updater.argoproj.io/nginx.helm.image-name: image.repository
    argocd-image-updater.argoproj.io/nginx.helm.image-tag: image.tag
    argocd-image-updater.argoproj.io/git-repository: https://github.com/chengfang/image-updater-examples.git
    # argocd-image-updater.argoproj.io/git-branch: main
    argocd-image-updater.argoproj.io/write-back-method: git:secret:argocd/git-creds
    argocd-image-updater.argoproj.io/write-back-target: helmvalues:/write-helmvalues/source2/values.yaml
```

**Expected behavior**
the branch of the specified source repo is selected as the default write-back git branch, unless overridden with `git-branch` annotation.

**Additional context**
The workaround is to always specify `git-branch` annotation. 

**Version**
v0.15.1

**Logs**
```text
INFO[0001] Committing 1 parameter update(s) for application write-helmvalues  application=write-helmvalues
INFO[0001] Initializing https://github.com/chengfang/image-updater-examples.git to /var/folders/sc/bblzy4t15995h2zs1s4bztc00000gn/T/git-write-helmvalues2791939777
INFO[0001] git fetch origin 18.2.3 --force --prune --depth 1  dir=/var/folders/sc/bblzy4t15995h2zs1s4bztc00000gn/T/git-write-helmvalues2791939777 execID=a6d31
ERRO[0002] `git fetch origin 18.2.3 --force --prune --depth 1` failed exit status 128: fatal: couldn't find remote ref 18.2.3  execID=a6d31
INFO[0002] Trace                                         args="[git fetch origin 18.2.3 --force --prune --depth 1]" dir=/var/folders/sc/bblzy4t15995h2zs1s4bztc00000gn/T/git-write-helmvalues2791939777 operation_name="exec git" time_ms=284.635333
ERRO[0002] Could not update application spec: `git fetch origin 18.2.3 --force --prune --depth 1` failed exit status 128: fatal: couldn't find remote ref 18.2.3  application=write-helmvalues
INFO[0002] Processing results: applications=1 images_considered=1 images_skipped=0 images_updated=0 errors=1
```

Hints from issue comments:
@chengfang: https://github.com/argoproj-labs/argocd-image-updater/blob/192433988b13a46a8f8c5f4001f26c0fbdec05aa/pkg/argocd/git.go#L161-L165

Need to improve this block to choose a source matching the `git-repository` annotation value. If not found, then default to the primary source.@chengfang: the sample app used above is https://github.com/chengfang/image-updater-examples/tree/main/write-helmvalues

Solution patch:
diff --git a/pkg/argocd/git.go b/pkg/argocd/git.go
index 14b5398..4cb119a 100644
--- a/pkg/argocd/git.go
+++ b/pkg/argocd/git.go
@@ -17,6 +17,7 @@ import (
 	"sigs.k8s.io/kustomize/kyaml/order"
 	kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
 
+	"github.com/argoproj-labs/argocd-image-updater/pkg/common"
 	"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
 
 	"github.com/argoproj-labs/argocd-image-updater/ext/git"
@@ -128,6 +129,33 @@ func TemplateBranchName(branchName string, changeList []ChangeEntry) string {
 
 type changeWriter func(app *v1alpha1.Application, wbc *WriteBackConfig, gitC git.Client) (err error, skip bool)
 
+// getWriteBackBranch returns the branch to use for write-back operations.
+// It first checks for a branch specified in annotations, then uses the
+// targetRevision from the matching git source, falling back to getApplicationSource.
+func getWriteBackBranch(app *v1alpha1.Application) string {
+	if app == nil {
+		return ""
+	}
+	// If git repository is specified, find matching source
+	if gitRepo, ok := app.GetAnnotations()[common.GitRepositoryAnnotation]; ok {
+		if app.Spec.HasMultipleSources() {
+			for _, s := range app.Spec.Sources {
+				if s.RepoURL == gitRepo {
+					log.WithContext().AddField("application", app.GetName()).
+						Debugf("Using target revision '%s' from matching source '%s'", s.TargetRevision, gitRepo)
+					return s.TargetRevision
+				}
+			}
+			log.WithContext().AddField("application", app.GetName()).
+				Debugf("No matching source found for git repository %s, falling back to primary source", gitRepo)
+		}
+	}
+
+	// Fall back to getApplicationSource's targetRevision
+	// This maintains consistency with how other parts of the code select the source
+	return getApplicationSource(app).TargetRevision
+}
+
 // commitChanges commits any changes required for updating one or more images
 // after the UpdateApplication cycle has finished.
 func commitChangesGit(app *v1alpha1.Application, wbc *WriteBackConfig, changeList []ChangeEntry, write changeWriter) error {
@@ -164,9 +192,11 @@ func commitChangesGit(app *v1alpha1.Application, wbc *WriteBackConfig, changeLis
 	// config, or taken from the application spec's targetRevision. If the
 	// target revision is set to the special value HEAD, or is the empty
 	// string, we'll try to resolve it to a branch name.
-	checkOutBranch := getApplicationSource(app).TargetRevision
+	var checkOutBranch string
 	if wbc.GitBranch != "" {
 		checkOutBranch = wbc.GitBranch
+	} else {
+		checkOutBranch = getWriteBackBranch(app)
 	}
 	logCtx.Tracef("targetRevision for update is '%s'", checkOutBranch)
 	if checkOutBranch == "" || checkOutBranch == "HEAD" {



Test patch:
diff --git a/pkg/argocd/git_test.go b/pkg/argocd/git_test.go
index 7db7aad..655285f 100644
--- a/pkg/argocd/git_test.go
+++ b/pkg/argocd/git_test.go
@@ -10,6 +10,8 @@ import (
 	"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
 	"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
 
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	"sigs.k8s.io/kustomize/api/types"
 	kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
 
@@ -329,3 +331,132 @@ func Test_updateKustomizeFile(t *testing.T) {
 		})
 	}
 }
+
+func Test_getApplicationSource(t *testing.T) {
+	t.Run("multi-source without git repo annotation", func(t *testing.T) {
+		app := &v1alpha1.Application{
+			ObjectMeta: v1.ObjectMeta{
+				Name: "test-app",
+			},
+			Spec: v1alpha1.ApplicationSpec{
+				Sources: v1alpha1.ApplicationSources{
+					{
+						RepoURL:        "https://charts.bitnami.com/bitnami",
+						TargetRevision: "18.2.3",
+						Chart:          "nginx",
+						Helm:           &v1alpha1.ApplicationSourceHelm{},
+					},
+					{
+						RepoURL:        "https://github.com/chengfang/image-updater-examples.git",
+						TargetRevision: "main",
+					},
+				},
+			},
+		}
+
+		source := getApplicationSource(app)
+		assert.Equal(t, "18.2.3", source.TargetRevision)
+		assert.Equal(t, "https://charts.bitnami.com/bitnami", source.RepoURL)
+	})
+
+	t.Run("single source application", func(t *testing.T) {
+		app := &v1alpha1.Application{
+			ObjectMeta: v1.ObjectMeta{
+				Name: "test-app",
+			},
+			Spec: v1alpha1.ApplicationSpec{
+				Source: &v1alpha1.ApplicationSource{
+					RepoURL:        "https://github.com/example/repo.git",
+					TargetRevision: "main",
+				},
+			},
+		}
+
+		source := getApplicationSource(app)
+		assert.Equal(t, "main", source.TargetRevision)
+		assert.Equal(t, "https://github.com/example/repo.git", source.RepoURL)
+	})
+}
+
+func Test_getWriteBackBranch(t *testing.T) {
+	t.Run("nil application", func(t *testing.T) {
+		branch := getWriteBackBranch(nil)
+		assert.Equal(t, "", branch)
+	})
+
+	t.Run("matching git-repository annotation", func(t *testing.T) {
+		app := &v1alpha1.Application{
+			ObjectMeta: v1.ObjectMeta{
+				Name: "test-app",
+				Annotations: map[string]string{
+					"argocd-image-updater.argoproj.io/git-repository": "https://github.com/chengfang/image-updater-examples.git",
+				},
+			},
+			Spec: v1alpha1.ApplicationSpec{
+				Sources: v1alpha1.ApplicationSources{
+					{
+						RepoURL:        "https://charts.bitnami.com/bitnami",
+						TargetRevision: "18.2.3",
+						Chart:          "nginx",
+					},
+					{
+						RepoURL:        "https://github.com/chengfang/image-updater-examples.git",
+						TargetRevision: "main",
+					},
+				},
+			},
+		}
+
+		branch := getWriteBackBranch(app)
+		assert.Equal(t, "main", branch)
+	})
+
+	t.Run("fallback to primary source when no match", func(t *testing.T) {
+		app := &v1alpha1.Application{
+			ObjectMeta: v1.ObjectMeta{
+				Name: "test-app",
+			},
+			Spec: v1alpha1.ApplicationSpec{
+				Sources: v1alpha1.ApplicationSources{
+					{
+						RepoURL:        "https://charts.bitnami.com/bitnami",
+						TargetRevision: "18.2.3",
+						Chart:          "nginx",
+						Helm:           &v1alpha1.ApplicationSourceHelm{},
+					},
+					{
+						RepoURL:        "https://github.com/chengfang/image-updater-examples.git",
+						TargetRevision: "main",
+					},
+				},
+			},
+		}
+
+		branch := getWriteBackBranch(app)
+		assert.Equal(t, "18.2.3", branch)
+	})
+
+	t.Run("git-repository annotation with non-matching URL", func(t *testing.T) {
+		app := &v1alpha1.Application{
+			ObjectMeta: v1.ObjectMeta{
+				Name: "test-app",
+				Annotations: map[string]string{
+					"argocd-image-updater.argoproj.io/git-repository": "https://github.com/different/repo.git",
+				},
+			},
+			Spec: v1alpha1.ApplicationSpec{
+				Sources: v1alpha1.ApplicationSources{
+					{
+						RepoURL:        "https://charts.bitnami.com/bitnami",
+						TargetRevision: "18.2.3",
+						Chart:          "nginx",
+						Helm:           &v1alpha1.ApplicationSourceHelm{},
+					},
+				},
+			},
+		}
+
+		branch := getWriteBackBranch(app)
+		assert.Equal(t, "18.2.3", branch)
+	})
+}