Merge
Some checks failed
/ release (push) Has been cancelled
testing / backend-checks (push) Has been cancelled
testing / frontend-checks (push) Has been cancelled
testing / test-unit (push) Has been cancelled
testing / test-e2e (push) Has been cancelled
testing / test-remote-cacher (redis) (push) Has been cancelled
testing / test-remote-cacher (valkey) (push) Has been cancelled
testing / test-remote-cacher (garnet) (push) Has been cancelled
testing / test-remote-cacher (redict) (push) Has been cancelled
testing / test-mysql (push) Has been cancelled
testing / test-pgsql (push) Has been cancelled
testing / test-sqlite (push) Has been cancelled
testing / security-check (push) Has been cancelled

Signed-off-by: Minecon724 <minecon724@noreply.git.m724.eu>
This commit is contained in:
Minecon724 2025-04-15 13:58:50 +02:00
commit 2ca66a6614
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
34 changed files with 290 additions and 37 deletions

View file

@ -2408,7 +2408,7 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The first locale will be used as the default if user browser's language doesn't match any locale in the list.
;LANGS = en-US,zh-CN,zh-HK,zh-TW,da,de-DE,nds,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg,it-IT,fi-FI,fil,eo,tr-TR,cs-CZ,sl,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Danish,Deutsch,Plattdüütsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Filipino,Esperanto,Türkçe,Čeština,Slovenščina,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Dansk,Deutsch,Plattdüütsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Filipino,Esperanto,Türkçe,Čeština,Slovenščina,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -0,0 +1,17 @@
-
id: 1
size: 10
hash_md5: HASHMD5_1
hash_sha1: HASHSHA1_1
hash_sha256: HASHSHA256_1
hash_sha512: HASHSHA512_1
hash_blake2b: HASHBLAKE2B_1
created_unix: 946687980
-
id: 2
size: 20
hash_md5: HASHMD5_2
hash_sha1: HASHSHA1_2
hash_sha256: HASHSHA256_2
hash_sha512: HASHSHA512_2
created_unix: 946687980

View file

@ -7,8 +7,8 @@ import "xorm.io/xorm"
func AddHashBlake2bToPackageBlob(x *xorm.Engine) error {
type PackageBlob struct {
ID int64 `xorm:"pk autoincr"`
HashBlake2b string
ID int64 `xorm:"pk autoincr"`
HashBlake2b string `xorm:"hash_blake2b char(128) UNIQUE(blake2b) INDEX"`
}
return x.Sync(&PackageBlob{})
}

View file

@ -0,0 +1,31 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package packages
import (
"path/filepath"
"testing"
"forgejo.org/models/unittest"
"forgejo.org/modules/setting"
_ "forgejo.org/models"
_ "forgejo.org/models/actions"
_ "forgejo.org/models/activities"
_ "forgejo.org/models/forgefed"
)
func AddFixtures(dirs ...string) func() {
return unittest.OverrideFixtures(
unittest.FixturesOptions{
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
Base: setting.AppWorkPath,
Dirs: dirs,
},
)
}
func TestMain(m *testing.M) {
unittest.MainTest(m)
}

View file

@ -44,14 +44,19 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
existing := &PackageBlob{}
has, err := e.Where(builder.Eq{
"size": pb.Size,
"hash_md5": pb.HashMD5,
"hash_sha1": pb.HashSHA1,
"hash_sha256": pb.HashSHA256,
"hash_sha512": pb.HashSHA512,
"hash_blake2b": pb.HashBlake2b,
}).Get(existing)
has, err := e.Where(builder.And(
builder.Eq{
"size": pb.Size,
"hash_md5": pb.HashMD5,
"hash_sha1": pb.HashSHA1,
"hash_sha256": pb.HashSHA256,
"hash_sha512": pb.HashSHA512,
},
builder.Or(
builder.Eq{"hash_blake2b": pb.HashBlake2b},
builder.IsNull{"hash_blake2b"},
),
)).Get(existing)
if err != nil {
return nil, false, err
}

View file

