1
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-02-02 15:09:33 -05:00

Merge branch 'main' into agit/update

This commit is contained in:
hiifong 2025-01-14 19:38:44 +08:00 committed by GitHub
commit 59f37ce829
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 747 additions and 608 deletions

View File

@ -22,6 +22,7 @@
content_type: 1 # json content_type: 1 # json
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
is_active: true is_active: true
- -
id: 4 id: 4
repo_id: 2 repo_id: 2
@ -29,3 +30,23 @@
content_type: 1 # json content_type: 1 # json
events: '{"push_only":true,"branch_filter":"{master,feature*}"}' events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
is_active: true is_active: true
-
id: 5
repo_id: 0
owner_id: 0
url: www.example.com/url5
content_type: 1 # json
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
is_active: true
is_system_webhook: true
-
id: 6
repo_id: 0
owner_id: 0
url: www.example.com/url6
content_type: 1 # json
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
is_active: true
is_system_webhook: false

View File

@ -175,10 +175,14 @@ func (p *Permission) LogString() string {
return fmt.Sprintf(format, args...) return fmt.Sprintf(format, args...)
} }
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) { func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
if user == nil || user.ID <= 0 { if user == nil || user.ID <= 0 {
// for anonymous access, it could be:
// AccessMode is None or Read, units has repo units, unitModes is nil
return return
} }
// apply everyone access permissions
for _, u := range perm.units { for _, u := range perm.units {
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] { if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
if perm.everyoneAccessMode == nil { if perm.everyoneAccessMode == nil {
@ -187,17 +191,40 @@ func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode
} }
} }
if perm.unitsMode == nil {
// if unitsMode is not set, then it means that the default p.AccessMode applies to all units
return
}
// remove no permission units
origPermUnits := perm.units
perm.units = make([]*repo_model.RepoUnit, 0, len(perm.units))
for _, u := range origPermUnits {
shouldKeep := false
for t := range perm.unitsMode {
if shouldKeep = u.Type == t; shouldKeep {
break
}
}
for t := range perm.everyoneAccessMode {
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
break
}
}
if shouldKeep {
perm.units = append(perm.units, u)
}
}
} }
// GetUserRepoPermission returns the user permissions to the repository // GetUserRepoPermission returns the user permissions to the repository
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
defer func() { defer func() {
if err == nil { if err == nil {
applyEveryoneRepoPermission(user, &perm) finalProcessRepoUnitPermission(user, &perm)
}
if log.IsTrace() {
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
} }
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
}() }()
if err = repo.LoadUnits(ctx); err != nil { if err = repo.LoadUnits(ctx); err != nil {
@ -294,16 +321,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
} }
} }
// remove no permission units
perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
for t := range perm.unitsMode {
for _, u := range repo.Units {
if u.Type == t {
perm.units = append(perm.units, u)
}
}
}
return perm, err return perm, err
} }

View File

@ -50,7 +50,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
}, },
} }
applyEveryoneRepoPermission(nil, &perm) finalProcessRepoUnitPermission(nil, &perm)
assert.False(t, perm.CanRead(unit.TypeWiki)) assert.False(t, perm.CanRead(unit.TypeWiki))
perm = Permission{ perm = Permission{
@ -59,7 +59,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
}, },
} }
applyEveryoneRepoPermission(&user_model.User{ID: 0}, &perm) finalProcessRepoUnitPermission(&user_model.User{ID: 0}, &perm)
assert.False(t, perm.CanRead(unit.TypeWiki)) assert.False(t, perm.CanRead(unit.TypeWiki))
perm = Permission{ perm = Permission{
@ -68,7 +68,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
}, },
} }
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm) finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
assert.True(t, perm.CanRead(unit.TypeWiki)) assert.True(t, perm.CanRead(unit.TypeWiki))
perm = Permission{ perm = Permission{
@ -77,20 +77,22 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
}, },
} }
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm) finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
// it should work the same as "EveryoneAccessMode: none" because the default AccessMode should be applied to units // it should work the same as "EveryoneAccessMode: none" because the default AccessMode should be applied to units
assert.True(t, perm.CanWrite(unit.TypeWiki)) assert.True(t, perm.CanWrite(unit.TypeWiki))
perm = Permission{ perm = Permission{
units: []*repo_model.RepoUnit{ units: []*repo_model.RepoUnit{
{Type: unit.TypeCode}, // will be removed
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
}, },
unitsMode: map[unit.Type]perm_model.AccessMode{ unitsMode: map[unit.Type]perm_model.AccessMode{
unit.TypeWiki: perm_model.AccessModeWrite, unit.TypeWiki: perm_model.AccessModeWrite,
}, },
} }
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm) finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
assert.True(t, perm.CanWrite(unit.TypeWiki)) assert.True(t, perm.CanWrite(unit.TypeWiki))
assert.Len(t, perm.units, 1)
} }
func TestUnitAccessMode(t *testing.T) { func TestUnitAccessMode(t *testing.T) {

View File

@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
for _, o := range oldLicenses { for _, o := range oldLicenses {
// Update already existing license // Update already existing license
if o.License == license { if o.License == license {
o.CommitID = commitID
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil { if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
return err return err
} }

View File

@ -11,35 +11,13 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
// Copy copies file from source to target path. // SyncFile synchronizes the two files. This is skipped if both files
func Copy(src, dest string) error {
// Gather file information to set back later.
si, err := os.Lstat(src)
if err != nil {
return err
}
// Handle symbolic link.
if si.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(src)
if err != nil {
return err
}
// NOTE: os.Chmod and os.Chtimes don't recognize symbolic link,
// which will lead "no such file or directory" error.
return os.Symlink(target, dest)
}
return util.CopyFile(src, dest)
}
// Sync synchronizes the two files. This is skipped if both files
// exist and the size, modtime, and mode match. // exist and the size, modtime, and mode match.
func Sync(srcPath, destPath string) error { func SyncFile(srcPath, destPath string) error {
dest, err := os.Stat(destPath) dest, err := os.Stat(destPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return Copy(srcPath, destPath) return util.CopyFile(srcPath, destPath)
} }
return err return err
} }
@ -55,7 +33,7 @@ func Sync(srcPath, destPath string) error {
return nil return nil
} }
return Copy(srcPath, destPath) return util.CopyFile(srcPath, destPath)
} }
// SyncDirs synchronizes files recursively from source to target directory. // SyncDirs synchronizes files recursively from source to target directory.
@ -66,6 +44,10 @@ func SyncDirs(srcPath, destPath string) error {
return err return err
} }
// the keep file is used to keep the directory in a git repository, it doesn't need to be synced
// and go-git doesn't work with the ".keep" file (it would report errors like "ref is empty")
const keepFile = ".keep"
// find and delete all untracked files // find and delete all untracked files
destFiles, err := util.ListDirRecursively(destPath, &util.ListDirOptions{IncludeDir: true}) destFiles, err := util.ListDirRecursively(destPath, &util.ListDirOptions{IncludeDir: true})
if err != nil { if err != nil {
@ -73,16 +55,20 @@ func SyncDirs(srcPath, destPath string) error {
} }
for _, destFile := range destFiles { for _, destFile := range destFiles {
destFilePath := filepath.Join(destPath, destFile) destFilePath := filepath.Join(destPath, destFile)
shouldRemove := filepath.Base(destFilePath) == keepFile
if _, err = os.Stat(filepath.Join(srcPath, destFile)); err != nil { if _, err = os.Stat(filepath.Join(srcPath, destFile)); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
// if src file does not exist, remove dest file shouldRemove = true
if err = os.RemoveAll(destFilePath); err != nil {
return err
}
} else { } else {
return err return err
} }
} }
// if src file does not exist, remove dest file
if shouldRemove {
if err = os.RemoveAll(destFilePath); err != nil {
return err
}
}
} }
// sync src files to dest // sync src files to dest
@ -95,8 +81,8 @@ func SyncDirs(srcPath, destPath string) error {
// util.ListDirRecursively appends a slash to the directory name // util.ListDirRecursively appends a slash to the directory name
if strings.HasSuffix(srcFile, "/") { if strings.HasSuffix(srcFile, "/") {
err = os.MkdirAll(destFilePath, os.ModePerm) err = os.MkdirAll(destFilePath, os.ModePerm)
} else { } else if filepath.Base(destFilePath) != keepFile {
err = Sync(filepath.Join(srcPath, srcFile), destFilePath) err = SyncFile(filepath.Join(srcPath, srcFile), destFilePath)
} }
if err != nil { if err != nil {
return err return err

View File

@ -11,6 +11,19 @@ import (
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
) )
// GetSystemOrDefaultWebhooks returns webhooks by given argument or all if argument is missing.
func GetSystemOrDefaultWebhooks(ctx context.Context, isSystemWebhook optional.Option[bool]) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)
if !isSystemWebhook.Has() {
return webhooks, db.GetEngine(ctx).Where("repo_id=? AND owner_id=?", 0, 0).
Find(&webhooks)
}
return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, isSystemWebhook.Value()).
Find(&webhooks)
}
// GetDefaultWebhooks returns all admin-default webhooks. // GetDefaultWebhooks returns all admin-default webhooks.
func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5) webhooks := make([]*Webhook, 0, 5)

View File

@ -0,0 +1,37 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package webhook
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert"
)
func TestGetSystemOrDefaultWebhooks(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := GetSystemOrDefaultWebhooks(db.DefaultContext, optional.None[bool]())
assert.NoError(t, err)
if assert.Len(t, hooks, 2) {
assert.Equal(t, int64(5), hooks[0].ID)
assert.Equal(t, int64(6), hooks[1].ID)
}
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(true))
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(5), hooks[0].ID)
}
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(false))
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(6), hooks[0].ID)
}
}

View File

@ -1115,9 +1115,6 @@ blame.ignore_revs=Ignorování revizí v <a href="%s">.git-blame-ignorerevs</a>.
blame.ignore_revs.failed=Nepodařilo se ignorovat revize v <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=Nepodařilo se ignorovat revize v <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Zobrazí maximálně 30 uživatelů user_search_tooltip=Zobrazí maximálně 30 uživatelů
tree_path_not_found_commit=Cesta %[1]s v commitu %[2]s neexistuje
tree_path_not_found_branch=Cesta %[1]s ve větvi %[2]s neexistuje
tree_path_not_found_tag=Cesta %[1]s ve značce %[2]s neexistuje
transfer.accept=Přijmout převod transfer.accept=Přijmout převod
transfer.accept_desc=Převést do „%s“ transfer.accept_desc=Převést do „%s“

View File

