diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index eacc732c22..8e64c834d7 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -78,8 +78,9 @@ RUN_USER = ; git
 ;; Set the domain for the server
 ;DOMAIN = localhost
 ;;
-;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
-;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
+;; The AppURL used by Gitea to generate absolute links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/".
+;; Most users should set it to the real website URL of their Gitea instance.
+;ROOT_URL =
 ;;
 ;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
 ;; DO NOT USE IT IN PRODUCTION!!!
@@ -103,8 +104,8 @@ RUN_USER = ; git
 ;REDIRECT_OTHER_PORT = false
 ;PORT_TO_REDIRECT = 80
 ;;
-;; expect PROXY protocol header on connections to https redirector.
-;REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s
+;; expect PROXY protocol header on connections to https redirector, defaults to USE_PROXY_PROTOCOL
+;REDIRECTOR_USE_PROXY_PROTOCOL =
 ;; Minimum and maximum supported TLS versions
 ;SSL_MIN_VERSION=TLSv1.2
 ;SSL_MAX_VERSION=
@@ -128,13 +129,14 @@ RUN_USER = ; git
 ;; most cases you do not need to change the default value. Alter it only if
 ;; your SSH server node is not the same as HTTP node. For different protocol, the default
 ;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
-;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`.
-;; If listen on `0.0.0.0`, the default value is `%(PROTOCOL)s://localhost:%(HTTP_PORT)s/`, Otherwise the default
-;; value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`.
-;LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
+;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
+;; If listen on `0.0.0.0`, the default value is `{PROTOCOL}://localhost:{HTTP_PORT}/`.
+;; Otherwise the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
+;; Most users don't need (and shouldn't) set this value.
+;LOCAL_ROOT_URL =
 ;;
-;; When making local connections pass the PROXY protocol header.
-;LOCAL_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s
+;; When making local connections pass the PROXY protocol header, defaults to USE_PROXY_PROTOCOL
+;LOCAL_USE_PROXY_PROTOCOL =
 ;;
 ;; Disable SSH feature when not available
 ;DISABLE_SSH = false
@@ -146,13 +148,17 @@ RUN_USER = ; git
 ;SSH_SERVER_USE_PROXY_PROTOCOL = false
 ;;
 ;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
-;BUILTIN_SSH_SERVER_USER = %(RUN_USER)s
+;BUILTIN_SSH_SERVER_USER =
 ;;
-;; Domain name to be exposed in clone URL
-;SSH_DOMAIN = %(DOMAIN)s
+;; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL
+;SSH_DOMAIN =
 ;;
-;; SSH username displayed in clone URLs.
-;SSH_USER = %(BUILTIN_SSH_SERVER_USER)s
+;; SSH username displayed in clone URLs. It defaults to BUILTIN_SSH_SERVER_USER or RUN_USER.
+;; If it is set to "(DOER_USERNAME)", it will use current signed-in user's username.
+;; This option is only for some advanced users who have configured their SSH reverse-proxy
+;; and need to use different usernames for git SSH clone.
+;; Most users should just leave it blank.
+;SSH_USER =
 ;;
 ;; The network interface the builtin SSH server should listen on
 ;SSH_LISTEN_HOST =
@@ -160,8 +166,8 @@ RUN_USER = ; git
 ;; Port number to be exposed in clone URL
 ;SSH_PORT = 22
 ;;
-;; The port number the builtin SSH server should listen on
-;SSH_LISTEN_PORT = %(SSH_PORT)s
+;; The port number the builtin SSH server should listen on, defaults to SSH_PORT
+;SSH_LISTEN_PORT =
 ;;
 ;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
 ;SSH_ROOT_PATH =
@@ -188,7 +194,7 @@ RUN_USER = ; git
 ;;
 ;; For the built-in SSH server, choose the keypair to offer as the host key
 ;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
-;; relative paths are made absolute relative to the %(APP_DATA_PATH)s
+;; relative paths are made absolute relative to the APP_DATA_PATH
 ;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
 ;;
 ;; Directory to create temporary files in when testing public keys using ssh-keygen,
@@ -582,7 +588,7 @@ ENABLED = true
 [log]
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Root path for the log files - defaults to %(GITEA_WORK_DIR)/log
+;; Root path for the log files - defaults to "{AppWorkPath}/log"
 ;ROOT_PATH =
 ;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -682,8 +688,8 @@ LEVEL = Info
 ;; The path of git executable. If empty, Gitea searches through the PATH environment.
 ;PATH =
 ;;