@ -0,0 +1,64 @@
// Copyright 2025 The Forgejo Authors.
// SPDX-License-Identifier: GPL-3.0-or-later
package packages
import (
"testing"
"forgejo.org/models/unittest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPackagesGetOrInsertBlob(t *testing.T) {
defer AddFixtures("models/fixtures/TestPackagesGetOrInsertBlob/")()
require.NoError(t, unittest.PrepareTestDatabase())
blake2bIsSet := unittest.AssertExistsAndLoadBean(t, &PackageBlob{ID: 1})
blake2bNotSet := unittest.AssertExistsAndLoadBean(t, &PackageBlob{ID: 2})
blake2bSetToRandom := *blake2bNotSet
blake2bSetToRandom.HashBlake2b = "SOMETHING RANDOM"
for _, testCase := range []struct {
name string
exists bool
packageBlob *PackageBlob
}{
{
name: "exists and blake2b is not null in the database",
exists: true,
packageBlob: blake2bIsSet,
},
{
name: "exists and blake2b is null in the database",
exists: true,
packageBlob: &blake2bSetToRandom,
},
{
name: "does not exists",
exists: false,
packageBlob: &PackageBlob{
Size: 30,
HashMD5: "HASHMD5_3",
HashSHA1: "HASHSHA1_3",
HashSHA256: "HASHSHA256_3",
HashSHA512: "HASHSHA512_3",
HashBlake2b: "HASHBLAKE2B_3",
},
},
} {
t.Run(testCase.name, func(t *testing.T) {
found, has, _ := GetOrInsertBlob(t.Context(), testCase.packageBlob)
assert.Equal(t, testCase.exists, has)
require.NotNil(t, found)
if testCase.exists {
assert.Equal(t, found.ID, testCase.packageBlob.ID)
} else {
unittest.BeanExists(t, &PackageBlob{ID: found.ID})
}
})
}
}

View file

