From 08f4b3f31288bc4e12e94f00c7d88583ab04dd2e Mon Sep 17 00:00:00 2001
From: Viktor Yakovchuk <viktor@yakovchuk.net>
Date: Thu, 24 Jun 2021 00:08:26 +0300
Subject: [PATCH] Fix 500 Error with branch and tag sharing the same name
 #15592 (#16040)

* Fix 500 Error with branch and tag sharing the same name #15592

Fixed 500 error while create Pull request when there are more
than one sources (branch, tag) with the same name

Fix #15592

Signed-off-by: Viktor Yakovchuk <viktor@yakovchuk.net>

* fix logging

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
---
 modules/git/error.go  | 17 +++++++++++++++++
 modules/git/repo.go   |  7 +++++++
 services/pull/pull.go | 14 +++++++++++++-
 3 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/modules/git/error.go b/modules/git/error.go
index 85a938a7da..387dd724e5 100644
--- a/modules/git/error.go
+++ b/modules/git/error.go
@@ -159,3 +159,20 @@ func (err *ErrPushRejected) GenerateMessage() {
 	}
 	err.Message = strings.TrimSpace(messageBuilder.String())
 }
+
+// ErrMoreThanOne represents an error if pull request fails when there are more than one sources (branch, tag) with the same name
+type ErrMoreThanOne struct {
+	StdOut string
+	StdErr string
+	Err    error
+}
+
+// IsErrMoreThanOne checks if an error is a ErrMoreThanOne
+func IsErrMoreThanOne(err error) bool {
+	_, ok := err.(*ErrMoreThanOne)
+	return ok
+}
+
+func (err *ErrMoreThanOne) Error() string {
+	return fmt.Sprintf("ErrMoreThanOne Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
+}
diff --git a/modules/git/repo.go b/modules/git/repo.go
index e06cd43935..43f329f448 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -225,6 +225,13 @@ func Push(repoPath string, opts PushOptions) error {
 			}
 			err.GenerateMessage()
 			return err
+		} else if strings.Contains(errbuf.String(), "matches more than one") {
+			err := &ErrMoreThanOne{
+				StdOut: outbuf.String(),
+				StdErr: errbuf.String(),
+				Err:    err,
+			}
+			return err
 		}
 	}
 
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 02c0a7fe7c..7b5dd6a964 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -389,6 +389,10 @@ func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID st
 // corresponding branches of base repository.
 // FIXME: Only push branches that are actually updates?
 func PushToBaseRepo(pr *models.PullRequest) (err error) {
+	return pushToBaseRepoHelper(pr, "")
+}
+
+func pushToBaseRepoHelper(pr *models.PullRequest, prefixHeadBranch string) (err error) {
 	log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName())
 
 	if err := pr.LoadHeadRepo(); err != nil {
@@ -414,7 +418,7 @@ func PushToBaseRepo(pr *models.PullRequest) (err error) {
 
 	if err := git.Push(headRepoPath, git.PushOptions{
 		Remote: baseRepoPath,
-		Branch: pr.HeadBranch + ":" + gitRefName,
+		Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
 		Force:  true,
 		// Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
 		Env: models.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
@@ -427,6 +431,14 @@ func PushToBaseRepo(pr *models.PullRequest) (err error) {
 			rejectErr := err.(*git.ErrPushRejected)
 			log.Info("Unable to push PR head for %s#%d (%-v:%s) due to rejection:\nStdout: %s\nStderr: %s\nError: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
 			return err
+		} else if git.IsErrMoreThanOne(err) {
+			if prefixHeadBranch != "" {
+				log.Info("Can't push with %s%s", prefixHeadBranch, pr.HeadBranch)
+				return err
+			}
+			log.Info("Retrying to push with refs/heads/%s", pr.HeadBranch)
+			err = pushToBaseRepoHelper(pr, "refs/heads/")
+			return err
 		}
 		log.Error("Unable to push PR head for %s#%d (%-v:%s) due to Error: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err)
 		return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err)