-;; The HOME directory for Git
-;HOME_PATH = %(APP_DATA_PATH)s/home
+;; The HOME directory for Git, defaults to "{APP_DATA_PATH}/home"
+;HOME_PATH =
 ;;
 ;; Disables highlight of added and removed changes
 ;DISABLE_DIFF_HIGHLIGHT = false
@@ -946,8 +952,8 @@ LEVEL = Info
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;[repository]
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories.
-;; A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s
+;; Root path for storing all repository data. By default, it is set to "{APP_DATA_PATH}/gitea-repositories".
+;; A relative path is interpreted as "{AppWorkPath}/{ROOT}" (use AppWorkPath as base path).
 ;ROOT =
 ;;
 ;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available.
@@ -1506,7 +1512,8 @@ LEVEL = Info
 ;TYPE = persistable-channel
 ;;
 ;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
-;DATADIR = queues/ ; Relative paths will be made absolute against `%(APP_DATA_PATH)s`.
+;; Relative paths will be made absolute against "APP_DATA_PATH"
+;DATADIR = queues/
 ;;
 ;; Default queue length before a channel queue will block
 ;LENGTH = 100000
diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go
index 15177bf040..9d22c9052e 100644
--- a/models/migrations/v1_21/v276.go
+++ b/models/migrations/v1_21/v276.go
@@ -172,7 +172,7 @@ func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
 		return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
 	}
 
-	u, err := giturl.Parse(remoteURL)
+	u, err := giturl.ParseGitURL(remoteURL)
 	if err != nil {
 		return "", err
 	}
diff --git a/models/repo/repo.go b/models/repo/repo.go
index af4a1f7fb5..4432fef810 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -20,6 +20,7 @@ import (
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
+	giturl "code.gitea.io/gitea/modules/git/url"
 	"code.gitea.io/gitea/modules/httplib"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
@@ -637,14 +638,26 @@ type CloneLink struct {
 }
 
 // ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
-func ComposeHTTPSCloneURL(owner, repo string) string {
-	return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo))
+func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string {
+	return fmt.Sprintf("%s%s/%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), url.PathEscape(repo))
 }
 
-func ComposeSSHCloneURL(ownerName, repoName string) string {
+func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
 	sshUser := setting.SSH.User
 	sshDomain := setting.SSH.Domain
 
+	if sshUser == "(DOER_USERNAME)" {
+		// Some users use SSH reverse-proxy and need to use the current signed-in username as the SSH user
+		// to make the SSH reverse-proxy could prepare the user's public keys ahead.
+		// For most cases we have the correct "doer", then use it as the SSH user.
+		// If we can't get the doer, then use the built-in SSH user.
+		if doer != nil {
+			sshUser = doer.Name
+		} else {
+			sshUser = setting.SSH.BuiltinServerUser
+		}
+	}
+
 	// non-standard port, it must use full URI
 	if setting.SSH.Port != 22 {
 		sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port))
@@ -662,21 +675,20 @@ func ComposeSSHCloneURL(ownerName, repoName string) string {
 	return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
 }
 
-func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
-	repoName := repo.Name
-	if isWiki {
-		repoName += ".wiki"
-	}
-
+func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
 	cl := new(CloneLink)
-	cl.SSH = ComposeSSHCloneURL(repo.OwnerName, repoName)
-	cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName)
+	cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName)
+	cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName)
 	return cl
 }
 
 // CloneLink returns clone URLs of repository.
-func (repo *Repository) CloneLink() (cl *CloneLink) {
-	return repo.cloneLink(false)
+func (repo *Repository) CloneLink(ctx context.Context, doer *user_model.User) (cl *CloneLink) {
+	return repo.cloneLink(ctx, doer, repo.Name)
+}
+
+func (repo *Repository) CloneLinkGeneral(ctx context.Context) (cl *CloneLink) {
+	return repo.cloneLink(ctx, nil /* no doer, use a general git user */, repo.Name)
 }
 
 // GetOriginalURLHostname returns the hostname of a URL or the URL
@@ -772,47 +784,75 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo
 	return &repo, err
 }
 
