Add anonymous access support for private/unlisted repositories (#34051)

Follow #33127

Fix #8649, fix #639

This is a complete solution. A repo unit could be set to:

* Anonymous read (non-signed-in user)
* Everyone read (signed-in user)
* Everyone write (wiki-only)
pull/34054/head
wxiaoguang 1 week ago committed by GitHub
parent 49899070cd
commit cddd19efc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -50,7 +51,7 @@ func (p *Permission) HasAnyUnitAccess() bool {
return p.AccessMode >= perm_model.AccessModeRead return p.AccessMode >= perm_model.AccessModeRead
} }
func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool { func (p *Permission) HasAnyUnitPublicAccess() bool {
for _, v := range p.anonymousAccessMode { for _, v := range p.anonymousAccessMode {
if v >= perm_model.AccessModeRead { if v >= perm_model.AccessModeRead {
return true return true
@ -61,7 +62,11 @@ func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool {
return true return true
} }
} }
return p.HasAnyUnitAccess() return false
}
func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool {
return p.HasAnyUnitPublicAccess() || p.HasAnyUnitAccess()
} }
// HasUnits returns true if the permission contains attached units // HasUnits returns true if the permission contains attached units
@ -188,6 +193,9 @@ func (p *Permission) LogString() string {
} }
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) { func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {
if setting.Repository.ForcePrivate {
return
}
if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] { if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] {
if *modeMap == nil { if *modeMap == nil {
*modeMap = make(map[unit.Type]perm_model.AccessMode) *modeMap = make(map[unit.Type]perm_model.AccessMode)

@ -342,3 +342,9 @@ func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error {
_, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit)
return err return err
} }
func UpdateRepoUnitPublicAccess(ctx context.Context, unit *RepoUnit) error {
_, err := db.GetEngine(ctx).Where("repo_id=? AND `type`=?", unit.RepoID, unit.Type).
Cols("anonymous_access_mode", "everyone_access_mode").Update(unit)
return err
}

@ -926,6 +926,9 @@ permission_not_set = Not set
permission_no_access = No Access permission_no_access = No Access
permission_read = Read permission_read = Read
permission_write = Read and Write permission_write = Read and Write
permission_anonymous_read = Anonymous Read
permission_everyone_read = Everyone Read
permission_everyone_write = Everyone Write
access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information. access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information.
at_least_one_permission = You must select at least one permission to create a token at_least_one_permission = You must select at least one permission to create a token
permissions_list = Permissions: permissions_list = Permissions:
@ -1138,6 +1141,7 @@ transfer.no_permission_to_reject = You do not have permission to reject this tra
desc.private = Private desc.private = Private
desc.public = Public desc.public = Public
desc.public_access = Public Access
desc.template = Template desc.template = Template
desc.internal = Internal desc.internal = Internal
desc.archived = Archived desc.archived = Archived
@ -2133,6 +2137,7 @@ contributors.contribution_type.deletions = Deletions
settings = Settings settings = Settings
settings.desc = Settings is where you can manage the settings for the repository settings.desc = Settings is where you can manage the settings for the repository
settings.options = Repository settings.options = Repository
settings.public_access = Public Access
settings.collaboration = Collaborators settings.collaboration = Collaborators
settings.collaboration.admin = Administrator settings.collaboration.admin = Administrator
settings.collaboration.write = Write settings.collaboration.write = Write
@ -2179,7 +2184,6 @@ settings.advanced_settings = Advanced Settings
settings.wiki_desc = Enable Repository Wiki settings.wiki_desc = Enable Repository Wiki
settings.use_internal_wiki = Use Built-In Wiki settings.use_internal_wiki = Use Built-In Wiki
settings.default_wiki_branch_name = Default Wiki Branch Name settings.default_wiki_branch_name = Default Wiki Branch Name
settings.default_permission_everyone_access = Default access permission for all signed-in users:
settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch. settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch.
settings.use_external_wiki = Use External Wiki settings.use_external_wiki = Use External Wiki
settings.external_wiki_url = External Wiki URL settings.external_wiki_url = External Wiki URL

@ -0,0 +1,155 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"net/http"
"slices"
"strconv"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const tplRepoSettingsPublicAccess templates.TplName = "repo/settings/public_access"
func parsePublicAccessMode(permission string, allowed []string) (ret struct {
AnonymousAccessMode, EveryoneAccessMode perm.AccessMode
},
) {
ret.AnonymousAccessMode = perm.AccessModeNone
ret.EveryoneAccessMode = perm.AccessModeNone
// if site admin forces repositories to be private, then do not allow any other access mode,
// otherwise the "force private" setting would be bypassed
if setting.Repository.ForcePrivate {
return ret
}
if !slices.Contains(allowed, permission) {
return ret
}
switch permission {
case paAnonymousRead:
ret.AnonymousAccessMode = perm.AccessModeRead
case paEveryoneRead:
ret.EveryoneAccessMode = perm.AccessModeRead
case paEveryoneWrite:
ret.EveryoneAccessMode = perm.AccessModeWrite
}
return ret
}
const (
paNotSet = "not-set"
paAnonymousRead = "anonymous-read"
paEveryoneRead = "everyone-read"
paEveryoneWrite = "everyone-write"
)
type repoUnitPublicAccess struct {
UnitType unit.Type
FormKey string
DisplayName string
PublicAccessTypes []string
UnitPublicAccess string
}
func repoUnitPublicAccesses(ctx *context.Context) []*repoUnitPublicAccess {
accesses := []*repoUnitPublicAccess{
{
UnitType: unit.TypeCode,
DisplayName: ctx.Locale.TrString("repo.code"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
},
{
UnitType: unit.TypeIssues,
DisplayName: ctx.Locale.TrString("issues"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
},
{
UnitType: unit.TypePullRequests,
DisplayName: ctx.Locale.TrString("pull_requests"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
},
{
UnitType: unit.TypeReleases,
DisplayName: ctx.Locale.TrString("repo.releases"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
},
{
UnitType: unit.TypeWiki,
DisplayName: ctx.Locale.TrString("repo.wiki"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead, paEveryoneWrite},
},
{
UnitType: unit.TypeProjects,
DisplayName: ctx.Locale.TrString("repo.projects"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
},
{
UnitType: unit.TypePackages,
DisplayName: ctx.Locale.TrString("repo.packages"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
},
{
UnitType: unit.TypeActions,
DisplayName: ctx.Locale.TrString("repo.actions"),
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
},
}
for _, ua := range accesses {
ua.FormKey = "repo-unit-access-" + strconv.Itoa(int(ua.UnitType))
for _, u := range ctx.Repo.Repository.Units {
if u.Type == ua.UnitType {
ua.UnitPublicAccess = paNotSet
switch {
case u.EveryoneAccessMode == perm.AccessModeWrite:
ua.UnitPublicAccess = paEveryoneWrite
case u.EveryoneAccessMode == perm.AccessModeRead:
ua.UnitPublicAccess = paEveryoneRead
case u.AnonymousAccessMode == perm.AccessModeRead:
ua.UnitPublicAccess = paAnonymousRead
}
break
}
}
}
return slices.DeleteFunc(accesses, func(ua *repoUnitPublicAccess) bool {
return ua.UnitPublicAccess == ""
})
}
func PublicAccess(ctx *context.Context) {
ctx.Data["PageIsSettingsPublicAccess"] = true
ctx.Data["RepoUnitPublicAccesses"] = repoUnitPublicAccesses(ctx)
ctx.Data["GlobalForcePrivate"] = setting.Repository.ForcePrivate
if setting.Repository.ForcePrivate {
ctx.Flash.Error(ctx.Tr("form.repository_force_private"), true)
}
ctx.HTML(http.StatusOK, tplRepoSettingsPublicAccess)
}
func PublicAccessPost(ctx *context.Context) {
accesses := repoUnitPublicAccesses(ctx)
for _, ua := range accesses {
formVal := ctx.FormString(ua.FormKey)
parsed := parsePublicAccessMode(formVal, ua.PublicAccessTypes)
err := repo.UpdateRepoUnitPublicAccess(ctx, &repo.RepoUnit{
RepoID: ctx.Repo.Repository.ID,
Type: ua.UnitType,
AnonymousAccessMode: parsed.AnonymousAccessMode,
EveryoneAccessMode: parsed.EveryoneAccessMode,
})
if err != nil {
ctx.ServerError("UpdateRepoUnitPublicAccess", err)
return
}
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/public_access")
}

@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit" unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -37,6 +36,8 @@ import (
mirror_service "code.gitea.io/gitea/services/mirror" mirror_service "code.gitea.io/gitea/services/mirror"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki" wiki_service "code.gitea.io/gitea/services/wiki"
"xorm.io/xorm/convert"
) )
const ( const (
@ -48,15 +49,6 @@ const (
tplDeployKeys templates.TplName = "repo/settings/deploy_keys" tplDeployKeys templates.TplName = "repo/settings/deploy_keys"
) )
func parseEveryoneAccessMode(permission string, allowed ...perm.AccessMode) perm.AccessMode {
// if site admin forces repositories to be private, then do not allow any other access mode,
// otherwise the "force private" setting would be bypassed
if setting.Repository.ForcePrivate {
return perm.AccessModeNone
}
return perm.ParseAccessMode(permission, allowed...)
}
// SettingsCtxData is a middleware that sets all the general context data for the // SettingsCtxData is a middleware that sets all the general context data for the
// settings template. // settings template.
func SettingsCtxData(ctx *context.Context) { func SettingsCtxData(ctx *context.Context) {
@ -504,6 +496,17 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) {
ctx.Redirect(repo.Link() + "/settings") ctx.Redirect(repo.Link() + "/settings")
} }
func newRepoUnit(repo *repo_model.Repository, unitType unit_model.Type, config convert.Conversion) repo_model.RepoUnit {
repoUnit := repo_model.RepoUnit{RepoID: repo.ID, Type: unitType, Config: config}
for _, u := range repo.Units {
if u.Type == unitType {
repoUnit.EveryoneAccessMode = u.EveryoneAccessMode
repoUnit.AnonymousAccessMode = u.AnonymousAccessMode
}
}
return repoUnit
}
func handleSettingsPostAdvanced(ctx *context.Context) { func handleSettingsPostAdvanced(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm) form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
@ -521,11 +524,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
} }
if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeCode, nil))
RepoID: repo.ID,
Type: unit_model.TypeCode,
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead),
})
} else if !unit_model.TypeCode.UnitGlobalDisabled() { } else if !unit_model.TypeCode.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode)
} }
@ -537,21 +536,12 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
return return
} }
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeExternalWiki, &repo_model.ExternalWikiConfig{
RepoID: repo.ID, ExternalWikiURL: form.ExternalWikiURL,
Type: unit_model.TypeExternalWiki, }))
Config: &repo_model.ExternalWikiConfig{
ExternalWikiURL: form.ExternalWikiURL,
},
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
} else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeWiki, new(repo_model.UnitConfig)))
RepoID: repo.ID,
Type: unit_model.TypeWiki,
Config: new(repo_model.UnitConfig),
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite),
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
} else { } else {
if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { if !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
@ -580,28 +570,19 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
ctx.Redirect(repo.Link() + "/settings") ctx.Redirect(repo.Link() + "/settings")
return return
} }
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeExternalTracker, &repo_model.ExternalTrackerConfig{
RepoID: repo.ID, ExternalTrackerURL: form.ExternalTrackerURL,
Type: unit_model.TypeExternalTracker, ExternalTrackerFormat: form.TrackerURLFormat,
Config: &repo_model.ExternalTrackerConfig{ ExternalTrackerStyle: form.TrackerIssueStyle,
ExternalTrackerURL: form.ExternalTrackerURL, ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
ExternalTrackerFormat: form.TrackerURLFormat, }))
ExternalTrackerStyle: form.TrackerIssueStyle,
ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
},
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
} else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeIssues, &repo_model.IssuesConfig{
RepoID: repo.ID, EnableTimetracker: form.EnableTimetracker,
Type: unit_model.TypeIssues, AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
Config: &repo_model.IssuesConfig{ EnableDependencies: form.EnableIssueDependencies,
EnableTimetracker: form.EnableTimetracker, }))
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
EnableDependencies: form.EnableIssueDependencies,
},
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead),
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
} else { } else {
if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { if !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
@ -613,63 +594,46 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
} }
if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeProjects, &repo_model.ProjectsConfig{
RepoID: repo.ID, ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode),
Type: unit_model.TypeProjects, }))
Config: &repo_model.ProjectsConfig{
ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode),
},
})
} else if !unit_model.TypeProjects.UnitGlobalDisabled() { } else if !unit_model.TypeProjects.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
} }
if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeReleases, nil))
RepoID: repo.ID,
Type: unit_model.TypeReleases,
})
} else if !unit_model.TypeReleases.UnitGlobalDisabled() { } else if !unit_model.TypeReleases.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases)
} }
if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypePackages, nil))
RepoID: repo.ID,
Type: unit_model.TypePackages,
})
} else if !unit_model.TypePackages.UnitGlobalDisabled() { } else if !unit_model.TypePackages.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
} }
if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypeActions, nil))
RepoID: repo.ID,
Type: unit_model.TypeActions,
})
} else if !unit_model.TypeActions.UnitGlobalDisabled() { } else if !unit_model.TypeActions.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
} }
if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, &repo_model.PullRequestsConfig{
RepoID: repo.ID, IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
Type: unit_model.TypePullRequests, AllowMerge: form.PullsAllowMerge,
Config: &repo_model.PullRequestsConfig{ AllowRebase: form.PullsAllowRebase,
IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, AllowRebaseMerge: form.PullsAllowRebaseMerge,
AllowMerge: form.PullsAllowMerge, AllowSquash: form.PullsAllowSquash,
AllowRebase: form.PullsAllowRebase, AllowFastForwardOnly: form.PullsAllowFastForwardOnly,
AllowRebaseMerge: form.PullsAllowRebaseMerge, AllowManualMerge: form.PullsAllowManualMerge,
AllowSquash: form.PullsAllowSquash, AutodetectManualMerge: form.EnableAutodetectManualMerge,
AllowFastForwardOnly: form.PullsAllowFastForwardOnly, AllowRebaseUpdate: form.PullsAllowRebaseUpdate,
AllowManualMerge: form.PullsAllowManualMerge, DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
AutodetectManualMerge: form.EnableAutodetectManualMerge, DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
AllowRebaseUpdate: form.PullsAllowRebaseUpdate, DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, }))
DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
},
})
} else if !unit_model.TypePullRequests.UnitGlobalDisabled() { } else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
} }

