@@ -20,6 +20,7 @@ import (
2020 user_model "code.gitea.io/gitea/models/user"
2121 "code.gitea.io/gitea/modules/base"
2222 "code.gitea.io/gitea/modules/git"
23+ giturl "code.gitea.io/gitea/modules/git/url"
2324 "code.gitea.io/gitea/modules/httplib"
2425 "code.gitea.io/gitea/modules/log"
2526 "code.gitea.io/gitea/modules/markup"
@@ -637,14 +638,26 @@ type CloneLink struct {
637638}
638639
639640// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
640- func ComposeHTTPSCloneURL (owner , repo string ) string {
641- return fmt .Sprintf ("%s%s/%s.git" , setting . AppURL , url .PathEscape (owner ), url .PathEscape (repo ))
641+ func ComposeHTTPSCloneURL (ctx context. Context , owner , repo string ) string {
642+ return fmt .Sprintf ("%s%s/%s.git" , httplib . GuessCurrentAppURL ( ctx ) , url .PathEscape (owner ), url .PathEscape (repo ))
642643}
643644
644- func ComposeSSHCloneURL (ownerName , repoName string ) string {
645+ func ComposeSSHCloneURL (doer * user_model. User , ownerName , repoName string ) string {
645646 sshUser := setting .SSH .User
646647 sshDomain := setting .SSH .Domain
647648
649+ if sshUser == "(DOER_USERNAME)" {
650+ // Some users use SSH reverse-proxy and need to use the current signed-in username as the SSH user
651+ // to make the SSH reverse-proxy could prepare the user's public keys ahead.
652+ // For most cases we have the correct "doer", then use it as the SSH user.
653+ // If we can't get the doer, then use the built-in SSH user.
654+ if doer != nil {
655+ sshUser = doer .Name
656+ } else {
657+ sshUser = setting .SSH .BuiltinServerUser
658+ }
659+ }
660+
648661 // non-standard port, it must use full URI
649662 if setting .SSH .Port != 22 {
650663 sshHost := net .JoinHostPort (sshDomain , strconv .Itoa (setting .SSH .Port ))
@@ -662,21 +675,20 @@ func ComposeSSHCloneURL(ownerName, repoName string) string {
662675 return fmt .Sprintf ("%s@%s:%s/%s.git" , sshUser , sshHost , url .PathEscape (ownerName ), url .PathEscape (repoName ))
663676}
664677
665- func (repo * Repository ) cloneLink (isWiki bool ) * CloneLink {
666- repoName := repo .Name
667- if isWiki {
668- repoName += ".wiki"
669- }
670-
678+ func (repo * Repository ) cloneLink (ctx context.Context , doer * user_model.User , repoPathName string ) * CloneLink {
671679 cl := new (CloneLink )
672- cl .SSH = ComposeSSHCloneURL (repo .OwnerName , repoName )
673- cl .HTTPS = ComposeHTTPSCloneURL (repo .OwnerName , repoName )
680+ cl .SSH = ComposeSSHCloneURL (doer , repo .OwnerName , repoPathName )
681+ cl .HTTPS = ComposeHTTPSCloneURL (ctx , repo .OwnerName , repoPathName )
674682 return cl
675683}
676684
677685// CloneLink returns clone URLs of repository.
678- func (repo * Repository ) CloneLink () (cl * CloneLink ) {
679- return repo .cloneLink (false )
686+ func (repo * Repository ) CloneLink (ctx context.Context , doer * user_model.User ) (cl * CloneLink ) {
687+ return repo .cloneLink (ctx , doer , repo .Name )
688+ }
689+
690+ func (repo * Repository ) CloneLinkGeneral (ctx context.Context ) (cl * CloneLink ) {
691+ return repo .cloneLink (ctx , nil /* no doer, use a general git user */ , repo .Name )
680692}
681693
682694// GetOriginalURLHostname returns the hostname of a URL or the URL
@@ -772,47 +784,75 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo
772784 return & repo , err
773785}
774786
775- // getRepositoryURLPathSegments returns segments (owner, reponame) extracted from a url
776- func getRepositoryURLPathSegments (repoURL string ) []string {
777- if strings .HasPrefix (repoURL , setting .AppURL ) {
778- return strings .Split (strings .TrimPrefix (repoURL , setting .AppURL ), "/" )
787+ func parseRepositoryURL (ctx context.Context , repoURL string ) (ret struct {
788+ OwnerName , RepoName , RemainingPath string
789+ },
790+ ) {
791+ // possible urls for git:
792+ // https://my.domain/sub-path/<owner>/<repo>[.git]
793+ // git+ssh://[email protected] /<owner>/<repo>[.git] 794+ // ssh://[email protected] /<owner>/<repo>[.git] 795+ // [email protected] :<owner>/<repo>[.git] 796+
797+ fillPathParts := func (s string ) {
798+ s = strings .TrimPrefix (s , "/" )
799+ fields := strings .SplitN (s , "/" , 3 )
800+ if len (fields ) >= 2 {
801+ ret .OwnerName = fields [0 ]
802+ ret .RepoName = strings .TrimSuffix (fields [1 ], ".git" )
803+ if len (fields ) == 3 {
804+ ret .RemainingPath = "/" + fields [2 ]
805+ }
806+ }
779807 }
780808
781- sshURLVariants := [4 ]string {
782- setting .SSH .Domain + ":" ,
783- setting .SSH .User + "@" + setting .SSH .Domain + ":" ,
784- "git+ssh://" + setting .SSH .Domain + "/" ,
785- "git+ssh://" + setting .SSH .User + "@" + setting .SSH .Domain + "/" ,
809+ parsed , err := giturl .ParseGitURL (repoURL )
810+ if err != nil {
811+ return ret
786812 }
787-
788- for _ , sshURL := range sshURLVariants {
789- if strings .HasPrefix (repoURL , sshURL ) {
790- return strings .Split (strings .TrimPrefix (repoURL , sshURL ), "/" )
813+ if parsed .URL .Scheme == "http" || parsed .URL .Scheme == "https" {
814+ if ! httplib .IsCurrentGiteaSiteURL (ctx , repoURL ) {
815+ return ret
816+ }
817+ fillPathParts (strings .TrimPrefix (parsed .URL .Path , setting .AppSubURL ))
818+ } else if parsed .URL .Scheme == "ssh" || parsed .URL .Scheme == "git+ssh" {
819+ domainSSH := setting .SSH .Domain
820+ domainCur := httplib .GuessCurrentHostDomain (ctx )
821+ urlDomain , _ , _ := net .SplitHostPort (parsed .URL .Host )
822+ urlDomain = util .IfZero (urlDomain , parsed .URL .Host )
823+ if urlDomain == "" {
824+ return ret
825+ }
826+ // check whether URL domain is the App domain
827+ domainMatches := domainSSH == urlDomain
828+ // check whether URL domain is current domain from context
829+ domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain )
830+ if domainMatches {
831+ fillPathParts (parsed .URL .Path )
791832 }
792833 }
793-
794- return nil
834+ return ret
795835}
796836
797837// GetRepositoryByURL returns the repository by given url
798838func GetRepositoryByURL (ctx context.Context , repoURL string ) (* Repository , error ) {
799- // possible urls for git:
800- // https://my.domain/sub-path/<owner>/<repo>.git
801- // https://my.domain/sub-path/<owner>/<repo>
802- // git+ssh://[email protected] /<owner>/<repo>.git 803- // git+ssh://[email protected] /<owner>/<repo> 804- // [email protected] :<owner>/<repo>.git 805- // [email protected] :<owner>/<repo> 806-
807- pathSegments := getRepositoryURLPathSegments (repoURL )
808-
809- if len (pathSegments ) != 2 {
839+ ret := parseRepositoryURL (ctx , repoURL )
840+ if ret .OwnerName == "" {
810841 return nil , fmt .Errorf ("unknown or malformed repository URL" )
811842 }
843+ return GetRepositoryByOwnerAndName (ctx , ret .OwnerName , ret .RepoName )
844+ }
812845
813- ownerName := pathSegments [0 ]
814- repoName := strings .TrimSuffix (pathSegments [1 ], ".git" )
815- return GetRepositoryByOwnerAndName (ctx , ownerName , repoName )
846+ // GetRepositoryByURLRelax also accepts an SSH clone URL without user part
847+ func GetRepositoryByURLRelax (ctx context.Context , repoURL string ) (* Repository , error ) {
848+ if ! strings .Contains (repoURL , "://" ) && ! strings .Contains (repoURL , "@" ) {
849+ // convert "example.com:owner/repo" to "@example.com:owner/repo"
850+ p1 , p2 , p3 := strings .Index (repoURL , "." ), strings .Index (repoURL , ":" ), strings .Index (repoURL , "/" )
851+ if 0 < p1 && p1 < p2 && p2 < p3 {
852+ repoURL = "@" + repoURL
853+ }
854+ }
855+ return GetRepositoryByURL (ctx , repoURL )
816856}
817857
818858// GetRepositoryByID returns the repository by given id if exists.
0 commit comments