@ -1111,9 +1111,6 @@ blame.ignore_revs=Revisionen in <a href="%s">.git-blame-ignore-revs</a> werden i
blame.ignore_revs.failed=Fehler beim Ignorieren der Revisionen in <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=Fehler beim Ignorieren der Revisionen in <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Zeigt maximal 30 Benutzer user_search_tooltip=Zeigt maximal 30 Benutzer
tree_path_not_found_commit=Pfad %[1]s existiert nicht in Commit%[2]s
tree_path_not_found_branch=Pfad %[1]s existiert nicht in Branch %[2]s
tree_path_not_found_tag=Pfad %[1]s existiert nicht in Tag %[2]s
transfer.accept=Übertragung Akzeptieren transfer.accept=Übertragung Akzeptieren
transfer.accept_desc=`Übertragung nach "%s"` transfer.accept_desc=`Übertragung nach "%s"`

View File

@ -992,9 +992,6 @@ blame_prior=Προβολή ευθύνης πριν από αυτή την αλλ
blame.ignore_revs=Αγνόηση των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>. Πατήστε <a href="%s">εδώ</a> για να το παρακάμψετε και να δείτε την κανονική προβολή ευθυνών. blame.ignore_revs=Αγνόηση των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>. Πατήστε <a href="%s">εδώ</a> για να το παρακάμψετε και να δείτε την κανονική προβολή ευθυνών.
blame.ignore_revs.failed=Αποτυχία αγνόησης των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=Αποτυχία αγνόησης των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>.
tree_path_not_found_commit=Η διαδρομή %[1]s δεν υπάρχει στην υποβολή %[2]s
tree_path_not_found_branch=Η διαδρομή %[1]s δεν υπάρχει στον κλάδο %[2]s
tree_path_not_found_tag=Η διαδρομή %[1]s δεν υπάρχει στην ετικέτα %[2]s
transfer.accept=Αποδοχή Μεταφοράς transfer.accept=Αποδοχή Μεταφοράς
transfer.reject=Απόρριψη Μεταφοράς transfer.reject=Απόρριψη Μεταφοράς

View File

@ -2158,7 +2158,7 @@ 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_wiki_everyone_access = Default Access Permission for signed-in users: 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

View File

@ -982,9 +982,6 @@ blame_prior=Ver la culpa antes de este cambio
blame.ignore_revs=Ignorando revisiones en <a href="%s">.git-blame-ignore-revs</a>. Haga clic <a href="%s">aquí para saltar</a> y para a la vista normal. blame.ignore_revs=Ignorando revisiones en <a href="%s">.git-blame-ignore-revs</a>. Haga clic <a href="%s">aquí para saltar</a> y para a la vista normal.
blame.ignore_revs.failed=No se pudieron ignorar las revisiones en <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=No se pudieron ignorar las revisiones en <a href="%s">.git-blame-ignore-revs</a>.
tree_path_not_found_commit=La ruta %[1]s no existe en el commit %[2]s
tree_path_not_found_branch=La ruta %[1]s no existe en la rama %[2]s
tree_path_not_found_tag=La ruta %[1]s no existe en la etiqueta %[2]s
transfer.accept=Aceptar transferencia transfer.accept=Aceptar transferencia
transfer.reject=Rechazar transferencia transfer.reject=Rechazar transferencia

View File

@ -1115,9 +1115,6 @@ blame.ignore_revs=Les révisions dans <a href="%s">.git-blame-ignore-revs</a> so
blame.ignore_revs.failed=Impossible d'ignorer les révisions dans <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=Impossible d'ignorer les révisions dans <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Affiche un maximum de 30 utilisateurs user_search_tooltip=Affiche un maximum de 30 utilisateurs
tree_path_not_found_commit=Le chemin %[1]s nexiste pas dans la révision %[2]s.
tree_path_not_found_branch=Le chemin %[1]s nexiste pas dans la branche %[2]s.
tree_path_not_found_tag=Le chemin %[1]s nexiste pas dans létiquette %[2]s.
transfer.accept=Accepter le transfert transfer.accept=Accepter le transfert
transfer.accept_desc=Transférer à « %s » transfer.accept_desc=Transférer à « %s »

View File

@ -1115,9 +1115,6 @@ blame.ignore_revs=Ag déanamh neamhairde de leasuithe i <a href="%s">.git-blame-
blame.ignore_revs.failed=Theip ar neamhaird a dhéanamh ar leasuithe i <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=Theip ar neamhaird a dhéanamh ar leasuithe i <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Taispeáint uasmhéid de 30 úsáideoir user_search_tooltip=Taispeáint uasmhéid de 30 úsáideoir
tree_path_not_found_commit=Níl cosán %[1]s ann i dtiomantas %[2]s
tree_path_not_found_branch=Níl cosán %[1]s ann i mbrainse %[2]s
tree_path_not_found_tag=Níl cosán %[1]s ann i gclib %[2]s
transfer.accept=Glac le hAistriú transfer.accept=Glac le hAistriú
transfer.accept_desc=Aistriú chuig “%s” transfer.accept_desc=Aistriú chuig “%s”

View File

@ -1109,9 +1109,6 @@ blame_prior=この変更より前のBlameを表示
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> で指定されたリビジョンは除外しています。 これを迂回して通常のBlame表示を見るには <a href="%s">ここ</a>をクリック。 blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> で指定されたリビジョンは除外しています。 これを迂回して通常のBlame表示を見るには <a href="%s">ここ</a>をクリック。
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> によるリビジョンの無視は失敗しました。 blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> によるリビジョンの無視は失敗しました。
tree_path_not_found_commit=パス %[1]s はコミット %[2]s に存在しません
tree_path_not_found_branch=パス %[1]s はブランチ %[2]s に存在しません
tree_path_not_found_tag=パス %[1]s はタグ %[2]s に存在しません
transfer.accept=移転を承認 transfer.accept=移転を承認
transfer.accept_desc=`"%s" に移転` transfer.accept_desc=`"%s" に移転`

View File

@ -997,9 +997,6 @@ blame_prior=Aplūkot vainīgo par izmaiņām pirms šīs revīzijas
blame.ignore_revs=Neņem vērā izmaiņas no <a href="%s">.git-blame-ignore-revs</a>. Nospiediet <a href="%s">šeit, lai to apietu</a> un redzētu visu izmaiņu skatu. blame.ignore_revs=Neņem vērā izmaiņas no <a href="%s">.git-blame-ignore-revs</a>. Nospiediet <a href="%s">šeit, lai to apietu</a> un redzētu visu izmaiņu skatu.
blame.ignore_revs.failed=Neizdevās neņemt vērā izmaiņas no <a href="%s">.git-blam-ignore-revs</a>. blame.ignore_revs.failed=Neizdevās neņemt vērā izmaiņas no <a href="%s">.git-blam-ignore-revs</a>.
tree_path_not_found_commit=Revīzijā %[2]s neeksistē ceļš %[1]s
tree_path_not_found_branch=Atzarā %[2]s nepastāv ceļš %[1]s
tree_path_not_found_tag=Tagā %[2]s nepastāv ceļš %[1]s
transfer.accept=Apstiprināt īpašnieka maiņu transfer.accept=Apstiprināt īpašnieka maiņu
transfer.reject=Noraidīt īpašnieka maiņu transfer.reject=Noraidīt īpašnieka maiņu

View File

@ -1115,9 +1115,6 @@ blame.ignore_revs=Ignorando as revisões em <a href="%s">.git-blame-ignore-revs<
blame.ignore_revs.failed=Falhou ao ignorar as revisões em <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=Falhou ao ignorar as revisões em <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Mostra um máximo de 30 utilizadores user_search_tooltip=Mostra um máximo de 30 utilizadores
tree_path_not_found_commit=A localização %[1]s não existe no cometimento %[2]s
tree_path_not_found_branch=A localização %[1]s não existe no ramo %[2]s
tree_path_not_found_tag=A localização %[1]s não existe na etiqueta %[2]s
transfer.accept=Aceitar transferência transfer.accept=Aceitar transferência
transfer.accept_desc=`Transferir para "%s"` transfer.accept_desc=`Transferir para "%s"`

View File

@ -978,8 +978,6 @@ delete_preexisting_content=Удалить файлы из %s
delete_preexisting_success=Удалены непринятые файлы в %s delete_preexisting_success=Удалены непринятые файлы в %s
blame_prior=Показать авторство предшествующих изменений blame_prior=Показать авторство предшествующих изменений
tree_path_not_found_commit=Путь %[1]s не существует в коммите %[2]s
tree_path_not_found_branch=Путь %[1]s не существует в ветке %[2]s
transfer.accept=Принять трансфер transfer.accept=Принять трансфер
transfer.reject=Отказаться от перемещения transfer.reject=Отказаться от перемещения

View File

@ -1083,9 +1083,6 @@ blame_prior=Bu değişiklikten önceki suçu görüntüle
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>. blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>.
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı. blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
tree_path_not_found_commit=%[1] yolu, %[2]s işlemesinde mevcut değil
tree_path_not_found_branch=%[1] yolu, %[2]s dalında mevcut değil
tree_path_not_found_tag=%[1] yolu, %[2]s etiketinde mevcut değil
transfer.accept=Aktarımı Kabul Et transfer.accept=Aktarımı Kabul Et
transfer.reject=Aktarımı Reddet transfer.reject=Aktarımı Reddet

View File

@ -1111,9 +1111,6 @@ blame.ignore_revs=忽略 <a href="%s">.git-blame-ignore-revs</a> 的修订。点
blame.ignore_revs.failed=忽略 <a href="%s">.git-blame-ignore-revs</a> 版本失败。 blame.ignore_revs.failed=忽略 <a href="%s">.git-blame-ignore-revs</a> 版本失败。
user_search_tooltip=最多显示30名用户 user_search_tooltip=最多显示30名用户
tree_path_not_found_commit=路径%[1]s 在提交 %[2]s 中不存在
tree_path_not_found_branch=路径 %[1]s 不存在于分支 %[2]s 中。
tree_path_not_found_tag=路径 %[1]s 不存在于标签 %[2]s 中
transfer.accept=接受转移 transfer.accept=接受转移
transfer.accept_desc=`转移到 "%s"` transfer.accept_desc=`转移到 "%s"`

View File

@ -1108,9 +1108,6 @@ blame.ignore_revs=忽略 <a href="%s">.git-blame-ignore-revs</a> 中的修訂。
blame.ignore_revs.failed=忽略 <a href="%s">.git-blame-ignore-revs</a> 中的修訂失敗。 blame.ignore_revs.failed=忽略 <a href="%s">.git-blame-ignore-revs</a> 中的修訂失敗。
user_search_tooltip=顯示最多 30 個使用者 user_search_tooltip=顯示最多 30 個使用者
tree_path_not_found_commit=路徑 %[1]s 在提交 %[2]s 中不存在
tree_path_not_found_branch=路徑 %[1]s 在分支 %[2]s 中不存在
tree_path_not_found_tag=路徑 %[1]s 在標籤 %[2]s 中不存在
transfer.accept=同意轉移 transfer.accept=同意轉移
transfer.accept_desc=轉移到「%s」 transfer.accept_desc=轉移到「%s」