-// getRepositoryURLPathSegments returns segments (owner, reponame) extracted from a url
-func getRepositoryURLPathSegments(repoURL string) []string {
-	if strings.HasPrefix(repoURL, setting.AppURL) {
-		return strings.Split(strings.TrimPrefix(repoURL, setting.AppURL), "/")
-	}
+func parseRepositoryURL(ctx context.Context, repoURL string) (ret struct {
+	OwnerName, RepoName, RemainingPath string
+},
+) {
+	// possible urls for git:
+	//  https://my.domain/sub-path/<owner>/<repo>[.git]
+	//  git+ssh://user@my.domain/<owner>/<repo>[.git]
+	//  ssh://user@my.domain/<owner>/<repo>[.git]
+	//  user@my.domain:<owner>/<repo>[.git]
 
-	sshURLVariants := [4]string{
-		setting.SSH.Domain + ":",
-		setting.SSH.User + "@" + setting.SSH.Domain + ":",
-		"git+ssh://" + setting.SSH.Domain + "/",
-		"git+ssh://" + setting.SSH.User + "@" + setting.SSH.Domain + "/",
-	}
-
-	for _, sshURL := range sshURLVariants {
-		if strings.HasPrefix(repoURL, sshURL) {
-			return strings.Split(strings.TrimPrefix(repoURL, sshURL), "/")
+	fillPathParts := func(s string) {
+		s = strings.TrimPrefix(s, "/")
+		fields := strings.SplitN(s, "/", 3)
+		if len(fields) >= 2 {
+			ret.OwnerName = fields[0]
+			ret.RepoName = strings.TrimSuffix(fields[1], ".git")
+			if len(fields) == 3 {
+				ret.RemainingPath = "/" + fields[2]
+			}
 		}
 	}
 
-	return nil
+	parsed, err := giturl.ParseGitURL(repoURL)
+	if err != nil {
+		return ret
+	}
+	if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" {
+		if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) {
+			return ret
+		}
+		fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL))
+	} else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" {
+		domainSSH := setting.SSH.Domain
+		domainCur := httplib.GuessCurrentHostDomain(ctx)
+		urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host)
+		urlDomain = util.IfZero(urlDomain, parsed.URL.Host)
+		if urlDomain == "" {
+			return ret
+		}
+		// check whether URL domain is the App domain
+		domainMatches := domainSSH == urlDomain
+		// check whether URL domain is current domain from context
+		domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain)
+		if domainMatches {
+			fillPathParts(parsed.URL.Path)
+		}
+	}
+	return ret
 }
 
 // GetRepositoryByURL returns the repository by given url
 func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) {
-	// possible urls for git:
-	//  https://my.domain/sub-path/<owner>/<repo>.git
-	//  https://my.domain/sub-path/<owner>/<repo>
-	//  git+ssh://user@my.domain/<owner>/<repo>.git
-	//  git+ssh://user@my.domain/<owner>/<repo>
-	//  user@my.domain:<owner>/<repo>.git
-	//  user@my.domain:<owner>/<repo>
-
-	pathSegments := getRepositoryURLPathSegments(repoURL)
-
-	if len(pathSegments) != 2 {
+	ret := parseRepositoryURL(ctx, repoURL)
+	if ret.OwnerName == "" {
 		return nil, fmt.Errorf("unknown or malformed repository URL")
 	}
+	return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName)
+}
 
-	ownerName := pathSegments[0]
-	repoName := strings.TrimSuffix(pathSegments[1], ".git")
-	return GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
+// GetRepositoryByURLRelax also accepts an SSH clone URL without user part
+func GetRepositoryByURLRelax(ctx context.Context, repoURL string) (*Repository, error) {
+	if !strings.Contains(repoURL, "://") && !strings.Contains(repoURL, "@") {
+		// convert "example.com:owner/repo" to "@example.com:owner/repo"
+		p1, p2, p3 := strings.Index(repoURL, "."), strings.Index(repoURL, ":"), strings.Index(repoURL, "/")
+		if 0 < p1 && p1 < p2 && p2 < p3 {
+			repoURL = "@" + repoURL
+		}
+	}
+	return GetRepositoryByURL(ctx, repoURL)
 }
 
 // GetRepositoryByID returns the repository by given id if exists.
diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go
index 001f8ecd84..ffae642285 100644
--- a/models/repo/repo_test.go
+++ b/models/repo/repo_test.go
@@ -4,18 +4,23 @@
 package repo
 
 import (
+	"context"
+	"net/http"
+	"net/url"
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/httplib"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/test"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 var (
@@ -127,65 +132,121 @@ func TestMetas(t *testing.T) {
 	assert.Equal(t, ",owners,team1,", metas["teams"])
 }
 
+func TestParseRepositoryURLPathSegments(t *testing.T) {
+	defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")()
+
+	ctxURL, _ := url.Parse("https://gitea")
+	ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}}
+	ctxReq.Host = ctxURL.Host
+	ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme)
+	ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq)
+	cases := []struct {
+		input                          string
+		ownerName, repoName, remaining string
+	}{
+		{input: "/user/repo"},
+
+		{input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"},
+		{input: "https://external:3000/user/repo"},
+
+		{input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
+
+		{input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"},
+		{input: "https://gitea:3333/user/repo"},
+
+		{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
+		{input: "ssh://external:2222/user/repo"},
+
+		{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
+		{input: "git+ssh://user@external/user/repo.git"},
+
+		{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
+		{input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"},
+		{input: "root@external:user/repo.git"},
+	}
+
+	for _, c := range cases {
+		t.Run(c.input, func(t *testing.T) {
+			ret := parseRepositoryURL(ctx, c.input)
+			assert.Equal(t, c.ownerName, ret.OwnerName)
+			assert.Equal(t, c.repoName, ret.RepoName)
+			assert.Equal(t, c.remaining, ret.RemainingPath)
+		})
+	}
+
+	t.Run("WithSubpath", func(t *testing.T) {
+		defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
+		defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
+		cases = []struct {
+			input                          string
+			ownerName, repoName, remaining string
+		}{
+			{input: "https://localhost:3000/user/repo"},
+			{input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
+
+			{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
+			{input: "ssh://external:2222/user/repo"},
+
+			{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
+			{input: "git+ssh://user@external/user/repo.git"},
+
+			{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
+			{input: "root@external:user/repo.git"},
+		}
+
+		for _, c := range cases {
+			t.Run(c.input, func(t *testing.T) {
+				ret := parseRepositoryURL(ctx, c.input)
+				assert.Equal(t, c.ownerName, ret.OwnerName)
+				assert.Equal(t, c.repoName, ret.RepoName)
+				assert.Equal(t, c.remaining, ret.RemainingPath)
+			})
+		}
+	})
+}
+
 func TestGetRepositoryByURL(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	t.Run("InvalidPath", func(t *testing.T) {
 		repo, err := GetRepositoryByURL(db.DefaultContext, "something")
-
 		assert.Nil(t, repo)
 		assert.Error(t, err)
 	})
 
+	testRepo2 := func(t *testing.T, url string) {
+		repo, err := GetRepositoryByURL(db.DefaultContext, url)
+		require.NoError(t, err)
+		assert.EqualValues(t, 2, repo.ID)
+		assert.EqualValues(t, 2, repo.OwnerID)
+	}
+
 	t.Run("ValidHttpURL", func(t *testing.T) {
-		test := func(t *testing.T, url string) {
-			repo, err := GetRepositoryByURL(db.DefaultContext, url)
-
-			assert.NotNil(t, repo)
-			assert.NoError(t, err)
-
-			assert.Equal(t, int64(2), repo.ID)
-			assert.Equal(t, int64(2), repo.OwnerID)
-		}
-
-		test(t, "https://try.gitea.io/user2/repo2")
-		test(t, "https://try.gitea.io/user2/repo2.git")
+		testRepo2(t, "https://try.gitea.io/user2/repo2")
+		testRepo2(t, "https://try.gitea.io/user2/repo2.git")
 	})
 
 	t.Run("ValidGitSshURL", func(t *testing.T) {
-		test := func(t *testing.T, url string) {
-			repo, err := GetRepositoryByURL(db.DefaultContext, url)
+		testRepo2(t, "git+ssh://sshuser@try.gitea.io/user2/repo2")
+		testRepo2(t, "git+ssh://sshuser@try.gitea.io/user2/repo2.git")
 
-			assert.NotNil(t, repo)
-			assert.NoError(t, err)
-
-			assert.Equal(t, int64(2), repo.ID)
-			assert.Equal(t, int64(2), repo.OwnerID)
-		}
-
-		test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2")
-		test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2.git")
-
-		test(t, "git+ssh://try.gitea.io/user2/repo2")
-		test(t, "git+ssh://try.gitea.io/user2/repo2.git")
+		testRepo2(t, "git+ssh://try.gitea.io/user2/repo2")
+		testRepo2(t, "git+ssh://try.gitea.io/user2/repo2.git")
 	})
 
 	t.Run("ValidImplicitSshURL", func(t *testing.T) {
-		test := func(t *testing.T, url string) {
-			repo, err := GetRepositoryByURL(db.DefaultContext, url)
-
-			assert.NotNil(t, repo)
-			assert.NoError(t, err)
+		testRepo2(t, "sshuser@try.gitea.io:user2/repo2")
+		testRepo2(t, "sshuser@try.gitea.io:user2/repo2.git")
 
+		testRelax := func(t *testing.T, url string) {
+			repo, err := GetRepositoryByURLRelax(db.DefaultContext, url)
+			require.NoError(t, err)
 			assert.Equal(t, int64(2), repo.ID)
 			assert.Equal(t, int64(2), repo.OwnerID)
 		}
-
-		test(t, "sshuser@try.gitea.io:user2/repo2")
-		test(t, "sshuser@try.gitea.io:user2/repo2.git")
-
-		test(t, "try.gitea.io:user2/repo2")
-		test(t, "try.gitea.io:user2/repo2.git")
+		// TODO: it doesn't seem to be common git ssh URL, should we really support this?
+		testRelax(t, "try.gitea.io:user2/repo2")
+		testRelax(t, "try.gitea.io:user2/repo2.git")
 	})
 }
 
@@ -199,23 +260,30 @@ func TestComposeSSHCloneURL(t *testing.T) {
 	setting.SSH.Domain = "domain"
 	setting.SSH.Port = 22
 	setting.Repository.UseCompatSSHURI = false
-	assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL("user", "repo"))
+	assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
 	setting.Repository.UseCompatSSHURI = true
-	assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL("user", "repo"))
+	assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
 	// test SSH_DOMAIN while use non-standard SSH port
 	setting.SSH.Port = 123
 	setting.Repository.UseCompatSSHURI = false
-	assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
+	assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL(nil, "user", "repo"))
 	setting.Repository.UseCompatSSHURI = true
-	assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
+	assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL(nil, "user", "repo"))
 
 	// test IPv6 SSH_DOMAIN
 	setting.Repository.UseCompatSSHURI = false
 	setting.SSH.Domain = "::1"
 	setting.SSH.Port = 22
-	assert.Equal(t, "git@[::1]:user/repo.git", ComposeSSHCloneURL("user", "repo"))
+	assert.Equal(t, "git@[::1]:user/repo.git", ComposeSSHCloneURL(nil, "user", "repo"))
 	setting.SSH.Port = 123
-	assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
+	assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", ComposeSSHCloneURL(nil, "user", "repo"))
+
+	setting.SSH.User = "(DOER_USERNAME)"
+	setting.SSH.Domain = "domain"
+	setting.SSH.Port = 22
+	assert.Equal(t, "doer@domain:user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
+	setting.SSH.Port = 123
+	assert.Equal(t, "ssh://doer@domain:123/user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
 }
 
 func TestIsUsableRepoName(t *testing.T) {
diff --git a/models/repo/wiki.go b/models/repo/wiki.go
index b378666a20..4239a815b2 100644
--- a/models/repo/wiki.go
+++ b/models/repo/wiki.go
@@ -5,6 +5,7 @@
 package repo
 
 import (
+	"context"
 	"fmt"
 	"path/filepath"
 	"strings"
@@ -72,8 +73,8 @@ func (err ErrWikiInvalidFileName) Unwrap() error {
 }
 
 // WikiCloneLink returns clone URLs of repository wiki.
-func (repo *Repository) WikiCloneLink() *CloneLink {
-	return repo.cloneLink(true)
+func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User) *CloneLink {
+	return repo.cloneLink(ctx, doer, repo.Name+".wiki")
 }
 
 // WikiPath returns wiki data path by given user and repository name.
diff --git a/models/repo/wiki_test.go b/models/repo/wiki_test.go
index 629986f741..0157b7735d 100644
--- a/models/repo/wiki_test.go
+++ b/models/repo/wiki_test.go
@@ -4,6 +4,7 @@
 package repo_test
 
 import (
+	"context"
 	"path/filepath"
 	"testing"
 
@@ -18,7 +19,7 @@ func TestRepository_WikiCloneLink(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
-	cloneLink := repo.WikiCloneLink()
+	cloneLink := repo.WikiCloneLink(context.Background(), nil)
 	assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH)
 	assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
 }
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index 0dcff166cc..7a9ca9698d 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -84,6 +84,7 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) {
 
 	setting.IsInTesting = true
 	setting.AppURL = "https://try.gitea.io/"
+	setting.Domain = "try.gitea.io"
 	setting.RunUser = "runuser"
 	setting.SSH.User = "sshuser"
 	setting.SSH.BuiltinServerUser = "builtinuser"
diff --git a/modules/git/remote.go b/modules/git/remote.go
index de8d74eded..88d824b52b 100644
--- a/modules/git/remote.go
+++ b/modules/git/remote.go
@@ -39,7 +39,7 @@ func GetRemoteURL(ctx context.Context, repoPath, remoteName string) (*giturl.Git
 	if err != nil {
 		return nil, err
 	}
-	return giturl.Parse(addr)
+	return giturl.ParseGitURL(addr)
 }
 
 // ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
diff --git a/modules/git/url/url.go b/modules/git/url/url.go
index 637685183e..667e542199 100644
--- a/modules/git/url/url.go
+++ b/modules/git/url/url.go
@@ -21,7 +21,7 @@ func (err ErrWrongURLFormat) Error() string {
 // GitURL represents a git URL
 type GitURL struct {
 	*stdurl.URL
-	extraMark int // 0 no extra 1 scp 2 file path with no prefix
+	extraMark int // 0: standard URL with scheme, 1: scp short syntax (no scheme), 2: file path with no prefix
 }
 
 // String returns the URL's string
@@ -38,8 +38,11 @@ func (u *GitURL) String() string {
 	}
 }
 
-// Parse parse all kinds of git URL
-func Parse(remote string) (*GitURL, error) {
+// ParseGitURL parse all kinds of git URL:
+// * Full URL: http://git@host/path, http://git@host:port/path
+// * SCP short syntax: git@host:/path
+// * File path: /dir/repo/path
+func ParseGitURL(remote string) (*GitURL, error) {
 	if strings.Contains(remote, "://") {
 		u, err := stdurl.Parse(remote)
 		if err != nil {
diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go
index da820ed889..a23121a907 100644
--- a/modules/git/url/url_test.go
+++ b/modules/git/url/url_test.go
@@ -157,7 +157,7 @@ func TestParseGitURLs(t *testing.T) {
 
 	for _, kase := range kases {
 		t.Run(kase.kase, func(t *testing.T) {
-			u, err := Parse(kase.kase)
+			u, err := ParseGitURL(kase.kase)
 			assert.NoError(t, err)
 			assert.EqualValues(t, kase.expected.extraMark, u.extraMark)
 			assert.EqualValues(t, *kase.expected, *u)
diff --git a/modules/httplib/url.go b/modules/httplib/url.go
index e3bad1e5fb..f543c09190 100644
--- a/modules/httplib/url.go
+++ b/modules/httplib/url.go
@@ -5,6 +5,7 @@ package httplib
 
 import (
 	"context"
+	"net"
 	"net/http"
 	"net/url"
 	"strings"
@@ -81,6 +82,12 @@ func GuessCurrentHostURL(ctx context.Context) string {
 	return reqScheme + "://" + req.Host
 }
 
+func GuessCurrentHostDomain(ctx context.Context) string {
+	_, host, _ := strings.Cut(GuessCurrentHostURL(ctx), "://")
+	domain, _, _ := net.SplitHostPort(host)
+	return util.IfZero(domain, host)
+}
+
 // MakeAbsoluteURL tries to make a link to an absolute URL:
 // * If link is empty, it returns the current app URL.
 // * If link is absolute, it returns the link.
@@ -105,7 +112,7 @@ func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
 		if cleanedPath == "" || cleanedPath == "." {
 			u.Path = "/"
 		} else {
-			u.Path += "/" + cleanedPath + "/"
+			u.Path = "/" + cleanedPath + "/"
 		}
 	}
 	if urlIsRelative(s, u) {
diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go
index d645fa013e..2d42bc76b5 100644
--- a/modules/templates/util_misc.go
+++ b/modules/templates/util_misc.go
@@ -150,7 +150,7 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa
 		return ret
 	}
 
-	u, err := giturl.Parse(remoteURL)
+	u, err := giturl.ParseGitURL(remoteURL)
 	if err != nil {
 		log.Error("giturl.Parse %v", err)
 		return ret
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 1372303049..6ec46bcb36 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -163,7 +163,7 @@ func UploadPackage(ctx *context.Context) {
 		return
 	}
 
-	repo, err := repo_model.GetRepositoryByURL(ctx, npmPackage.Metadata.Repository.URL)
+	repo, err := repo_model.GetRepositoryByURLRelax(ctx, npmPackage.Metadata.Repository.URL)
 	if err == nil {
 		canWrite := repo.OwnerID == ctx.Doer.ID
 
diff --git a/routers/web/goget.go b/routers/web/goget.go
index 3714dd8eb0..79d5c2b207 100644
--- a/routers/web/goget.go
+++ b/routers/web/goget.go
@@ -69,9 +69,9 @@ func goGet(ctx *context.Context) {
 
 	var cloneURL string
 	if setting.Repository.GoGetCloneURLProtocol == "ssh" {
-		cloneURL = repo_model.ComposeSSHCloneURL(ownerName, repoName)
+		cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, ownerName, repoName)
 	} else {
-		cloneURL = repo_model.ComposeHTTPSCloneURL(ownerName, repoName)
+		cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, ownerName, repoName)
 	}
 	goImportContent := fmt.Sprintf("%s git %s", goGetImport, cloneURL /*CloneLink*/)
 	goSourceContent := fmt.Sprintf("%s _ %s %s", goGetImport, prefix+"{/dir}" /*GoDocDirectory*/, prefix+"{/dir}/{file}#L{line}" /*GoDocFile*/)
diff --git a/routers/web/web.go b/routers/web/web.go
index ff91bda3d2..32d65865ac 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -1450,7 +1450,7 @@ func registerRoutes(m *web.Router) {
 		m.Get("/raw/*", repo.WikiRaw)
 	}, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqRepoWikiReader, func(ctx *context.Context) {
 		ctx.Data["PageIsWiki"] = true
-		ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink()
+		ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer)
 	})
 	// end "/{username}/{reponame}/wiki"
 
diff --git a/services/context/repo.go b/services/context/repo.go
index 63529e1d81..4de905ef2c 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -325,9 +325,9 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
 
 	var cloneURL string
 	if setting.Repository.GoGetCloneURLProtocol == "ssh" {
-		cloneURL = repo_model.ComposeSSHCloneURL(username, reponame)
+		cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, username, reponame)
 	} else {
-		cloneURL = repo_model.ComposeHTTPSCloneURL(username, reponame)
+		cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, username, reponame)
 	}
 	goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), cloneURL)
 	htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
@@ -564,7 +564,7 @@ func RepoAssignment(ctx *Context) {
 	// If multiple forks are available or if the user can fork to another account, but there is already a fork: open selection dialog
 	ctx.Data["ShowForkModal"] = len(userAndOrgForks) > 1 || (canSignedUserFork && len(userAndOrgForks) > 0)
 
-	ctx.Data["RepoCloneLink"] = repo.CloneLink()
+	ctx.Data["RepoCloneLink"] = repo.CloneLink(ctx, ctx.Doer)
 
 	cloneButtonShowHTTPS := !setting.Repository.DisableHTTPGit
 	cloneButtonShowSSH := !setting.SSH.Disabled && (ctx.IsSigned || setting.SSH.ExposeAnonymous)
diff --git a/services/convert/repository.go b/services/convert/repository.go
index 88ccd88fcf..632b6392d5 100644
--- a/services/convert/repository.go
+++ b/services/convert/repository.go
@@ -33,7 +33,9 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
 		permissionInRepo.SetUnitsWithDefaultAccessMode(repo.Units, permissionInRepo.AccessMode)
 	}
 
-	cloneLink := repo.CloneLink()
+	// TODO: ideally we should pass "doer" into "ToRepo" to to make CloneLink could generate user-related links
+	// And passing "doer" in will also fix other FIXMEs in this file.
+	cloneLink := repo.CloneLinkGeneral(ctx) // no doer at the moment
 	permission := &api.Permission{
 		Admin: permissionInRepo.AccessMode >= perm.AccessModeAdmin,
 		Push:  permissionInRepo.UnitAccessMode(unit_model.TypeCode) >= perm.AccessModeWrite,
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index 22d380e8e6..400444a26b 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -32,7 +32,7 @@ const gitShortEmptySha = "0000000"
 
 // UpdateAddress writes new address to Git repository and database
 func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error {
-	u, err := giturl.Parse(addr)
+	u, err := giturl.ParseGitURL(addr)
 	if err != nil {
 		return fmt.Errorf("invalid addr: %v", err)
 	}
diff --git a/services/repository/create.go b/services/repository/create.go
index a3199f2a40..23aacd6f95 100644
--- a/services/repository/create.go
+++ b/services/repository/create.go
@@ -79,7 +79,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
 		return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
 	}
 
-	cloneLink := repo.CloneLink()
+	cloneLink := repo.CloneLink(ctx, nil /* no doer so do not generate user-related SSH link */)
 	match := map[string]string{
 		"Name":           repo.Name,
 		"Description":    repo.Description,
diff --git a/services/repository/generate.go b/services/repository/generate.go
index ef9a8dc940..d5c07e9800 100644
--- a/services/repository/generate.go
+++ b/services/repository/generate.go
@@ -51,7 +51,7 @@ var defaultTransformers = []transformer{
 	{Name: "TITLE", Transform: util.ToTitleCase},
 }
 
-func generateExpansion(src string, templateRepo, generateRepo *repo_model.Repository, sanitizeFileName bool) string {
+func generateExpansion(ctx context.Context, src string, templateRepo, generateRepo *repo_model.Repository, sanitizeFileName bool) string {
 	year, month, day := time.Now().Date()
 	expansions := []expansion{
 		{Name: "YEAR", Value: strconv.Itoa(year), Transformers: nil},
@@ -66,10 +66,10 @@ func generateExpansion(src string, templateRepo, generateRepo *repo_model.Reposi
 		{Name: "TEMPLATE_OWNER", Value: templateRepo.OwnerName, Transformers: defaultTransformers},
 		{Name: "REPO_LINK", Value: generateRepo.Link(), Transformers: nil},
 		{Name: "TEMPLATE_LINK", Value: templateRepo.Link(), Transformers: nil},
-		{Name: "REPO_HTTPS_URL", Value: generateRepo.CloneLink().HTTPS, Transformers: nil},
-		{Name: "TEMPLATE_HTTPS_URL", Value: templateRepo.CloneLink().HTTPS, Transformers: nil},
-		{Name: "REPO_SSH_URL", Value: generateRepo.CloneLink().SSH, Transformers: nil},
-		{Name: "TEMPLATE_SSH_URL", Value: templateRepo.CloneLink().SSH, Transformers: nil},
+		{Name: "REPO_HTTPS_URL", Value: generateRepo.CloneLinkGeneral(ctx).HTTPS, Transformers: nil},
+		{Name: "TEMPLATE_HTTPS_URL", Value: templateRepo.CloneLinkGeneral(ctx).HTTPS, Transformers: nil},
+		{Name: "REPO_SSH_URL", Value: generateRepo.CloneLinkGeneral(ctx).SSH, Transformers: nil},
+		{Name: "TEMPLATE_SSH_URL", Value: templateRepo.CloneLinkGeneral(ctx).SSH, Transformers: nil},
 	}
 
 	expansionMap := make(map[string]string)
@@ -138,7 +138,7 @@ func readGiteaTemplateFile(tmpDir string) (*GiteaTemplate, error) {
 	return &GiteaTemplate{Path: gtPath, Content: content}, nil
 }
 
-func processGiteaTemplateFile(tmpDir string, templateRepo, generateRepo *repo_model.Repository, giteaTemplateFile *GiteaTemplate) error {
+func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo, generateRepo *repo_model.Repository, giteaTemplateFile *GiteaTemplate) error {
 	if err := util.Remove(giteaTemplateFile.Path); err != nil {
 		return fmt.Errorf("remove .giteatemplate: %w", err)
 	}
@@ -163,12 +163,12 @@ func processGiteaTemplateFile(tmpDir string, templateRepo, generateRepo *repo_mo
 					return err
 				}
 
-				generatedContent := []byte(generateExpansion(string(content), templateRepo, generateRepo, false))
+				generatedContent := []byte(generateExpansion(ctx, string(content), templateRepo, generateRepo, false))
 				if err := os.WriteFile(path, generatedContent, 0o644); err != nil {
 					return err
 				}
 
-				substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, generateExpansion(base, templateRepo, generateRepo, true)))
+				substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, generateExpansion(ctx, base, templateRepo, generateRepo, true)))
 
 				// Create parent subdirectories if needed or continue silently if it exists
 				if err = os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil {
@@ -226,7 +226,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
 	}
 
 	if giteaTemplateFile != nil {
-		err = processGiteaTemplateFile(tmpDir, templateRepo, generateRepo, giteaTemplateFile)
+		err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, giteaTemplateFile)
 		if err != nil {
 			return err
 		}
diff --git a/tests/integration/dump_restore_test.go b/tests/integration/dump_restore_test.go
index 47bb6f76e9..abec8f300c 100644
--- a/tests/integration/dump_restore_test.go
+++ b/tests/integration/dump_restore_test.go
@@ -66,7 +66,7 @@ func TestDumpRestore(t *testing.T) {
 			Milestones:     true,
 			Comments:       true,
 			AuthToken:      token,
-			CloneAddr:      repo.CloneLink().HTTPS,
+			CloneAddr:      repo.CloneLinkGeneral(context.Background()).HTTPS,
 			RepoName:       reponame,
 		}
 		err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts)
@@ -96,7 +96,7 @@ func TestDumpRestore(t *testing.T) {
 		// Phase 3: dump restored from the Gitea instance to the filesystem
 		//
 		opts.RepoName = newreponame
-		opts.CloneAddr = newrepo.CloneLink().HTTPS
+		opts.CloneAddr = newrepo.CloneLinkGeneral(context.Background()).HTTPS
 		err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts)
 		assert.NoError(t, err)