@ -1078,6 +1078,8 @@ func registerRoutes(m *web.Router) {
m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar) m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar)
m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar) m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar)
m.Combo("/public_access").Get(repo_setting.PublicAccess).Post(repo_setting.PublicAccessPost)
m.Group("/collaboration", func() { m.Group("/collaboration", func() {
m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost) m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost)
m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode) m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode)

@ -110,17 +110,14 @@ type RepoSettingForm struct {
EnablePrune bool EnablePrune bool
// Advanced settings // Advanced settings
EnableCode bool EnableCode bool
DefaultCodeEveryoneAccess string
EnableWiki bool EnableWiki bool
EnableExternalWiki bool EnableExternalWiki bool
DefaultWikiBranch string DefaultWikiBranch string
DefaultWikiEveryoneAccess string ExternalWikiURL string
ExternalWikiURL string
EnableIssues bool EnableIssues bool
DefaultIssuesEveryoneAccess string
EnableExternalTracker bool EnableExternalTracker bool
ExternalTrackerURL string ExternalTrackerURL string
TrackerURLFormat string TrackerURLFormat string

@ -25,6 +25,9 @@
<div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.internal"}}">{{svg "octicon-shield-lock" 18}}</div> <div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.internal"}}">{{svg "octicon-shield-lock" 18}}</div>
{{end}} {{end}}
{{end}} {{end}}
{{if $.Permission.HasAnyUnitPublicAccess}}
<span class="ui basic orange label">{{ctx.Locale.Tr "repo.desc.public_access"}}</span>
{{end}}
{{if .IsTemplate}} {{if .IsTemplate}}
<span class="ui basic label not-mobile">{{ctx.Locale.Tr "repo.desc.template"}}</span> <span class="ui basic label not-mobile">{{ctx.Locale.Tr "repo.desc.template"}}</span>
<div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.template"}}">{{svg "octicon-repo-template" 18}}</div> <div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.template"}}">{{svg "octicon-repo-template" 18}}</div>
@ -208,7 +211,7 @@
</a> </a>
{{end}} {{end}}
{{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases) (not .IsEmptyRepo)}} {{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases ctx.Consts.RepoUnitTypeCode) (not .IsEmptyRepo)}}
<a class="{{if .PageIsActivity}}active {{end}}item" href="{{.RepoLink}}/activity"> <a class="{{if .PageIsActivity}}active {{end}}item" href="{{.RepoLink}}/activity">
{{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}} {{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}}
</a> </a>