55
package-lock.json generated
View File

@ -67,7 +67,6 @@
"devDependencies": { "devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1", "@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
"@playwright/test": "1.49.1", "@playwright/test": "1.49.1",
"@silverwind/vue-tsc": "2.1.13",
"@stoplight/spectral-cli": "6.14.2", "@stoplight/spectral-cli": "6.14.2",
"@stylistic/eslint-plugin-js": "2.12.1", "@stylistic/eslint-plugin-js": "2.12.1",
"@stylistic/stylelint-plugin": "3.1.1", "@stylistic/stylelint-plugin": "3.1.1",
@ -112,7 +111,8 @@
"type-fest": "4.30.2", "type-fest": "4.30.2",
"updates": "16.4.1", "updates": "16.4.1",
"vite-string-plugin": "1.3.4", "vite-string-plugin": "1.3.4",
"vitest": "2.1.8" "vitest": "2.1.8",
"vue-tsc": "2.2.0"
}, },
"engines": { "engines": {
"node": ">= 18.0.0" "node": ">= 18.0.0"
@ -3643,24 +3643,6 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@silverwind/vue-tsc": {
"version": "2.1.13",
"resolved": "https://registry.npmjs.org/@silverwind/vue-tsc/-/vue-tsc-2.1.13.tgz",
"integrity": "sha512-ejFxz1KZiUGAESbC+eURnjqt0N95qkU9eZU7W15wgF9zV+v2FEu3ZLduuXTC7D/Sg6lL1R/QjPfUbxbAbBQOsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "~2.4.11",
"@vue/language-core": "2.1.10",
"semver": "^7.5.4"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
},
"peerDependencies": {
"typescript": ">=5.0.0"
}
},
"node_modules/@silverwind/vue3-calendar-heatmap": { "node_modules/@silverwind/vue3-calendar-heatmap": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz",
@ -5251,17 +5233,17 @@
} }
}, },
"node_modules/@vue/language-core": { "node_modules/@vue/language-core": {
"version": "2.1.10", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.10.tgz", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz",
"integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==", "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/language-core": "~2.4.8", "@volar/language-core": "~2.4.11",
"@vue/compiler-dom": "^3.5.0", "@vue/compiler-dom": "^3.5.0",
"@vue/compiler-vue2": "^2.7.16", "@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0", "@vue/shared": "^3.5.0",
"alien-signals": "^0.2.0", "alien-signals": "^0.4.9",
"minimatch": "^9.0.3", "minimatch": "^9.0.3",
"muggle-string": "^0.4.1", "muggle-string": "^0.4.1",
"path-browserify": "^1.0.1" "path-browserify": "^1.0.1"
@ -5664,9 +5646,9 @@
} }
}, },
"node_modules/alien-signals": { "node_modules/alien-signals": {
"version": "0.2.2", "version": "0.4.14",
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz", "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz",
"integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==", "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -14526,6 +14508,23 @@
} }
} }
}, },
"node_modules/vue-tsc": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.0.tgz",
"integrity": "sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "~2.4.11",
"@vue/language-core": "2.2.0"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
},
"peerDependencies": {
"typescript": ">=5.0.0"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",

View File

@ -66,7 +66,6 @@
"devDependencies": { "devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1", "@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
"@playwright/test": "1.49.1", "@playwright/test": "1.49.1",
"@silverwind/vue-tsc": "2.1.13",
"@stoplight/spectral-cli": "6.14.2", "@stoplight/spectral-cli": "6.14.2",
"@stylistic/eslint-plugin-js": "2.12.1", "@stylistic/eslint-plugin-js": "2.12.1",
"@stylistic/stylelint-plugin": "3.1.1", "@stylistic/stylelint-plugin": "3.1.1",
@ -111,7 +110,8 @@
"type-fest": "4.30.2", "type-fest": "4.30.2",
"updates": "16.4.1", "updates": "16.4.1",
"vite-string-plugin": "1.3.4", "vite-string-plugin": "1.3.4",
"vitest": "2.1.8" "vitest": "2.1.8",
"vue-tsc": "2.2.0"
}, },
"browserslist": [ "browserslist": [
"defaults" "defaults"

View File

@ -34,11 +34,30 @@ func ListHooks(ctx *context.APIContext) {
// in: query // in: query
// description: page size of results // description: page size of results
// type: integer // type: integer
// - type: string
// enum:
// - system
// - default
// - all
// description: system, default or both kinds of webhooks
// name: type
// default: system
// in: query
//
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/HookList" // "$ref": "#/responses/HookList"
sysHooks, err := webhook.GetSystemWebhooks(ctx, optional.None[bool]()) // for compatibility the default value is true
isSystemWebhook := optional.Some(true)
typeValue := ctx.FormString("type")
if typeValue == "default" {
isSystemWebhook = optional.Some(false)
} else if typeValue == "all" {
isSystemWebhook = optional.None[bool]()
}
sysHooks, err := webhook.GetSystemOrDefaultWebhooks(ctx, isSystemWebhook)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err) ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err)
return return

View File

@ -26,13 +26,9 @@ type userSearchResponse struct {
Results []*userSearchInfo `json:"results"` Results []*userSearchInfo `json:"results"`
} }
// IssuePosters get posters for current repo's issues/pull requests func IssuePullPosters(ctx *context.Context) {
func IssuePosters(ctx *context.Context) { isPullList := ctx.PathParam("type") == "pulls"
issuePosters(ctx, false) issuePosters(ctx, isPullList)
}
func PullPosters(ctx *context.Context) {
issuePosters(ctx, true)
} }
func issuePosters(ctx *context.Context, isPullList bool) { func issuePosters(ctx *context.Context, isPullList bool) {

View File

@ -17,7 +17,6 @@ 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/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -330,34 +329,17 @@ func LatestRelease(ctx *context.Context) {
ctx.Redirect(release.Link()) ctx.Redirect(release.Link())
} }
// NewRelease render creating or edit release page func newReleaseCommon(ctx *context.Context) {
func NewRelease(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.release.new_release") ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
ctx.Data["PageIsReleaseList"] = true ctx.Data["PageIsReleaseList"] = true
ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
if tagName := ctx.FormString("tag"); len(tagName) > 0 {
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.ServerError("GetRelease", err)
return
}
if rel != nil { tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
rel.Repo = ctx.Repo.Repository if err != nil {
if err := rel.LoadAttributes(ctx); err != nil { ctx.ServerError("GetTagNamesByRepoID", err)
ctx.ServerError("LoadAttributes", err) return
return
}
ctx.Data["tag_name"] = rel.TagName
if rel.Target != "" {
ctx.Data["tag_target"] = rel.Target
}
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["attachments"] = rel.Attachments
}
} }
ctx.Data["Tags"] = tags
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
if err != nil { if err != nil {
@ -368,35 +350,74 @@ func NewRelease(ctx *context.Context) {
upload.AddUploadContext(ctx, "release") upload.AddUploadContext(ctx, "release")
// For New Release page PrepareBranchList(ctx) // for New Release page
PrepareBranchList(ctx) }
// NewRelease render creating or edit release page
func NewRelease(ctx *context.Context) {
newReleaseCommon(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) ctx.Data["ShowCreateTagOnlyButton"] = true
if err != nil {
ctx.ServerError("GetTagNamesByRepoID", err) // pre-fill the form with the tag name, target branch and the existing release (if exists)
return ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
if tagName := ctx.FormString("tag"); tagName != "" {
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.ServerError("GetRelease", err)
return
}
if rel != nil {
rel.Repo = ctx.Repo.Repository
if err = rel.LoadAttributes(ctx); err != nil {
ctx.ServerError("LoadAttributes", err)
return
}
ctx.Data["ShowCreateTagOnlyButton"] = false
ctx.Data["tag_name"] = rel.TagName
ctx.Data["tag_target"] = rel.Target
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["attachments"] = rel.Attachments
}
} }
ctx.Data["Tags"] = tags
ctx.HTML(http.StatusOK, tplReleaseNew) ctx.HTML(http.StatusOK, tplReleaseNew)
} }
// NewReleasePost response for creating a release // NewReleasePost response for creating a release
func NewReleasePost(ctx *context.Context) { func NewReleasePost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewReleaseForm) newReleaseCommon(ctx)
ctx.Data["Title"] = ctx.Tr("repo.release.new_release") if ctx.Written() {
ctx.Data["PageIsReleaseList"] = true
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("GetTagNamesByRepoID", err)
return return
} }
ctx.Data["Tags"] = tags
form := web.GetForm(ctx).(*forms.NewReleaseForm)
// first, check whether the release exists, and prepare "ShowCreateTagOnlyButton"
// the logic should be done before the form error check to make the tmpl has correct variables
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.ServerError("GetRelease", err)
return
}
// We should still show the "tag only" button if the user clicks it, no matter the release exists or not.
// Because if error occurs, end users need to have the chance to edit the name and submit the form with "tag-only" again.
// It is still not completely right, because there could still be cases like this:
// * user visit "new release" page, see the "tag only" button
// * input something, click other buttons but not "tag only"
// * error occurs, the "new release" page is rendered again, but the "tag only" button is gone
// Such cases are not able to be handled by current code, it needs frontend code to toggle the "tag-only" button if the input changes.
// Or another choice is "always show the tag-only button" if error occurs.
ctx.Data["ShowCreateTagOnlyButton"] = form.TagOnly || rel == nil
// do some form checks
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplReleaseNew) ctx.HTML(http.StatusOK, tplReleaseNew)
return return
@ -407,59 +428,49 @@ func NewReleasePost(ctx *context.Context) {
return return
} }
// Title of release cannot be empty if !form.TagOnly && form.Title == "" {
if len(form.TagOnly) == 0 && len(form.Title) == 0 { // if not "tag only", then the title of the release cannot be empty
ctx.RenderWithErr(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form) ctx.RenderWithErr(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form)
return return
} }
var attachmentUUIDs []string handleTagReleaseError := func(err error) {
if setting.Attachment.Enabled { ctx.Data["Err_TagName"] = true
attachmentUUIDs = form.Files switch {
case release_service.IsErrTagAlreadyExists(err):
ctx.RenderWithErr(ctx.Tr("repo.branch.tag_collision", form.TagName), tplReleaseNew, &form)
case repo_model.IsErrReleaseAlreadyExist(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
case release_service.IsErrInvalidTagName(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
case release_service.IsErrProtectedTagName(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
default:
ctx.ServerError("handleTagReleaseError", err)
}
} }
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName) // prepare the git message for creating a new tag
if err != nil { newTagMsg := ""
if !repo_model.IsErrReleaseNotExist(err) { if form.Title != "" && form.AddTagMsg {
ctx.ServerError("GetRelease", err) newTagMsg = form.Title + "\n\n" + form.Content
}
// no release, and tag only
if rel == nil && form.TagOnly {
if err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, newTagMsg); err != nil {
handleTagReleaseError(err)
return return
} }
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
return
}
msg := "" attachmentUUIDs := util.Iif(setting.Attachment.Enabled, form.Files, nil)
if len(form.Title) > 0 && form.AddTagMsg {
msg = form.Title + "\n\n" + form.Content
}
if len(form.TagOnly) > 0 {
if err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, msg); err != nil {
if release_service.IsErrTagAlreadyExists(err) {
e := err.(release_service.ErrTagAlreadyExists)
ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
if release_service.IsErrInvalidTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_invalid"))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
if release_service.IsErrProtectedTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
ctx.ServerError("release_service.CreateNewTag", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
return
}
// no existing release, create a new release
if rel == nil {
rel = &repo_model.Release{ rel = &repo_model.Release{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Repo: ctx.Repo.Repository, Repo: ctx.Repo.Repository,
@ -469,48 +480,39 @@ func NewReleasePost(ctx *context.Context) {
TagName: form.TagName, TagName: form.TagName,
Target: form.Target, Target: form.Target,
Note: form.Content, Note: form.Content,
IsDraft: len(form.Draft) > 0, IsDraft: form.Draft,
IsPrerelease: form.Prerelease, IsPrerelease: form.Prerelease,
IsTag: false, IsTag: false,
} }
if err = release_service.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, newTagMsg); err != nil {
if err = release_service.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil { handleTagReleaseError(err)
ctx.Data["Err_TagName"] = true
switch {
case repo_model.IsErrReleaseAlreadyExist(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
case release_service.IsErrInvalidTagName(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
case release_service.IsErrProtectedTagName(err):
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
default:
ctx.ServerError("CreateRelease", err)
}
return
}
} else {
if !rel.IsTag {
ctx.Data["Err_TagName"] = true
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
return
}
rel.Title = form.Title
rel.Note = form.Content
rel.Target = form.Target
rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease
rel.PublisherID = ctx.Doer.ID
rel.IsTag = false
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil {
ctx.Data["Err_TagName"] = true
ctx.ServerError("UpdateRelease", err)
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
return
} }
log.Trace("Release created: %s/%s:%s", ctx.Doer.LowerName, ctx.Repo.Repository.Name, form.TagName)
// tag exists, try to convert it to a real release
// old logic: if the release is not a tag (it is a real release), do not update it on the "new release" page
// add new logic: if tag-only, do not convert the tag to a release
if form.TagOnly || !rel.IsTag {
ctx.Data["Err_TagName"] = true
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
return
}
// convert a tag to a real release (set is_tag=false)
rel.Title = form.Title
rel.Note = form.Content
rel.Target = form.Target
rel.IsDraft = form.Draft
rel.IsPrerelease = form.Prerelease
rel.PublisherID = ctx.Doer.ID
rel.IsTag = false
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil {
handleTagReleaseError(err)
return
}
ctx.Redirect(ctx.Repo.RepoLink + "/releases") ctx.Redirect(ctx.Repo.RepoLink + "/releases")
} }

View File

@ -11,60 +11,135 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/contexttest" "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestNewReleasePost(t *testing.T) { func TestNewReleasePost(t *testing.T) {
for _, testCase := range []struct { unittest.PrepareTestEnv(t)
RepoID int64
UserID int64
TagName string
Form forms.NewReleaseForm
}{
{
RepoID: 1,
UserID: 2,
TagName: "v1.1", // pre-existing tag
Form: forms.NewReleaseForm{
TagName: "newtag",
Target: "master",
Title: "title",
Content: "content",
},
},
{
RepoID: 1,
UserID: 2,
TagName: "newtag",
Form: forms.NewReleaseForm{
TagName: "newtag",
Target: "master",
Title: "title",
Content: "content",
},
},
} {
unittest.PrepareTestEnv(t)
get := func(t *testing.T, tagName string) *context.Context {
ctx, _ := contexttest.MockContext(t, "user2/repo1/releases/new?tag="+tagName)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
NewRelease(ctx)
return ctx
}
t.Run("NewReleasePage", func(t *testing.T) {
ctx := get(t, "v1.1")
assert.Empty(t, ctx.Data["ShowCreateTagOnlyButton"])
ctx = get(t, "new-tag-name")
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"])
})
post := func(t *testing.T, form forms.NewReleaseForm) *context.Context {
ctx, _ := contexttest.MockContext(t, "user2/repo1/releases/new") ctx, _ := contexttest.MockContext(t, "user2/repo1/releases/new")
contexttest.LoadUser(t, ctx, 2) contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1) contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadGitRepo(t, ctx) contexttest.LoadGitRepo(t, ctx)
web.SetForm(ctx, &testCase.Form) defer ctx.Repo.GitRepo.Close()
web.SetForm(ctx, &form)
NewReleasePost(ctx) NewReleasePost(ctx)
unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ return ctx
RepoID: 1,
PublisherID: 2,
TagName: testCase.Form.TagName,
Target: testCase.Form.Target,
Title: testCase.Form.Title,
Note: testCase.Form.Content,
}, unittest.Cond("is_draft=?", len(testCase.Form.Draft) > 0))
ctx.Repo.GitRepo.Close()
} }
loadRelease := func(t *testing.T, tagName string) *repo_model.Release {
return unittest.GetBean(t, &repo_model.Release{}, unittest.Cond("repo_id=1 AND tag_name=?", tagName))
}
t.Run("NewTagRelease", func(t *testing.T) {
post(t, forms.NewReleaseForm{
TagName: "newtag",
Target: "master",
Title: "title",
Content: "content",
})
rel := loadRelease(t, "newtag")
require.NotNil(t, rel)
assert.False(t, rel.IsTag)
assert.Equal(t, "master", rel.Target)
assert.Equal(t, "title", rel.Title)
assert.Equal(t, "content", rel.Note)
})
t.Run("ReleaseExistsDoUpdate(non-tag)", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{
TagName: "v1.1",
Target: "master",
Title: "updated-title",
Content: "updated-content",
})
rel := loadRelease(t, "v1.1")
require.NotNil(t, rel)
assert.False(t, rel.IsTag)
assert.Equal(t, "testing-release", rel.Title)
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
})
t.Run("ReleaseExistsDoUpdate(tag-only)", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
Target: "master",
Title: "updated-title",
Content: "updated-content",
TagOnly: true,
})
rel := loadRelease(t, "delete-tag")
require.NotNil(t, rel)
assert.True(t, rel.IsTag) // the record should not be updated because the request is "tag-only". TODO: need to improve the logic?
assert.Equal(t, "delete-tag", rel.Title)
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"]) // still show the "tag-only" button
})
t.Run("ReleaseExistsDoUpdate(tag-release)", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
Target: "master",
Title: "updated-title",
Content: "updated-content",
})
rel := loadRelease(t, "delete-tag")
require.NotNil(t, rel)
assert.False(t, rel.IsTag) // the tag has been "updated" to be a real "release"
assert.Equal(t, "updated-title", rel.Title)
assert.Empty(t, ctx.Flash.ErrorMsg)
})
t.Run("TagOnly", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{
TagName: "new-tag-only",
Target: "master",
Title: "title",
Content: "content",
TagOnly: true,
})
rel := loadRelease(t, "new-tag-only")
require.NotNil(t, rel)
assert.True(t, rel.IsTag)
assert.Empty(t, ctx.Flash.ErrorMsg)
})
t.Run("TagOnlyConflict", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{
TagName: "v1.1",
Target: "master",
Title: "title",
Content: "content",
TagOnly: true,
})
rel := loadRelease(t, "v1.1")
require.NotNil(t, rel)
assert.False(t, rel.IsTag)
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
})
} }
func TestCalReleaseNumCommitsBehind(t *testing.T) { func TestCalReleaseNumCommitsBehind(t *testing.T) {

View File

@ -51,7 +51,7 @@ func MustBeNotEmpty(ctx *context.Context) {
// MustBeEditable check that repo can be edited // MustBeEditable check that repo can be edited
func MustBeEditable(ctx *context.Context) { func MustBeEditable(ctx *context.Context) {
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { if !ctx.Repo.Repository.CanEnableEditor() {
ctx.NotFound("", nil) ctx.NotFound("", nil)
return return
} }

View File

@ -49,6 +49,15 @@ 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) {
@ -447,8 +456,9 @@ func SettingsPost(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, repo_model.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: unit_model.TypeCode, 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)
@ -474,7 +484,7 @@ func SettingsPost(ctx *context.Context) {
RepoID: repo.ID, RepoID: repo.ID,
Type: unit_model.TypeWiki, Type: unit_model.TypeWiki,
Config: new(repo_model.UnitConfig), Config: new(repo_model.UnitConfig),
EveryoneAccessMode: perm.ParseAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite), EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite),
}) })
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
} else { } else {
@ -524,6 +534,7 @@ func SettingsPost(ctx *context.Context) {
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
EnableDependencies: form.EnableIssueDependencies, 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 {

View File

@ -249,7 +249,7 @@ func handleRepoEmptyOrBroken(ctx *context.Context) {
} else if reallyEmpty { } else if reallyEmpty {
showEmpty = true // the repo is really empty showEmpty = true // the repo is really empty
updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady) updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady)
} else if ctx.Repo.Commit == nil { } else if branches, _, _ := ctx.Repo.GitRepo.GetBranches(0, 1); len(branches) == 0 {
showEmpty = true // it is not really empty, but there is no branch showEmpty = true // it is not really empty, but there is no branch
// at the moment, other repo units like "actions" are not able to handle such case, // at the moment, other repo units like "actions" are not able to handle such case,
// so we just mark the repo as empty to prevent from displaying these units. // so we just mark the repo as empty to prevent from displaying these units.

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/metrics" "code.gitea.io/gitea/modules/metrics"
"code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/public"
@ -101,7 +102,7 @@ func buildAuthGroup() *auth_service.Group {
group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers
if setting.Service.EnableReverseProxyAuth { if setting.Service.EnableReverseProxyAuth {
group.Add(&auth_service.ReverseProxy{}) // reverseproxy should before Session, otherwise the header will be ignored if user has login group.Add(&auth_service.ReverseProxy{}) // reverse-proxy should before Session, otherwise the header will be ignored if user has login
} }
group.Add(&auth_service.Session{}) group.Add(&auth_service.Session{})
@ -816,21 +817,23 @@ func registerRoutes(m *web.Router) {
m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.Action) m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.Action)
reqRepoAdmin := context.RequireRepoAdmin() reqRepoAdmin := context.RequireRepoAdmin()
reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode) reqRepoCodeWriter := context.RequireUnitWriter(unit.TypeCode)
canEnableEditor := context.CanEnableEditor() reqRepoReleaseWriter := context.RequireUnitWriter(unit.TypeReleases)
reqRepoCodeReader := context.RequireRepoReader(unit.TypeCode) reqRepoReleaseReader := context.RequireUnitReader(unit.TypeReleases)
reqRepoReleaseWriter := context.RequireRepoWriter(unit.TypeReleases) reqRepoIssuesOrPullsWriter := context.RequireUnitWriter(unit.TypeIssues, unit.TypePullRequests)
reqRepoReleaseReader := context.RequireRepoReader(unit.TypeReleases) reqRepoIssuesOrPullsReader := context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests)
reqRepoWikiReader := context.RequireRepoReader(unit.TypeWiki) reqRepoProjectsReader := context.RequireUnitReader(unit.TypeProjects)
reqRepoWikiWriter := context.RequireRepoWriter(unit.TypeWiki) reqRepoProjectsWriter := context.RequireUnitWriter(unit.TypeProjects)
reqRepoIssueReader := context.RequireRepoReader(unit.TypeIssues) reqRepoActionsReader := context.RequireUnitReader(unit.TypeActions)
reqRepoPullsReader := context.RequireRepoReader(unit.TypePullRequests) reqRepoActionsWriter := context.RequireUnitWriter(unit.TypeActions)
reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(unit.TypeIssues, unit.TypePullRequests)
reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests) // the legacy names "reqRepoXxx" should be renamed to the correct name "reqUnitXxx", these permissions are for units, not repos
reqRepoProjectsReader := context.RequireRepoReader(unit.TypeProjects) reqUnitsWithMarkdown := context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases, unit.TypeWiki)
reqRepoProjectsWriter := context.RequireRepoWriter(unit.TypeProjects) reqUnitCodeReader := context.RequireUnitReader(unit.TypeCode)
reqRepoActionsReader := context.RequireRepoReader(unit.TypeActions) reqUnitIssuesReader := context.RequireUnitReader(unit.TypeIssues)
reqRepoActionsWriter := context.RequireRepoWriter(unit.TypeActions) reqUnitPullsReader := context.RequireUnitReader(unit.TypePullRequests)
reqUnitWikiReader := context.RequireUnitReader(unit.TypeWiki)
reqUnitWikiWriter := context.RequireUnitWriter(unit.TypeWiki)
reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) { reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) {
return func(ctx *context.Context) { return func(ctx *context.Context) {
@ -1053,7 +1056,7 @@ func registerRoutes(m *web.Router) {
m.Group("/migrate", func() { m.Group("/migrate", func() {
m.Get("/status", repo.MigrateStatus) m.Get("/status", repo.MigrateStatus)
}) })
}, optSignIn, context.RepoAssignment, reqRepoCodeReader) }, optSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}/-": migrate // end "/{username}/{reponame}/-": migrate
m.Group("/{username}/{reponame}/settings", func() { m.Group("/{username}/{reponame}/settings", func() {
@ -1149,56 +1152,54 @@ func registerRoutes(m *web.Router) {
// end "/{username}/{reponame}/settings" // end "/{username}/{reponame}/settings"
// user/org home, including rss feeds // user/org home, including rss feeds
m.Get("/{username}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRef(), repo.SetEditorconfigIfExists, repo.Home) m.Get("/{username}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch), repo.SetEditorconfigIfExists, repo.Home)
// TODO: maybe it should relax the permission to allow "any access" m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup)
m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases, unit.TypeWiki), web.Bind(structs.MarkupOption{}), misc.Markup)
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Get("/find/*", repo.FindFiles) m.Get("/find/*", repo.FindFiles)
m.Group("/tree-list", func() { m.Group("/tree-list", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.TreeList) m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.TreeList)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.TreeList) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeList)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.TreeList) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeList)
}) })
m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff) m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists). m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists).
Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff). Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).
Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost) Post(reqSignIn, context.RepoMustNotBeArchived(), reqUnitPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost)
}, optSignIn, context.RepoAssignment, reqRepoCodeReader) }, optSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}": repo code: find, compare, list // end "/{username}/{reponame}": repo code: find, compare, list
addIssuesPullsViewRoutes := func() {
// for /{username}/{reponame}/issues" or "/{username}/{reponame}/pulls"
m.Get("/posters", repo.IssuePullPosters)
m.Group("/{index}", func() {
m.Get("/info", repo.GetIssueInfo)
m.Get("/attachments", repo.GetIssueAttachments)
m.Get("/attachments/{uuid}", repo.GetAttachment)
m.Group("/content-history", func() {
m.Get("/overview", repo.GetContentHistoryOverview)
m.Get("/list", repo.GetContentHistoryList)
m.Get("/detail", repo.GetContentHistoryDetail)
})
})
}
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Get("/issues/posters", repo.IssuePosters) // it can't use {type:issues|pulls} because it would conflict with other routes like "/pulls/{index}"
m.Get("/pulls/posters", repo.PullPosters)
m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/comments/{id}/attachments", repo.GetCommentAttachments)
m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels)
m.Get("/milestones", repo.Milestones) m.Get("/milestones", repo.Milestones)
m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls)
m.Group("/{type:issues|pulls}", func() {
m.Group("/{index}", func() {
m.Get("/info", repo.GetIssueInfo)
m.Get("/attachments", repo.GetIssueAttachments)
m.Get("/attachments/{uuid}", repo.GetAttachment)
m.Group("/content-history", func() {
m.Get("/overview", repo.GetContentHistoryOverview)
m.Get("/list", repo.GetContentHistoryList)
m.Get("/detail", repo.GetContentHistoryDetail)
})
})
}, context.RepoRef())
m.Get("/issues/suggestions", repo.IssueSuggestions) m.Get("/issues/suggestions", repo.IssueSuggestions)
}, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones
m.Group("/{username}/{reponame}/{type:issues}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitIssuesReader)
m.Group("/{username}/{reponame}/{type:pulls}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitPullsReader)
// end "/{username}/{reponame}": view milestone, label, issue, pull, etc // end "/{username}/{reponame}": view milestone, label, issue, pull, etc
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}/{type:issues}", func() {
m.Group("/{type:issues|pulls}", func() { m.Get("", repo.Issues)
m.Get("", repo.Issues) m.Get("/{index}", repo.ViewIssue)
m.Group("/{index}", func() { }, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypeExternalTracker))
m.Get("", repo.ViewIssue)
})
})
}, optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests, unit.TypeExternalTracker))
// end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker // end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker
m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc
@ -1209,11 +1210,10 @@ func registerRoutes(m *web.Router) {
m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
}) })
m.Get("/search", repo.SearchRepoIssuesJSON) m.Get("/search", repo.SearchRepoIssuesJSON)
}, context.RepoMustNotBeArchived(), reqRepoIssueReader) }, reqUnitIssuesReader)
// FIXME: should use different URLs but mostly same logic for comments of issue and pull request. addIssuesPullsRoutes := func() {
// So they can apply their own enable/disable logic on routers. // for "/{username}/{reponame}/issues" or "/{username}/{reponame}/pulls"
m.Group("/{type:issues|pulls}", func() {
m.Group("/{index}", func() { m.Group("/{index}", func() {
m.Post("/title", repo.UpdateIssueTitle) m.Post("/title", repo.UpdateIssueTitle)
m.Post("/content", repo.UpdateIssueContent) m.Post("/content", repo.UpdateIssueContent)
@ -1240,39 +1240,37 @@ func registerRoutes(m *web.Router) {
m.Post("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue) m.Post("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue)
m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue) m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue)
m.Post("/delete", reqRepoAdmin, repo.DeleteIssue) m.Post("/delete", reqRepoAdmin, repo.DeleteIssue)
}, context.RepoMustNotBeArchived())
m.Group("/{index}", func() {
m.Post("/content-history/soft-delete", repo.SoftDeleteContentHistory) m.Post("/content-history/soft-delete", repo.SoftDeleteContentHistory)
}) })
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject)
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
m.Post("/request_review", repo.UpdatePullReviewRequest)
m.Post("/dismiss_review", reqRepoAdmin, web.Bind(forms.DismissReviewForm{}), repo.DismissReview)
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues)
m.Post("/resolve_conversation", repo.SetShowOutdatedComments, repo.UpdateResolveConversation)
m.Post("/attachments", repo.UploadIssueAttachment) m.Post("/attachments", repo.UploadIssueAttachment)
m.Post("/attachments/remove", repo.DeleteAttachment) m.Post("/attachments/remove", repo.DeleteAttachment)
m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject)
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues)
m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin) m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin)
m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove) m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove)
}, context.RepoMustNotBeArchived()) }
m.Group("/{type:issues}", addIssuesPullsRoutes, reqUnitIssuesReader, context.RepoMustNotBeArchived())
m.Group("/{type:pulls}", addIssuesPullsRoutes, reqUnitPullsReader, context.RepoMustNotBeArchived())
m.Group("/comments/{id}", func() { m.Group("/comments/{id}", func() {
m.Post("", repo.UpdateCommentContent) m.Post("", repo.UpdateCommentContent)
m.Post("/delete", repo.DeleteComment) m.Post("/delete", repo.DeleteComment)
m.Post("/reactions/{action}", web.Bind(forms.ReactionForm{}), repo.ChangeCommentReaction) m.Post("/reactions/{action}", web.Bind(forms.ReactionForm{}), repo.ChangeCommentReaction)
}, context.RepoMustNotBeArchived()) }, reqRepoIssuesOrPullsReader) // edit issue/pull comment
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
m.Group("/labels", func() { m.Group("/labels", func() {
m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel) m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel)
m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel)
m.Post("/delete", repo.DeleteLabel) m.Post("/delete", repo.DeleteLabel)
m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels)
}, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef()) }, reqRepoIssuesOrPullsWriter, context.RepoRef())
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
m.Group("/milestones", func() { m.Group("/milestones", func() {
m.Combo("/new").Get(repo.NewMilestone). m.Combo("/new").Get(repo.NewMilestone).
Post(web.Bind(forms.CreateMilestoneForm{}), repo.NewMilestonePost) Post(web.Bind(forms.CreateMilestoneForm{}), repo.NewMilestonePost)
@ -1280,11 +1278,15 @@ func registerRoutes(m *web.Router) {
m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost) m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost)
m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) m.Post("/{id}/{action}", repo.ChangeMilestoneStatus)
m.Post("/delete", repo.DeleteMilestone) m.Post("/delete", repo.DeleteMilestone)
}, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef()) }, reqRepoIssuesOrPullsWriter, context.RepoRef())
m.Group("/pull", func() {
m.Post("/{index}/target_branch", repo.UpdatePullRequestTarget) m.Group("", func() {
}, context.RepoMustNotBeArchived()) m.Post("/request_review", repo.UpdatePullReviewRequest)
}, reqSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) m.Post("/dismiss_review", reqRepoAdmin, web.Bind(forms.DismissReviewForm{}), repo.DismissReview)
m.Post("/resolve_conversation", repo.SetShowOutdatedComments, repo.UpdateResolveConversation)
m.Post("/pull/{index}/target_branch", repo.UpdatePullRequestTarget)
}, reqUnitPullsReader)
}, reqSignIn, context.RepoAssignment, context.RepoMustNotBeArchived())
// end "/{username}/{reponame}": create or edit issues, pulls, labels, milestones // end "/{username}/{reponame}": create or edit issues, pulls, labels, milestones
m.Group("/{username}/{reponame}", func() { // repo code m.Group("/{username}/{reponame}", func() { // repo code
@ -1304,18 +1306,18 @@ func registerRoutes(m *web.Router) {
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost) Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
m.Combo("/_cherrypick/{sha:([a-f0-9]{7,64})}/*").Get(repo.CherryPick). m.Combo("/_cherrypick/{sha:([a-f0-9]{7,64})}/*").Get(repo.CherryPick).
Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost) Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost)
}, repo.MustBeEditable) }, context.RepoRefByType(git.RefTypeBranch), context.CanWriteToBranch())
m.Group("", func() { m.Group("", func() {
m.Post("/upload-file", repo.UploadFileToServer) m.Post("/upload-file", repo.UploadFileToServer)
m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
}, repo.MustBeEditable, repo.MustBeAbleToUpload) }, repo.MustBeAbleToUpload, reqRepoCodeWriter)
}, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived()) }, repo.MustBeEditable, context.RepoMustNotBeArchived())
m.Group("/branches", func() { m.Group("/branches", func() {
m.Group("/_new", func() { m.Group("/_new", func() {
m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch) m.Post("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.CreateBranch)
m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch) m.Post("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.CreateBranch)
m.Post("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.CreateBranch) m.Post("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.CreateBranch)
}, web.Bind(forms.NewBranchForm{})) }, web.Bind(forms.NewBranchForm{}))
m.Post("/delete", repo.DeleteBranchPost) m.Post("/delete", repo.DeleteBranchPost)
m.Post("/restore", repo.RestoreBranchPost) m.Post("/restore", repo.RestoreBranchPost)
@ -1324,45 +1326,42 @@ func registerRoutes(m *web.Router) {
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost) m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
}, reqSignIn, context.RepoAssignment, reqRepoCodeReader) }, reqSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}": repo code // end "/{username}/{reponame}": repo code
m.Group("/{username}/{reponame}", func() { // repo tags m.Group("/{username}/{reponame}", func() { // repo tags
m.Group("/tags", func() { m.Group("/tags", func() {
m.Get("", repo.TagsList) m.Get("", repo.TagsList)
m.Get("/list", repo.GetTagList)
m.Get(".rss", feedEnabled, repo.TagsListFeedRSS) m.Get(".rss", feedEnabled, repo.TagsListFeedRSS)
m.Get(".atom", feedEnabled, repo.TagsListFeedAtom) m.Get(".atom", feedEnabled, repo.TagsListFeedAtom)
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed), m.Get("/list", repo.GetTagList)
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true})) }, ctxDataSet("EnableFeed", setting.Other.EnableFeed))
m.Post("/tags/delete", repo.DeleteTag, reqSignIn, m.Post("/tags/delete", reqSignIn, reqRepoCodeWriter, context.RepoMustNotBeArchived(), repo.DeleteTag)
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef()) }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqUnitCodeReader)
}, optSignIn, context.RepoAssignment, reqRepoCodeReader)
// end "/{username}/{reponame}": repo tags // end "/{username}/{reponame}": repo tags
m.Group("/{username}/{reponame}", func() { // repo releases m.Group("/{username}/{reponame}", func() { // repo releases
m.Group("/releases", func() { m.Group("/releases", func() {
m.Get("", repo.Releases) m.Get("", repo.Releases)
m.Get("/tag/*", repo.SingleRelease)
m.Get("/latest", repo.LatestRelease)
m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS) m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS)
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom) m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed), m.Get("/tag/*", repo.SingleRelease)
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true})) m.Get("/latest", repo.LatestRelease)
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment) }, ctxDataSet("EnableFeed", setting.Other.EnableFeed))
m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload) m.Get("/releases/attachments/{uuid}", repo.GetAttachment)
m.Get("/releases/download/{vTag}/{fileName}", repo.RedirectDownload)
m.Group("/releases", func() { m.Group("/releases", func() {
m.Get("/new", repo.NewRelease) m.Get("/new", repo.NewRelease)
m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost) m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)
m.Post("/delete", repo.DeleteRelease) m.Post("/delete", repo.DeleteRelease)
m.Post("/attachments", repo.UploadReleaseAttachment) m.Post("/attachments", repo.UploadReleaseAttachment)
m.Post("/attachments/remove", repo.DeleteAttachment) m.Post("/attachments/remove", repo.DeleteAttachment)
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
m.Group("/releases", func() { m.Group("/releases", func() {
m.Get("/edit/*", repo.EditRelease) m.Get("/edit/*", repo.EditRelease)
m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost)
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache) }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache)
}, optSignIn, context.RepoAssignment, reqRepoReleaseReader) }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoReleaseReader)
// end "/{username}/{reponame}": repo releases // end "/{username}/{reponame}": repo releases
m.Group("/{username}/{reponame}", func() { // to maintain compatibility with old attachments m.Group("/{username}/{reponame}", func() { // to maintain compatibility with old attachments
@ -1440,43 +1439,48 @@ func registerRoutes(m *web.Router) {
m.Group("/{username}/{reponame}/wiki", func() { m.Group("/{username}/{reponame}/wiki", func() {
m.Combo(""). m.Combo("").
Get(repo.Wiki). Get(repo.Wiki).
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) Post(context.RepoMustNotBeArchived(), reqSignIn, reqUnitWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
m.Combo("/*"). m.Combo("/*").
Get(repo.Wiki). Get(repo.Wiki).
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) Post(context.RepoMustNotBeArchived(), reqSignIn, reqUnitWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
m.Get("/blob_excerpt/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob) m.Get("/blob_excerpt/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff) m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff)
m.Get("/raw/*", repo.WikiRaw) m.Get("/raw/*", repo.WikiRaw)
}, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqRepoWikiReader, func(ctx *context.Context) { }, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqUnitWikiReader, func(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true ctx.Data["PageIsWiki"] = true
ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer) ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer)
}) })
// end "/{username}/{reponame}/wiki" // end "/{username}/{reponame}/wiki"
m.Group("/{username}/{reponame}/activity", func() { m.Group("/{username}/{reponame}/activity", func() {
// activity has its own permission checks
m.Get("", repo.Activity) m.Get("", repo.Activity)
m.Get("/{period}", repo.Activity) m.Get("/{period}", repo.Activity)
m.Group("/contributors", func() {
m.Get("", repo.Contributors) m.Group("", func() {
m.Get("/data", repo.ContributorsData) m.Group("/contributors", func() {
}) m.Get("", repo.Contributors)
m.Group("/code-frequency", func() { m.Get("/data", repo.ContributorsData)
m.Get("", repo.CodeFrequency) })
m.Get("/data", repo.CodeFrequencyData) m.Group("/code-frequency", func() {
}) m.Get("", repo.CodeFrequency)
m.Group("/recent-commits", func() { m.Get("/data", repo.CodeFrequencyData)
m.Get("", repo.RecentCommits) })
m.Get("/data", repo.RecentCommitsData) m.Group("/recent-commits", func() {
}) m.Get("", repo.RecentCommits)
m.Get("/data", repo.RecentCommitsData)
})
}, reqUnitCodeReader)
}, },
optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases), optSignIn, context.RepoAssignment, repo.MustBeNotEmpty,
context.RepoRef(), repo.MustBeNotEmpty, context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases),
) )
// end "/{username}/{reponame}/activity" // end "/{username}/{reponame}/activity"
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Group("/pulls/{index}", func() { m.Get("/{type:pulls}", repo.Issues)
m.Group("/{type:pulls}/{index}", func() {
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue) m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue)
m.Get(".diff", repo.DownloadPullDiff) m.Get(".diff", repo.DownloadPullDiff)
m.Get(".patch", repo.DownloadPullPatch) m.Get(".patch", repo.DownloadPullPatch)
@ -1501,7 +1505,7 @@ func registerRoutes(m *web.Router) {
}, context.RepoMustNotBeArchived()) }, context.RepoMustNotBeArchived())
}) })
}) })
}, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqRepoPullsReader) }, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqUnitPullsReader)
// end "/{username}/{reponame}/pulls/{index}": repo pull request // end "/{username}/{reponame}/pulls/{index}": repo pull request
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
@ -1521,42 +1525,39 @@ func registerRoutes(m *web.Router) {
}, repo.MustBeNotEmpty, context.RepoRef()) }, repo.MustBeNotEmpty, context.RepoRef())
m.Group("/media", func() { m.Group("/media", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownloadOrLFS)
m.Get("/blob/{sha}", repo.DownloadByIDOrLFS) m.Get("/blob/{sha}", repo.DownloadByIDOrLFS)
// "/*" route is deprecated, and kept for backward compatibility m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.SingleDownloadOrLFS)
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.SingleDownloadOrLFS) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.SingleDownloadOrLFS)
m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.SingleDownloadOrLFS)
m.Get("/*", context.RepoRefByType(""), repo.SingleDownloadOrLFS) // "/*" route is deprecated, and kept for backward compatibility
}, repo.MustBeNotEmpty) }, repo.MustBeNotEmpty)
m.Group("/raw", func() { m.Group("/raw", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownload)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownload)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload)
m.Get("/blob/{sha}", repo.DownloadByID) m.Get("/blob/{sha}", repo.DownloadByID)
// "/*" route is deprecated, and kept for backward compatibility m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.SingleDownload)
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.SingleDownload) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.SingleDownload)
m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.SingleDownload)
m.Get("/*", context.RepoRefByType(""), repo.SingleDownload) // "/*" route is deprecated, and kept for backward compatibility
}, repo.MustBeNotEmpty) }, repo.MustBeNotEmpty)
m.Group("/render", func() { m.Group("/render", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RenderFile) m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.RenderFile)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RenderFile) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.RenderFile)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RenderFile) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.RenderFile)
m.Get("/blob/{sha}", repo.RenderFile) m.Get("/blob/{sha}", repo.RenderFile)
}, repo.MustBeNotEmpty) }, repo.MustBeNotEmpty)
m.Group("/commits", func() { m.Group("/commits", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits) m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.RefCommits)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.RefCommits)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.RefCommits)
// "/*" route is deprecated, and kept for backward compatibility m.Get("/*", context.RepoRefByType(""), repo.RefCommits) // "/*" route is deprecated, and kept for backward compatibility
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.RefCommits)
}, repo.MustBeNotEmpty) }, repo.MustBeNotEmpty)
m.Group("/blame", func() { m.Group("/blame", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefBlame) m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.RefBlame)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefBlame) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.RefBlame)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefBlame) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.RefBlame)
}, repo.MustBeNotEmpty) }, repo.MustBeNotEmpty)
m.Get("/blob_excerpt/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob) m.Get("/blob_excerpt/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
@ -1568,27 +1569,27 @@ func registerRoutes(m *web.Router) {
m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.CherryPick) m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
}, repo.MustBeNotEmpty, context.RepoRef()) }, repo.MustBeNotEmpty, context.RepoRef())
m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed) m.Get("/rss/branch/*", context.RepoRefByType(git.RefTypeBranch), feedEnabled, feed.RenderBranchFeed)
m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed) m.Get("/atom/branch/*", context.RepoRefByType(git.RefTypeBranch), feedEnabled, feed.RenderBranchFeed)
m.Group("/src", func() { m.Group("/src", func() {
m.Get("", func(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink) }) // there is no "{owner}/{repo}/src" page, so redirect to "{owner}/{repo}" to avoid 404 m.Get("", func(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink) }) // there is no "{owner}/{repo}/src" page, so redirect to "{owner}/{repo}" to avoid 404
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.Home)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.Home)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.Home)
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.Home) // "/*" route is deprecated, and kept for backward compatibility m.Get("/*", context.RepoRefByType(""), repo.Home) // "/*" route is deprecated, and kept for backward compatibility
}, repo.SetEditorconfigIfExists) }, repo.SetEditorconfigIfExists)
m.Get("/forks", context.RepoRef(), repo.Forks) m.Get("/forks", context.RepoRef(), repo.Forks)
m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff)
m.Post("/lastcommit/*", context.RepoRefByType(context.RepoRefCommit), repo.LastCommit) m.Post("/lastcommit/*", context.RepoRefByType(git.RefTypeCommit), repo.LastCommit)
}, optSignIn, context.RepoAssignment, reqRepoCodeReader) }, optSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}": repo code // end "/{username}/{reponame}": repo code
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Get("/stars", repo.Stars) m.Get("/stars", repo.Stars)
m.Get("/watchers", repo.Watchers) m.Get("/watchers", repo.Watchers)
m.Get("/search", reqRepoCodeReader, repo.Search) m.Get("/search", reqUnitCodeReader, repo.Search)
m.Post("/action/{action}", reqSignIn, repo.Action) m.Post("/action/{action}", reqSignIn, repo.Action)
}, optSignIn, context.RepoAssignment, context.RepoRef()) }, optSignIn, context.RepoAssignment, context.RepoRef())