@ -13,18 +13,9 @@ import (
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
_ "forgejo.org/models"
_ "forgejo.org/models/actions"
_ "forgejo.org/models/activities"
_ "forgejo.org/models/forgefed"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
func prepareExamplePackage(t *testing.T) *packages_model.Package {
require.NoError(t, unittest.PrepareTestDatabase())

View file

@ -34,6 +34,7 @@ type PullRequest struct {
Assignees []string
IsLocked bool `yaml:"is_locked"`
Reactions []*Reaction
Flow int64
ForeignIndex int64
Context DownloaderContext `yaml:"-"`
EnsuredSafe bool `yaml:"ensured_safe"`

View file

@ -9,7 +9,7 @@ var defaultI18nLangNames = []string{
"zh-CN", "简体中文",
"zh-HK", "繁體中文(香港)",
"zh-TW", "繁體中文(台灣)",
"da", "Danish",
"da", "Dansk",
"de-DE", "Deutsch",
"nds", "Plattdüütsch",
"fr-FR", "Français",

View file

@ -57,7 +57,8 @@ type PullRequest struct {
// swagger:strfmt date-time
Closed *time.Time `json:"closed_at"`
PinOrder int `json:"pin_order"`
PinOrder int `json:"pin_order"`
Flow int64 `json:"flow"`
}
// PRBranchInfo information about a branch

View file

@ -1441,7 +1441,7 @@ editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be ed
editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository.
editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository.
editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit changes again</strong> to overwrite them.
editor.file_changed_while_editing = The file contents have changed since you opened the file. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit changes again</strong> to overwrite them.
editor.file_already_exists = A file named "%s" already exists in this repository.
editor.commit_id_not_matching = The file was changed while you were editing it. Commit to a new branch and then merge.
editor.push_out_of_date = The push appears to be out of date.

View file

@ -16,5 +16,8 @@
"incorrect_root_url": "This Forgejo instance is configured to be served on \"%s\". You are currently viewing Forgejo through a different URL, which may cause parts of the application to break. The canonical URL is controlled by Forgejo admins via the ROOT_URL setting in the app.ini.",
"themes.names.forgejo-auto": "Forgejo Auto (follows system theme)",
"themes.names.forgejo-light": "Forgejo Light",
"themes.names.forgejo-dark": "Forgejo Dark"
"themes.names.forgejo-dark": "Forgejo Dark",
"settings.adopt": "Adopt",
"install.invalid_lfs_path": "Unable to create the LFS root at the specified path: %[1]s",
"install.lfs_jwt_secret_failed": "Unable to generate a LFS JWT secret: %[1]s"
}

View file

@ -585,7 +585,7 @@ func DeleteFilePost(ctx *context.Context) {
ctx.Error(http.StatusInternalServerError, err.Error())
}
} else if models.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form)
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form)
} else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 {

View file

@ -1044,7 +1044,15 @@ func renderHomeCode(ctx *context.Context) {
return
}
if entry.IsDir() {
if entry.IsSubModule() {
subModuleURL, err := ctx.Repo.Commit.GetSubModule(entry.Name())
if err != nil {
HandleGitError(ctx, "Repo.Commit.GetSubModule", err)
return
}
subModuleFile := git.NewSubModuleFile(ctx.Repo.Commit, subModuleURL, entry.ID.String())
ctx.Redirect(subModuleFile.RefURL(setting.AppURL, ctx.Repo.Repository.FullName(), setting.SSH.Domain))
} else if entry.IsDir() {
renderDirectory(ctx)
} else {
renderFile(ctx, entry)

View file

@ -95,6 +95,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
RequestedReviewersTeams: []*api.Team{},
AllowMaintainerEdit: pr.AllowMaintainerEdit,
Flow: int64(pr.Flow),
Base: &api.PRBranchInfo{
Name: pr.BaseBranch,

View file

@ -504,6 +504,28 @@ func (g *GiteaDownloader) GetComments(commentable base.Commentable) ([]*base.Com
return allComments, true, nil
}
type ForgejoPullRequest struct {
gitea_sdk.PullRequest
Flow int64 `json:"flow"`
}
// Extracted from https://gitea.com/gitea/go-sdk/src/commit/164e3358bc02213954fb4380b821bed80a14824d/gitea/pull.go#L347-L364
func (g *GiteaDownloader) fixPullHeadSha(pr *ForgejoPullRequest) error {
if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil && pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" {
owner := pr.Base.Repository.Owner.UserName
repo := pr.Base.Repository.Name
refs, _, err := g.client.GetRepoRefs(owner, repo, pr.Head.Ref)
if err != nil {
return err
}
if len(refs) == 0 {
return fmt.Errorf("unable to resolve PR ref %q", pr.Head.Ref)
}
pr.Head.Sha = refs[0].Object.SHA
}
return nil
}
// GetPullRequests returns pull requests according page and perPage
func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) {
if perPage > g.maxPerPage {
@ -511,16 +533,30 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
}
allPRs := make([]*base.PullRequest, 0, perPage)
prs, _, err := g.client.ListRepoPullRequests(g.repoOwner, g.repoName, gitea_sdk.ListPullRequestsOptions{
prs := make([]*ForgejoPullRequest, 0, perPage)
opt := gitea_sdk.ListPullRequestsOptions{
ListOptions: gitea_sdk.ListOptions{
Page: page,
PageSize: perPage,
},
State: gitea_sdk.StateAll,
})
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", url.PathEscape(g.repoOwner), url.PathEscape(g.repoName)))
link.RawQuery = opt.QueryEncode()
_, err := getParsedResponse(g.client, "GET", link.String(), http.Header{"content-type": []string{"application/json"}}, nil, &prs)
if err != nil {
return nil, false, fmt.Errorf("error while listing pull requests (page: %d, pagesize: %d). Error: %w", page, perPage, err)
}
if g.client.CheckServerVersionConstraint(">= 1.14.0") != nil {
for i := range prs {
if err := g.fixPullHeadSha(prs[i]); err != nil {
return nil, false, fmt.Errorf("error while listing pull requests (page: %d, pagesize: %d). Error: %w", page, perPage, err)
}
}
}
for _, pr := range prs {
var milestone string
if pr.Milestone != nil {
@ -598,6 +634,7 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
MergeCommitSHA: mergeCommitSHA,
IsLocked: pr.IsLocked,
PatchURL: pr.PatchURL,
Flow: pr.Flow,
Head: base.PullRequestBranch{
Ref: headRef,
SHA: headSHA,

View file

@ -307,3 +307,46 @@ func TestGiteaDownloadRepo(t *testing.T) {
},
}, reviews)
}
func TestForgejoDownloadRepo(t *testing.T) {
token := os.Getenv("CODE_FORGEJO_TOKEN")
fixturePath := "./testdata/code-forgejo-org/full_download"
server := unittest.NewMockWebServer(t, "https://code.forgejo.org", fixturePath, token != "")
defer server.Close()
downloader, err := NewGiteaDownloader(t.Context(), server.URL, "Gusted/agit-test", "", "", token)
require.NoError(t, err)
require.NotNil(t, downloader)
prs, _, err := downloader.GetPullRequests(1, 50)
require.NoError(t, err)
assert.Len(t, prs, 1)
assertPullRequestEqual(t, &base.PullRequest{
Number: 1,
PosterID: 63,
PosterName: "Gusted",
PosterEmail: "postmaster@gusted.xyz",
Title: "Add extra information",
State: "open",
Created: time.Date(2025, time.April, 1, 20, 28, 45, 0, time.UTC),
Updated: time.Date(2025, time.April, 1, 20, 28, 45, 0, time.UTC),
Base: base.PullRequestBranch{
CloneURL: "",
Ref: "main",
SHA: "79ebb873a6497c8847141ba9706b3f757196a1e6",
RepoName: "agit-test",
OwnerName: "Gusted",
},
Head: base.PullRequestBranch{
CloneURL: server.URL + "/Gusted/agit-test.git",
Ref: "refs/pull/1/head",
SHA: "667e9317ec37b977e6d3d7d43e3440636970563c",
RepoName: "agit-test",
OwnerName: "Gusted",
},
PatchURL: server.URL + "/Gusted/agit-test/pulls/1.patch",
Flow: 1,
}, prs[0])
}

View file

@ -0,0 +1,16 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package migrations
import (
"io"
"net/http"
_ "unsafe" // Needed for go:linkname support
gitea_sdk "code.gitea.io/sdk/gitea"
)
//go:linkname getParsedResponse code.gitea.io/sdk/gitea.(*Client).getParsedResponse
func getParsedResponse(client *gitea_sdk.Client, method, path string, header http.Header, body io.Reader, obj any) (*gitea_sdk.Response, error)

View file

@ -802,6 +802,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model
MergeBase: pr.Base.SHA,
Index: pr.Number,
HasMerged: pr.Merged,
Flow: issues_model.PullRequestFlow(pr.Flow),
Issue: &issue,
}

View file

@ -136,6 +136,7 @@ func assertPullRequestEqual(t *testing.T, expected, actual *base.PullRequest) {
assert.ElementsMatch(t, expected.Assignees, actual.Assignees)
assert.Equal(t, expected.IsLocked, actual.IsLocked)
assertReactionsEqual(t, expected.Reactions, actual.Reactions)
assert.Equal(t, expected.Flow, actual.Flow)
}
func assertPullRequestsEqual(t *testing.T, expected, actual []*base.PullRequest) {

View file

@ -0,0 +1,7 @@
Content-Length: 117
Cache-Control: max-age=0, private, must-revalidate, no-transform
Content-Type: application/json;charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
{"max_response_items":50,"default_paging_num":30,"default_git_trees_per_page":1000,"default_max_blob_size":10485760}

View file

@ -0,0 +1,7 @@
Cache-Control: max-age=0, private, must-revalidate, no-transform
Content-Type: application/json;charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Length: 53
{"version":"11.0.0-dev-617-1d1e0ced3e+gitea-1.22.0"}

View file

@ -31,7 +31,7 @@
<td><a href="{{AppSubUrl}}/admin/auths/{{.ID}}">{{svg "octicon-pencil"}}</a></td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="7">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
<tr><td class="tw-text-center" colspan="7">{{ctx.Locale.Tr "repo.pulls.no_results"}}</td></tr>
{{end}}
</tbody>
</table>

View file

@ -67,7 +67,7 @@
</td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="6">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
<tr><td class="tw-text-center" colspan="6">{{ctx.Locale.Tr "repo.pulls.no_results"}}</td></tr>
{{end}}
</tbody>
</table>

View file

@ -25,7 +25,7 @@
<td class="view-detail"><a href="#">{{svg "octicon-note" 16}}</a></td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="6">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
<tr><td class="tw-text-center" colspan="6">{{ctx.Locale.Tr "repo.pulls.no_results"}}</td></tr>
{{end}}
</tbody>
{{if .Notices}}

View file

@ -67,7 +67,7 @@
<td><a href="{{.OrganisationLink}}/settings" data-tooltip-content="{{ctx.Locale.Tr "edit"}}">{{svg "octicon-pencil"}}</a></td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="7">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
<tr><td class="tw-text-center" colspan="7">{{ctx.Locale.Tr "repo.pulls.no_results"}}</td></tr>
{{end}}
</tbody>
</table>

View file

@ -75,7 +75,7 @@
<td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.Version.ID}}" data-name="{{.Package.Name}}" data-data-version="{{.Version.Version}}">{{svg "octicon-trash"}}</a></td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="10">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
<tr><td class="tw-text-center" colspan="10">{{ctx.Locale.Tr "repo.pulls.no_results"}}</td></tr>
{{end}}
</tbody>
</table>

View file

@ -87,7 +87,7 @@
<td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.ID}}" data-name="{{.Name}}">{{svg "octicon-trash"}}</a></td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="12">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
<tr><td class="tw-text-center" colspan="12">{{ctx.Locale.Tr "repo.pulls.no_results"}}</td></tr>
{{end}}
</tbody>
</table>

