diff --git a/cmd/serv.go b/cmd/serv.go index 089d0e3bb7..60f7fb92ff 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -229,11 +229,6 @@ func runServ(ctx context.Context, c *cli.Command) error { username := repoPathFields[0] reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki" - // LowerCase and trim the repoPath as that's how they are stored. - // This should be done after splitting the repoPath into username and reponame - // so that username and reponame are not affected. - repoPath = strings.ToLower(strings.TrimSpace(repoPath)) - if !repo.IsValidSSHAccessRepoName(reponame) { return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) } @@ -280,6 +275,11 @@ func runServ(ctx context.Context, c *cli.Command) error { return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error) } + // LowerCase and trim the repoPath as that's how they are stored. + // This should be done after splitting the repoPath into username and reponame + // so that username and reponame are not affected. + repoPath = strings.ToLower(results.OwnerName + "/" + results.RepoName + ".git") + // LFS SSH protocol if verb == git.CmdVerbLfsTransfer { token, err := getLFSAuthToken(ctx, lfsVerb, results) diff --git a/models/fixtures/user_redirect.yml b/models/fixtures/user_redirect.yml index 8ff7993398..c668cb6c3b 100644 --- a/models/fixtures/user_redirect.yml +++ b/models/fixtures/user_redirect.yml @@ -2,3 +2,7 @@ id: 1 lower_name: olduser1 redirect_user_id: 1 +- + id: 2 + lower_name: olduser2 + redirect_user_id: 2 diff --git a/routers/private/serv.go b/routers/private/serv.go index b879be0dc2..3dfe4d21da 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -108,6 +108,18 @@ func ServCommand(ctx *context.PrivateContext) { results.RepoName = repoName[:len(repoName)-5] } + // Check if there is a user redirect for the requested owner + redirectedUserID, err := user_model.LookupUserRedirect(ctx, results.OwnerName) + if err == nil { + owner, err := user_model.GetUserByID(ctx, redirectedUserID) + if err == nil { + log.Info("User %s has been redirected to %s", results.OwnerName, owner.Name) + results.OwnerName = owner.Name + } else { + log.Warn("User %s has a redirect to user with ID %d, but no user with this ID could be found. Trying without redirect...", results.OwnerName, redirectedUserID) + } + } + owner, err := user_model.GetUserByName(ctx, results.OwnerName) if err != nil { if user_model.IsErrUserNotExist(err) { @@ -131,6 +143,19 @@ func ServCommand(ctx *context.PrivateContext) { return } + redirectedRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, results.RepoName) + if err == nil { + redirectedRepo, err := repo_model.GetRepositoryByID(ctx, redirectedRepoID) + if err == nil { + log.Info("Repository %s/%s has been redirected to %s/%s", results.OwnerName, results.RepoName, redirectedRepo.OwnerName, redirectedRepo.Name) + results.RepoName = redirectedRepo.Name + results.OwnerName = redirectedRepo.OwnerName + owner.ID = redirectedRepo.OwnerID + } else { + log.Warn("Repo %s/%s has a redirect to repo with ID %d, but no repo with this ID could be found. Trying without redirect...", results.OwnerName, results.RepoName, redirectedRepoID) + } + } + // Now get the Repository and set the results section repoExist := true repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, results.RepoName) diff --git a/tests/integration/git_ssh_redirect_test.go b/tests/integration/git_ssh_redirect_test.go new file mode 100644 index 0000000000..5e35ed2a74 --- /dev/null +++ b/tests/integration/git_ssh_redirect_test.go @@ -0,0 +1,42 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/url" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" +) + +func TestGitSSHRedirect(t *testing.T) { + onGiteaRun(t, testGitSSHRedirect) +} + +func testGitSSHRedirect(t *testing.T, u *url.URL) { + apiTestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + withKeyFile(t, "my-testing-key", func(keyFile string) { + t.Run("CreateUserKey", doAPICreateUserKey(apiTestContext, "test-key", keyFile)) + + testCases := []struct { + testName string + userName string + repoName string + }{ + {"Test untouched", "user2", "repo1"}, + {"Test renamed user", "olduser2", "repo1"}, + {"Test renamed repo", "user2", "oldrepo1"}, + {"Test renamed user and repo", "olduser2", "oldrepo1"}, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + cloneURL := createSSHUrl(fmt.Sprintf("%s/%s.git", tc.userName, tc.repoName), u) + t.Run("Clone", doGitClone(t.TempDir(), cloneURL)) + }) + } + }) +}