View File

@ -9,31 +9,20 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/log"
) )
// RequireRepoAdmin returns a middleware for requiring repository admin permission // RequireRepoAdmin returns a middleware for requiring repository admin permission
func RequireRepoAdmin() func(ctx *Context) { func RequireRepoAdmin() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.IsSigned || !ctx.Repo.IsAdmin() { if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) ctx.NotFound("RequireRepoAdmin denies the request", nil)
return return
} }
} }
} }
// RequireRepoWriter returns a middleware for requiring repository write to the specify unitType // CanWriteToBranch checks if the user is allowed to write to the branch of the repo
func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { func CanWriteToBranch() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanWrite(unitType) {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
return
}
}
}
// CanEnableEditor checks if the user is allowed to write to the branch of the repo
func CanEnableEditor() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) { if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
ctx.NotFound("CanWriteToBranch denies permission", nil) ctx.NotFound("CanWriteToBranch denies permission", nil)
@ -42,75 +31,30 @@ func CanEnableEditor() func(ctx *Context) {
} }
} }
// RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission // RequireUnitWriter returns a middleware for requiring repository write to one of the unit permission
func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) { func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
for _, unitType := range unitTypes { for _, unitType := range unitTypes {
if ctx.Repo.CanWrite(unitType) { if ctx.Repo.CanWrite(unitType) {
return return
} }
} }
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) ctx.NotFound("RequireUnitWriter denies the request", nil)
} }
} }
// RequireRepoReader returns a middleware for requiring repository read to the specify unitType // RequireUnitReader returns a middleware for requiring repository write to one of the unit permission
func RequireRepoReader(unitType unit.Type) func(ctx *Context) { func RequireUnitReader(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanRead(unitType) {
if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
return
}
if log.IsTrace() {
if ctx.IsSigned {
log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
"User in Repo has Permissions: %-+v",
ctx.Doer,
unitType,
ctx.Repo.Repository,
ctx.Repo.Permission)
} else {
log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
"Anonymous user in Repo has Permissions: %-+v",
unitType,
ctx.Repo.Repository,
ctx.Repo.Permission)
}
}
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
return
}
}
}
// RequireRepoReaderOr returns a middleware for requiring repository write to one of the unit permission
func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
for _, unitType := range unitTypes { for _, unitType := range unitTypes {
if ctx.Repo.CanRead(unitType) { if ctx.Repo.CanRead(unitType) {
return return
} }
} if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
if log.IsTrace() { return
var format string
var args []any
if ctx.IsSigned {
format = "Permission Denied: User %-v cannot read ["
args = append(args, ctx.Doer)
} else {
format = "Permission Denied: Anonymous user cannot read ["
} }
for _, unit := range unitTypes {
format += "%-v, "
args = append(args, unit)
}
format = format[:len(format)-2] + "] in Repo %-v\n" +
"User in Repo has Permissions: %-+v"
args = append(args, ctx.Repo.Repository, ctx.Repo.Permission)
log.Trace(format, args...)
} }
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) ctx.NotFound("RequireUnitReader denies the request", nil)
} }
} }