@ -1,6 +1,7 @@
{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}} {{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
<div class="ui fluid vertical menu"> <div class="ui fluid vertical menu">
{{/* the default activity page "pulse" could work with any permission: code, issue, pr, release*/}}
<a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity"> <a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity">
{{ctx.Locale.Tr "repo.activity.navbar.pulse"}} {{ctx.Locale.Tr "repo.activity.navbar.pulse"}}
</a> </a>

@ -4,6 +4,11 @@
<a class="{{if .PageIsSettingsOptions}}active {{end}}item" href="{{.RepoLink}}/settings"> <a class="{{if .PageIsSettingsOptions}}active {{end}}item" href="{{.RepoLink}}/settings">
{{ctx.Locale.Tr "repo.settings.options"}} {{ctx.Locale.Tr "repo.settings.options"}}
</a> </a>
{{if or .Repository.IsPrivate .Permission.HasAnyUnitPublicAccess}}
<a class="{{if .PageIsSettingsPublicAccess}}active {{end}}item" href="{{.RepoLink}}/settings/public_access">
{{ctx.Locale.Tr "repo.settings.public_access"}}
</a>
{{end}}
<a class="{{if .PageIsSettingsCollaboration}}active {{end}}item" href="{{.RepoLink}}/settings/collaboration"> <a class="{{if .PageIsSettingsCollaboration}}active {{end}}item" href="{{.RepoLink}}/settings/collaboration">
{{ctx.Locale.Tr "repo.settings.collaboration"}} {{ctx.Locale.Tr "repo.settings.collaboration"}}
</a> </a>

@ -310,15 +310,6 @@
<input class="enable-system" name="enable_code" type="checkbox"{{if $isCodeEnabled}} checked{{end}}> <input class="enable-system" name="enable_code" type="checkbox"{{if $isCodeEnabled}} checked{{end}}>
<label>{{ctx.Locale.Tr "repo.code.desc"}}</label> <label>{{ctx.Locale.Tr "repo.code.desc"}}</label>
</div> </div>
<div class="inline field tw-pl-4">
{{$unitCode := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeCode}}
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
<select name="default_code_everyone_access" class="ui selection dropdown">
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
<option value="none" {{Iif (eq $unitCode.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
<option value="read" {{Iif (eq $unitCode.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
</select>
</div>
</div> </div>
{{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}} {{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}}
@ -346,16 +337,6 @@
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_branch_name"}}</label> <label>{{ctx.Locale.Tr "repo.settings.default_wiki_branch_name"}}</label>
<input name="default_wiki_branch" value="{{.Repository.DefaultWikiBranch}}"> <input name="default_wiki_branch" value="{{.Repository.DefaultWikiBranch}}">
</div> </div>
<div class="inline field">
{{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}}
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
<select name="default_wiki_everyone_access" class="ui selection dropdown">
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
<option value="none" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
<option value="read" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
<option value="write" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 2) "selected"}}>{{ctx.Locale.Tr "settings.permission_write"}}</option>
</select>
</div>
</div> </div>
<div class="field"> <div class="field">
<div class="ui radio checkbox{{if $isExternalWikiGlobalDisabled}} disabled{{end}}"{{if $isExternalWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}> <div class="ui radio checkbox{{if $isExternalWikiGlobalDisabled}} disabled{{end}}"{{if $isExternalWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
@ -391,15 +372,6 @@
</div> </div>
</div> </div>
<div class="field tw-pl-4 {{if (.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box"> <div class="field tw-pl-4 {{if (.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box">
<div class="inline field">
{{$unitIssue := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeIssues}}
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
<select name="default_issues_everyone_access" class="ui selection dropdown">
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
<option value="none" {{Iif (eq $unitIssue.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
<option value="read" {{Iif (eq $unitIssue.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
</select>
</div>
{{if .Repository.CanEnableTimetracker}} {{if .Repository.CanEnableTimetracker}}
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">

@ -0,0 +1,36 @@
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings")}}
<div class="repo-setting-content">
{{$paNotSet := "not-set"}}
{{$paAnonymousRead := "anonymous-read"}}
{{$paEveryoneRead := "everyone-read"}}
{{$paEveryoneWrite := "everyone-write"}}
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<table class="ui table unstackable tw-my-2">
<tr>
<th></th>
<th>{{ctx.Locale.Tr "settings.permission_not_set"}}</th>
<th>{{ctx.Locale.Tr "settings.permission_anonymous_read"}}</th>
<th>{{ctx.Locale.Tr "settings.permission_everyone_read"}}</th>
<th>{{ctx.Locale.Tr "settings.permission_everyone_write"}}</th>
</tr>
{{range $ua := .RepoUnitPublicAccesses}}
<tr>
<td>{{$ua.DisplayName}}</td>
<td class="tw-text-center"><label><input type="radio" name="{{$ua.FormKey}}" value="{{$paNotSet}}" {{Iif (eq $paNotSet $ua.UnitPublicAccess) "checked"}}></label></td>
<td class="tw-text-center"><label><input type="radio" name="{{$ua.FormKey}}" value="{{$paAnonymousRead}}" {{Iif (eq $paAnonymousRead $ua.UnitPublicAccess) "checked"}}></label></td>
<td class="tw-text-center"><label><input type="radio" name="{{$ua.FormKey}}" value="{{$paEveryoneRead}}" {{Iif (eq $paEveryoneRead $ua.UnitPublicAccess) "checked"}}></label></td>
<td class="tw-text-center">
{{if SliceUtils.Contains $ua.PublicAccessTypes $paEveryoneWrite}}
<label><input type="radio" name="{{$ua.FormKey}}" value="{{$paEveryoneWrite}}" {{Iif (eq $paEveryoneWrite $ua.UnitPublicAccess) "checked"}}></label>
{{else}}
-
{{end}}
</td>
</tr>
{{end}}
</table>
<button class="ui primary button {{if .GlobalForcePrivate}}disabled{{end}}">{{ctx.Locale.Tr "repo.settings.update_settings"}}</button>
</form>
</div>
{{template "repo/settings/layout_footer" .}}

@ -7,10 +7,12 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"path" "path"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -19,8 +21,26 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestViewRepo(t *testing.T) { func TestRepoView(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
t.Run("ViewRepoPublic", testViewRepoPublic)
t.Run("ViewRepoWithCache", testViewRepoWithCache)
t.Run("ViewRepoPrivate", testViewRepoPrivate)
t.Run("ViewRepo1CloneLinkAnonymous", testViewRepo1CloneLinkAnonymous)
t.Run("ViewRepo1CloneLinkAuthorized", testViewRepo1CloneLinkAuthorized)
t.Run("ViewRepoWithSymlinks", testViewRepoWithSymlinks)
t.Run("ViewFileInRepo", testViewFileInRepo)
t.Run("BlameFileInRepo", testBlameFileInRepo)
t.Run("ViewRepoDirectory", testViewRepoDirectory)
t.Run("ViewRepoDirectoryReadme", testViewRepoDirectoryReadme)
t.Run("MarkDownReadmeImage", testMarkDownReadmeImage)
t.Run("MarkDownReadmeImageSubfolder", testMarkDownReadmeImageSubfolder)
t.Run("GeneratedSourceLink", testGeneratedSourceLink)
t.Run("ViewCommit", testViewCommit)
}
func testViewRepoPublic(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -41,87 +61,118 @@ func TestViewRepo(t *testing.T) {
session.MakeRequest(t, req, http.StatusNotFound) session.MakeRequest(t, req, http.StatusNotFound)
} }
func testViewRepo(t *testing.T) { func testViewRepoWithCache(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
testView := func(t *testing.T) {
req := NewRequest(t, "GET", "/org3/repo3") req := NewRequest(t, "GET", "/org3/repo3")
session := loginUser(t, "user2") session := loginUser(t, "user2")
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
files := htmlDoc.doc.Find("#repo-files-table .repo-file-item")
type file struct {
fileName string
commitID string
commitMsg string
commitTime string
}
var items []file htmlDoc := NewHTMLParser(t, resp.Body)
files := htmlDoc.doc.Find("#repo-files-table .repo-file-item")
files.Each(func(i int, s *goquery.Selection) {
tds := s.Find(".repo-file-cell") type file struct {
var f file fileName string
tds.Each(func(i int, s *goquery.Selection) { commitID string
if i == 0 { commitMsg string
f.fileName = strings.TrimSpace(s.Text()) commitTime string
} else if i == 1 { }
a := s.Find("a")
f.commitMsg = strings.TrimSpace(a.Text()) var items []file
l, _ := a.Attr("href")
f.commitID = path.Base(l) files.Each(func(i int, s *goquery.Selection) {
} tds := s.Find(".repo-file-cell")
var f file
tds.Each(func(i int, s *goquery.Selection) {
if i == 0 {
f.fileName = strings.TrimSpace(s.Text())
} else if i == 1 {
a := s.Find("a")
f.commitMsg = strings.TrimSpace(a.Text())
l, _ := a.Attr("href")
f.commitID = path.Base(l)
}
})
// convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC"
htmlTimeString, _ := s.Find("relative-time").Attr("datetime")
htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString)
f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123)
items = append(items, f)
}) })
// convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC" commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123)
htmlTimeString, _ := s.Find("relative-time").Attr("datetime") assert.EqualValues(t, []file{
htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString) {
f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123) fileName: "doc",
items = append(items, f) commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
}) commitMsg: "init project",
commitTime: commitT,
commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123) },
assert.EqualValues(t, []file{ {
{ fileName: "README.md",
fileName: "doc", commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", commitMsg: "init project",
commitMsg: "init project", commitTime: commitT,
commitTime: commitT, },
}, }, items)
{ }
fileName: "README.md",
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
commitMsg: "init project",
commitTime: commitT,
},
}, items)
}
func TestViewRepo2(t *testing.T) { // FIXME: these test don't seem quite right, no enough assert
// no last commit cache // no last commit cache
testViewRepo(t) testView(t)
// enable last commit cache for all repositories // enable last commit cache for all repositories
oldCommitsCount := setting.CacheService.LastCommit.CommitsCount oldCommitsCount := setting.CacheService.LastCommit.CommitsCount
setting.CacheService.LastCommit.CommitsCount = 0 setting.CacheService.LastCommit.CommitsCount = 0
// first view will not hit the cache // first view will not hit the cache
testViewRepo(t) testView(t)
// second view will hit the cache // second view will hit the cache
testViewRepo(t) testView(t)
setting.CacheService.LastCommit.CommitsCount = oldCommitsCount setting.CacheService.LastCommit.CommitsCount = oldCommitsCount
} }
func TestViewRepo3(t *testing.T) { func testViewRepoPrivate(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/org3/repo3") req := NewRequest(t, "GET", "/org3/repo3")
session := loginUser(t, "user4") MakeRequest(t, req, http.StatusNotFound)
session.MakeRequest(t, req, http.StatusOK)
t.Run("OrgMemberAccess", func(t *testing.T) {
req = NewRequest(t, "GET", "/org3/repo3")
session := loginUser(t, "user4")
resp := session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), `<div id="repo-files-table"`)
})
t.Run("PublicAccess-AnonymousAccess", func(t *testing.T) {
session := loginUser(t, "user1")
// set unit code to "anonymous read"
req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"repo-unit-access-" + strconv.Itoa(int(unit.TypeCode)): "anonymous-read",
})
session.MakeRequest(t, req, http.StatusSeeOther)
// try to "anonymous read" (ok)
req = NewRequest(t, "GET", "/org3/repo3")
resp := MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), `<span class="ui basic orange label">Public Access</span>`)
// remove "anonymous read"
req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
session.MakeRequest(t, req, http.StatusSeeOther)
// try to "anonymous read" (not found)
req = NewRequest(t, "GET", "/org3/repo3")
MakeRequest(t, req, http.StatusNotFound)
})
} }
func TestViewRepo1CloneLinkAnonymous(t *testing.T) { func testViewRepo1CloneLinkAnonymous(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1") req := NewRequest(t, "GET", "/user2/repo1")
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
@ -139,8 +190,8 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
assert.Equal(t, "tea clone user2/repo1", link) assert.Equal(t, "tea clone user2/repo1", link)
} }
func TestViewRepo1CloneLinkAuthorized(t *testing.T) { func testViewRepo1CloneLinkAuthorized(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -162,8 +213,8 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
assert.Equal(t, "tea clone user2/repo1", link) assert.Equal(t, "tea clone user2/repo1", link)
} }
func TestViewRepoWithSymlinks(t *testing.T) { func testViewRepoWithSymlinks(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")() defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -186,8 +237,8 @@ func TestViewRepoWithSymlinks(t *testing.T) {
} }
// TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file // TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file
func TestViewFileInRepo(t *testing.T) { func testViewFileInRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -205,8 +256,8 @@ func TestViewFileInRepo(t *testing.T) {
} }
// TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file // TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file
func TestBlameFileInRepo(t *testing.T) { func testBlameFileInRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -224,8 +275,8 @@ func TestBlameFileInRepo(t *testing.T) {
} }
// TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory // TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory
func TestViewRepoDirectory(t *testing.T) { func testViewRepoDirectory(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -246,8 +297,8 @@ func TestViewRepoDirectory(t *testing.T) {
} }
// ensure that the all the different ways to find and render a README work // ensure that the all the different ways to find and render a README work
func TestViewRepoDirectoryReadme(t *testing.T) { func testViewRepoDirectoryReadme(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
// there are many combinations: // there are many combinations:
// - READMEs can be .md, .txt, or have no extension // - READMEs can be .md, .txt, or have no extension
@ -353,8 +404,8 @@ func TestViewRepoDirectoryReadme(t *testing.T) {
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/") missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
} }
func TestMarkDownReadmeImage(t *testing.T) { func testMarkDownReadmeImage(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -375,8 +426,8 @@ func TestMarkDownReadmeImage(t *testing.T) {
assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src) assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src)
} }
func TestMarkDownReadmeImageSubfolder(t *testing.T) { func testMarkDownReadmeImageSubfolder(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -398,8 +449,8 @@ func TestMarkDownReadmeImageSubfolder(t *testing.T) {
assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src) assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src)
} }
func TestGeneratedSourceLink(t *testing.T) { func testGeneratedSourceLink(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
t.Run("Rendered file", func(t *testing.T) { t.Run("Rendered file", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -434,8 +485,8 @@ func TestGeneratedSourceLink(t *testing.T) {
}) })
} }
func TestViewCommit(t *testing.T) { func testViewCommit(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789") req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789")
req.Header.Add("Accept", "text/html") req.Header.Add("Accept", "text/html")

Loading…
Cancel
Save