View file

@ -110,7 +110,7 @@
</td>
</tr>
{{else}}
<tr class="no-results-row"><td class="tw-text-center" colspan="9">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
<tr class="no-results-row"><td class="tw-text-center" colspan="9">{{ctx.Locale.Tr "repo.pulls.no_results"}}</td></tr>
{{end}}
</tbody>
</table>

View file

@ -31,7 +31,7 @@
<strong>{{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}}</strong>
</audio>
{{else if .IsPDFFile}}
<div class="pdf-content is-loading" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "diff.view_file"}}"></div>
<div class="pdf-content is-loading" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "repo.diff.view_file"}}"></div>
{{else}}
<a href="{{$.RawFileLink}}" rel="nofollow">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
{{end}}

View file

@ -26133,6 +26133,11 @@
"format": "date-time",
"x-go-name": "Deadline"
},
"flow": {
"type": "integer",
"format": "int64",
"x-go-name": "Flow"
},
"head": {
"$ref": "#/definitions/PRBranchInfo"
},

View file

@ -30,7 +30,7 @@
<div class="inline field">
<label></label>
<button class="ui primary button">{{ctx.Locale.Tr "sign_in"}}</button>
<a href="{{AppSubUrl}}/user/forget_password">{{ctx.Locale.Tr "auth.forget_password"}}</a>
<a href="{{AppSubUrl}}/user/forget_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
</div>
{{if .ShowRegistrationButton}}
<div class="inline field">

View file

@ -1423,6 +1423,12 @@ func TestRepoSubmoduleView(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body)
htmlDoc.AssertElement(t, fmt.Sprintf(`tr[data-entryname="repo1"] a[href="%s"]`, u.JoinPath("/user2/repo1").String()), true)
// Check that a link to the submodule returns a redirect and that the redirect link is correct.
req = NewRequest(t, "GET", "/"+repo.FullName()+"/src/branch/"+repo.DefaultBranch+"/repo1")
resp = MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, u.JoinPath("/user2/repo1").String(), resp.Header().Get("Location"))
})
}