View File

@ -677,24 +677,12 @@ func RepoAssignment(ctx *Context) {
} }
} }
// RepoRefType type of repo reference
type RepoRefType int
const (
// RepoRefUnknown is for legacy support, makes the code to "guess" the ref type
RepoRefUnknown RepoRefType = iota
RepoRefBranch
RepoRefTag
RepoRefCommit
)
const headRefName = "HEAD" const headRefName = "HEAD"
// RepoRef handles repository reference names when the ref name is not
// explicitly given
func RepoRef() func(*Context) { func RepoRef() func(*Context) {
// since no ref name is explicitly specified, ok to just use branch // old code does: return RepoRefByType(git.RefTypeBranch)
return RepoRefByType(RepoRefBranch) // in most cases, it is an abuse, so we just disable it completely and fix the abuses one by one (if there is anything wrong)
return nil
} }
func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string { func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
@ -710,29 +698,29 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
return "" return ""
} }
func getRefNameLegacy(ctx *Base, repo *Repository, reqPath, extraRef string) (string, RepoRefType) { func getRefNameLegacy(ctx *Base, repo *Repository, reqPath, extraRef string) (string, git.RefType) {
reqRefPath := path.Join(extraRef, reqPath) reqRefPath := path.Join(extraRef, reqPath)
reqRefPathParts := strings.Split(reqRefPath, "/") reqRefPathParts := strings.Split(reqRefPath, "/")
if refName := getRefName(ctx, repo, reqRefPath, RepoRefBranch); refName != "" { if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeBranch); refName != "" {
return refName, RepoRefBranch return refName, git.RefTypeBranch
} }
if refName := getRefName(ctx, repo, reqRefPath, RepoRefTag); refName != "" { if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeTag); refName != "" {
return refName, RepoRefTag return refName, git.RefTypeTag
} }
if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), reqRefPathParts[0]) { if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), reqRefPathParts[0]) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
repo.TreePath = strings.Join(reqRefPathParts[1:], "/") repo.TreePath = strings.Join(reqRefPathParts[1:], "/")
return reqRefPathParts[0], RepoRefCommit return reqRefPathParts[0], git.RefTypeCommit
} }
// FIXME: the old code falls back to default branch if "ref" doesn't exist, there could be an edge case: // FIXME: the old code falls back to default branch if "ref" doesn't exist, there could be an edge case:
// "README?ref=no-such" would read the README file from the default branch, but the user might expect a 404 // "README?ref=no-such" would read the README file from the default branch, but the user might expect a 404
repo.TreePath = reqPath repo.TreePath = reqPath
return repo.Repository.DefaultBranch, RepoRefBranch return repo.Repository.DefaultBranch, git.RefTypeBranch
} }
func getRefName(ctx *Base, repo *Repository, path string, pathType RepoRefType) string { func getRefName(ctx *Base, repo *Repository, path string, refType git.RefType) string {
switch pathType { switch refType {
case RepoRefBranch: case git.RefTypeBranch:
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist) ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
if len(ref) == 0 { if len(ref) == 0 {
// check if ref is HEAD // check if ref is HEAD
@ -762,9 +750,9 @@ func getRefName(ctx *Base, repo *Repository, path string, pathType RepoRefType)
} }
return ref return ref
case RepoRefTag: case git.RefTypeTag:
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist) return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
case RepoRefCommit: case git.RefTypeCommit:
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) { if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
@ -782,22 +770,18 @@ func getRefName(ctx *Base, repo *Repository, path string, pathType RepoRefType)
return commit.ID.String() return commit.ID.String()
} }
default: default:
panic(fmt.Sprintf("Unrecognized path type: %v", pathType)) panic(fmt.Sprintf("Unrecognized ref type: %v", refType))
} }
return "" return ""
} }
type RepoRefByTypeOptions struct { func repoRefFullName(typ git.RefType, shortName string) git.RefName {
IgnoreNotExistErr bool
}
func repoRefFullName(shortName string, typ RepoRefType) git.RefName {
switch typ { switch typ {
case RepoRefBranch: case git.RefTypeBranch:
return git.RefNameFromBranch(shortName) return git.RefNameFromBranch(shortName)
case RepoRefTag: case git.RefTypeTag:
return git.RefNameFromTag(shortName) return git.RefNameFromTag(shortName)
case RepoRefCommit: case git.RefTypeCommit:
return git.RefNameFromCommit(shortName) return git.RefNameFromCommit(shortName)
default: default:
setting.PanicInDevOrTesting("Unknown RepoRefType: %v", typ) setting.PanicInDevOrTesting("Unknown RepoRefType: %v", typ)
@ -807,8 +791,7 @@ func repoRefFullName(shortName string, typ RepoRefType) git.RefName {
// RepoRefByType handles repository reference name for a specific type // RepoRefByType handles repository reference name for a specific type
// of repository reference // of repository reference
func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) { func RepoRefByType(detectRefType git.RefType) func(*Context) {
opt := util.OptionalArg(opts)
return func(ctx *Context) { return func(ctx *Context) {
var err error var err error
refType := detectRefType refType := detectRefType
@ -824,14 +807,6 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
return return
} }
if ctx.Repo.GitRepo == nil {
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError(fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
return
}
}
// Get default branch. // Get default branch.
var refShortName string var refShortName string
reqPath := ctx.PathParam("*") reqPath := ctx.PathParam("*")
@ -861,13 +836,13 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
} }
ctx.Repo.IsViewBranch = true ctx.Repo.IsViewBranch = true
} else { // there is a path in request } else { // there is a path in request
guessLegacyPath := refType == RepoRefUnknown guessLegacyPath := refType == ""
if guessLegacyPath { if guessLegacyPath {
refShortName, refType = getRefNameLegacy(ctx.Base, ctx.Repo, reqPath, "") refShortName, refType = getRefNameLegacy(ctx.Base, ctx.Repo, reqPath, "")
} else { } else {
refShortName = getRefName(ctx.Base, ctx.Repo, reqPath, refType) refShortName = getRefName(ctx.Base, ctx.Repo, reqPath, refType)
} }
ctx.Repo.RefFullName = repoRefFullName(refShortName, refType) ctx.Repo.RefFullName = repoRefFullName(refType, refShortName)
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool) isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
if isRenamedBranch && has { if isRenamedBranch && has {
renamedBranchName := ctx.Data["RenamedBranchName"].(string) renamedBranchName := ctx.Data["RenamedBranchName"].(string)
@ -877,7 +852,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
return return
} }
if refType == RepoRefBranch && ctx.Repo.GitRepo.IsBranchExist(refShortName) { if refType == git.RefTypeBranch && ctx.Repo.GitRepo.IsBranchExist(refShortName) {
ctx.Repo.IsViewBranch = true ctx.Repo.IsViewBranch = true
ctx.Repo.BranchName = refShortName ctx.Repo.BranchName = refShortName
ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName) ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName)
@ -888,7 +863,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
return return
} }
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if refType == RepoRefTag && ctx.Repo.GitRepo.IsTagExist(refShortName) { } else if refType == git.RefTypeTag && ctx.Repo.GitRepo.IsTagExist(refShortName) {
ctx.Repo.IsViewTag = true ctx.Repo.IsViewTag = true
ctx.Repo.RefFullName = git.RefNameFromTag(refShortName) ctx.Repo.RefFullName = git.RefNameFromTag(refShortName)
ctx.Repo.TagName = refShortName ctx.Repo.TagName = refShortName
@ -919,9 +894,6 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL)) ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL))
} }
} else { } else {
if opt.IgnoreNotExistErr {
return
}
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refShortName)) ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refShortName))
return return
} }

View File

@ -110,41 +110,51 @@ type RepoSettingForm struct {
EnablePrune bool EnablePrune bool
// Advanced settings // Advanced settings
EnableCode bool EnableCode bool
EnableWiki bool DefaultCodeEveryoneAccess string
EnableExternalWiki bool
DefaultWikiBranch string EnableWiki bool
DefaultWikiEveryoneAccess string EnableExternalWiki bool
ExternalWikiURL string DefaultWikiBranch string
DefaultWikiEveryoneAccess string
ExternalWikiURL string
EnableIssues bool EnableIssues bool
DefaultIssuesEveryoneAccess string
EnableExternalTracker bool EnableExternalTracker bool
ExternalTrackerURL string ExternalTrackerURL string
TrackerURLFormat string TrackerURLFormat string
TrackerIssueStyle string TrackerIssueStyle string
ExternalTrackerRegexpPattern string ExternalTrackerRegexpPattern string
EnableCloseIssuesViaCommitInAnyBranch bool EnableCloseIssuesViaCommitInAnyBranch bool
EnableProjects bool
ProjectsMode string EnableProjects bool
EnableReleases bool ProjectsMode string
EnablePackages bool
EnablePulls bool EnableReleases bool
EnableActions bool
PullsIgnoreWhitespace bool EnablePackages bool
PullsAllowMerge bool
PullsAllowRebase bool EnablePulls bool
PullsAllowRebaseMerge bool PullsIgnoreWhitespace bool
PullsAllowSquash bool PullsAllowMerge bool
PullsAllowFastForwardOnly bool PullsAllowRebase bool
PullsAllowManualMerge bool PullsAllowRebaseMerge bool
PullsDefaultMergeStyle string PullsAllowSquash bool
EnableAutodetectManualMerge bool PullsAllowFastForwardOnly bool
PullsAllowRebaseUpdate bool PullsAllowManualMerge bool
DefaultDeleteBranchAfterMerge bool PullsDefaultMergeStyle string
DefaultAllowMaintainerEdit bool EnableAutodetectManualMerge bool
EnableTimetracker bool PullsAllowRebaseUpdate bool
AllowOnlyContributorsToTrackTime bool DefaultDeleteBranchAfterMerge bool
EnableIssueDependencies bool DefaultAllowMaintainerEdit bool
IsArchived bool EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool
EnableActions bool
IsArchived bool
// Signing Settings // Signing Settings
TrustModel string TrustModel string
@ -651,8 +661,8 @@ type NewReleaseForm struct {
Target string `form:"tag_target" binding:"Required;MaxSize(255)"` Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
Title string `binding:"MaxSize(255)"` Title string `binding:"MaxSize(255)"`
Content string Content string
Draft string Draft bool
TagOnly string TagOnly bool
Prerelease bool Prerelease bool
AddTagMsg bool AddTagMsg bool
Files []string Files []string

View File

@ -3,13 +3,15 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,13 +3,15 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,13 +3,15 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,13 +3,15 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,12 +3,14 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,12 +3,14 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,12 +3,14 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,12 +3,14 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -3,13 +3,15 @@
<div class="ui container medium-width"> <div class="ui container medium-width">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}} {{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post"> <form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label> <label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>

View File

@ -109,23 +109,17 @@
{{ctx.Locale.Tr "repo.release.delete_release"}} {{ctx.Locale.Tr "repo.release.delete_release"}}
</a> </a>
{{if .IsDraft}} {{if .IsDraft}}
<button class="ui small button" type="submit" name="draft" value="{{ctx.Locale.Tr "repo.release.save_draft"}}">{{ctx.Locale.Tr "repo.release.save_draft"}}</button> <button class="ui small button" type="submit" name="draft" value="1">{{ctx.Locale.Tr "repo.release.save_draft"}}</button>
<button class="ui small primary button"> <button class="ui small primary button">{{ctx.Locale.Tr "repo.release.publish"}}</button>
{{ctx.Locale.Tr "repo.release.publish"}}
</button>
{{else}} {{else}}
<button class="ui small primary button"> <button class="ui small primary button">{{ctx.Locale.Tr "repo.release.edit_release"}}</button>
{{ctx.Locale.Tr "repo.release.edit_release"}}
</button>
{{end}} {{end}}
{{else}} {{else}}
{{if not .tag_name}} {{if .ShowCreateTagOnlyButton}}
<button class="ui small button" name="tag_only" value="1">{{ctx.Locale.Tr "repo.release.add_tag"}}</button> <button class="ui small button" name="tag_only" value="1">{{ctx.Locale.Tr "repo.release.add_tag"}}</button>
{{end}} {{end}}
<button class="ui small button" name="draft" value="1">{{ctx.Locale.Tr "repo.release.save_draft"}}</button> <button class="ui small button" name="draft" value="1">{{ctx.Locale.Tr "repo.release.save_draft"}}</button>
<button class="ui small primary button"> <button class="ui small primary button">{{ctx.Locale.Tr "repo.release.publish"}}</button>
{{ctx.Locale.Tr "repo.release.publish"}}
</button>
{{end}} {{end}}
</div> </div>
</div> </div>

View File

@ -302,6 +302,15 @@
<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}}
@ -331,7 +340,7 @@
</div> </div>
<div class="inline field"> <div class="inline field">
{{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}} {{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}}
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_everyone_access"}}</label> <label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
<select name="default_wiki_everyone_access" class="ui selection dropdown"> <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 */}} {{/* 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="none" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
@ -374,6 +383,15 @@
</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">

View File

@ -234,6 +234,18 @@
"description": "page size of results", "description": "page size of results",
"name": "limit", "name": "limit",
"in": "query" "in": "query"
},
{
"enum": [
"system",
"default",
"all"
],
"type": "string",
"default": "system",
"description": "system, default or both kinds of webhooks",
"name": "type",
"in": "query"
} }
], ],
"responses": { "responses": {

View File

@ -14,6 +14,7 @@ import (
"testing" "testing"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -24,6 +25,7 @@ import (
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) *httptest.ResponseRecorder { func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) *httptest.ResponseRecorder {
@ -82,6 +84,29 @@ func TestEmptyRepoAddFile(t *testing.T) {
req = NewRequest(t, "GET", redirect) req = NewRequest(t, "GET", redirect)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "newly-added-test-file") assert.Contains(t, resp.Body.String(), "newly-added-test-file")
// the repo is not empty anymore
req = NewRequest(t, "GET", "/user30/empty")
resp = session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "test-file.md")
// if the repo is in incorrect state, it should be able to self-heal (recover to correct state)
user30EmptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
user30EmptyRepo.IsEmpty = true
user30EmptyRepo.DefaultBranch = "no-such"
_, err := db.GetEngine(db.DefaultContext).ID(user30EmptyRepo.ID).Cols("is_empty", "default_branch").Update(user30EmptyRepo)
require.NoError(t, err)
user30EmptyRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
assert.True(t, user30EmptyRepo.IsEmpty)
req = NewRequest(t, "GET", "/user30/empty")
resp = session.MakeRequest(t, req, http.StatusSeeOther)
redirect = test.RedirectURL(resp)
assert.Equal(t, "/user30/empty", redirect)
req = NewRequest(t, "GET", "/user30/empty")
resp = session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "test-file.md")
} }
func TestEmptyRepoUploadFile(t *testing.T) { func TestEmptyRepoUploadFile(t *testing.T) {

View File

@ -79,8 +79,12 @@ func TestMigrateGiteaForm(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
// Step 2: load the form // Step 2: load the form
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action") form := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`)
link, exists := form.Attr("action")
assert.True(t, exists, "The template has changed") assert.True(t, exists, "The template has changed")
serviceInput, exists := form.Find(`input[name="service"]`).Attr("value")
assert.True(t, exists)
assert.EqualValues(t, fmt.Sprintf("%d", structs.GiteaService), serviceInput)
// Step 4: submit the migration to only migrate issues // Step 4: submit the migration to only migrate issues
migratedRepoName := "otherrepo" migratedRepoName := "otherrepo"
req = NewRequestWithValues(t, "POST", link, map[string]string{ req = NewRequestWithValues(t, "POST", link, map[string]string{

View File

@ -39,7 +39,7 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st
postData["prerelease"] = "on" postData["prerelease"] = "on"
} }
if draft { if draft {
postData["draft"] = "Save Draft" postData["draft"] = "1"
} }
req = NewRequestWithValues(t, "POST", link, postData) req = NewRequestWithValues(t, "POST", link, postData)